♻️ Normalize organization naming across Penpot (#10977)

This commit is contained in:
Marina López 2026-07-31 12:04:10 +02:00 committed by GitHub
parent a2231a8bf9
commit 94f51afb20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 2117 additions and 2104 deletions

View File

@ -769,13 +769,13 @@
[value]
(when-not (str/blank? value) value))
(defn org-sso-discovery-uri
"Return the OIDC discovery URI from an org SSO config."
(defn organization-sso-discovery-uri
"Return the OIDC discovery URI from an organization SSO config."
[sso]
(non-blank-uri (:issuer sso)))
(defn prepare-org-sso-provider
"Build an OIDC provider map dynamically from the Nitrate org SSO config.
(defn prepare-organization-sso-provider
"Build an OIDC provider map dynamically from the Nitrate organization SSO config.
Uses OIDC discovery via :issuer when token/auth/user URIs are absent."
[cfg {:keys [client-id client-secret issuer]}]
(prepare-oidc-provider cfg
@ -788,18 +788,18 @@
:scopes default-oidc-scopes
:skip-ssrf-check? true}))
(defn build-org-sso-auth-redirect-uri
(defn build-organization-sso-auth-redirect-uri
"Build the OIDC authorization redirect URI for an organization SSO config.
Raises if the config is incomplete or OIDC discovery fails."
[cfg sso & {:keys [dest-url organization-id provider]}]
(let [organization-id (or organization-id (:organization-id sso))
issuer (org-sso-discovery-uri sso)
issuer (organization-sso-discovery-uri sso)
dest-url (or dest-url (str (cf/get :public-uri)))]
(when-not issuer
(ex/raise :type :validation
:code :invalid-sso-config
:hint "missing issuer"))
(let [oidc-provider (or provider (prepare-org-sso-provider cfg sso))
(let [oidc-provider (or provider (prepare-organization-sso-provider cfg sso))
state-token (tokens/generate cfg {:iss "oidc"
:dest-url dest-url
:organization-id organization-id
@ -839,7 +839,7 @@
(and (= error "access_denied")
(str/includes? description "unauthorized")))))
(defn- probe-org-sso-client-credentials
(defn- probe-organization-sso-client-credentials
"Probe the token endpoint with a dummy authorization code.
Valid client credentials are expected to answer with `invalid_grant`."
[cfg provider]
@ -864,10 +864,10 @@
and the client credentials are accepted by the token endpoint."
[cfg sso]
(try
(if (org-sso-discovery-uri sso)
(let [provider (prepare-org-sso-provider cfg sso)]
(and (build-org-sso-auth-redirect-uri cfg sso :provider provider)
(probe-org-sso-client-credentials cfg provider)))
(if (organization-sso-discovery-uri sso)
(let [provider (prepare-organization-sso-provider cfg sso)]
(and (build-organization-sso-auth-redirect-uri cfg sso :provider provider)
(probe-organization-sso-client-credentials cfg provider)))
false)
(catch Throwable _ false)))
@ -897,12 +897,12 @@
state (get params :state)
state (tokens/verify cfg {:token state :iss "oidc"})]
;; Org SSO flow: state carries :dest-url — exchange the authorization
;; Organization SSO flow: state carries :dest-url — exchange the authorization
;; code with the OIDC provider to verify authentication actually occurred.
(if-let [dest-url (:dest-url state)]
(let [organization-id (:organization-id state)
sso (nitrate/call cfg :get-org-sso {:organization-id organization-id})
provider (prepare-org-sso-provider cfg sso)
sso (nitrate/call cfg :get-organization-sso {:organization-id organization-id})
provider (prepare-organization-sso-provider cfg sso)
info (get-info cfg provider state code)
session (session/get-session request)
exp (or (:sso-token-exp info) (ct/in-future {:hours 48}))]

View File

@ -440,18 +440,18 @@
:id ::invite-to-team
:schema schema:invite-to-team))
(def ^:private schema:invite-to-org
(def ^:private schema:invite-to-organization
[:map
[:invited-by ::sm/text]
[:user-name [:maybe ::sm/text]]
[:token ::sm/text]
[:organization schema:organization-data]])
(def invite-to-org
"Org member invitation email."
(def invite-to-organization
"Organization member invitation email."
(template-factory
:id ::invite-to-org
:schema schema:invite-to-org))
:id ::invite-to-organization
:schema schema:invite-to-organization))
(def ^:private schema:organization-setup-sso
[:map

View File

@ -226,19 +226,19 @@
(-> (db/exec-one! cfg [sql (:profile-id session) (:id session)])
(db/get-update-count))))
(def ^:private sql:clear-org-sso-sessions
(def ^:private sql:clear-organization-sso-sessions
(str "UPDATE http_session_v2 "
"SET props = props #- ARRAY['~:sso', ?]::text[] "
"WHERE props IS NOT NULL "
"AND jsonb_exists(props -> '~:sso', ?)"))
(defn clear-org-sso-sessions!
(defn clear-organization-sso-sessions!
"Remove the SSO entry for organization-id from the props of every
session that currently holds it. The key is transit-encoded as the
string '~u<uuid>' under the '~:sso' path."
[pool organization-id]
(let [org-key (str "~u" organization-id)]
(db/exec! pool [sql:clear-org-sso-sessions org-key org-key])))
(let [organization-key (str "~u" organization-id)]
(db/exec! pool [sql:clear-organization-sso-sessions organization-key organization-key])))
(defn- renew-session?
[{:keys [id modified-at] :as session}]

View File

@ -162,7 +162,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:org-summary
(def ^:private schema:organization-summary
[:map
[:id ::sm/uuid]
[:name ::sm/text]
@ -189,7 +189,7 @@
:decode/json ct/inst
:encode/json inst-ms}}))
(def ^:private schema:profile-org
(def ^:private schema:profile-organization
[:map
[:is-member :boolean]
[:organization-id {:optional true} [:maybe ::sm/uuid]]
@ -253,13 +253,13 @@
[:map
[:licenses ::sm/boolean]])
(defn- get-team-org-api
(defn- get-team-organization-api
[cfg {:keys [team-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri "api/teams/" team-id)
cto/schema:team-with-organization params))
(defn- get-org-membership-api
(defn- get-organization-membership-api
[cfg {:keys [profile-id organization-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri
@ -267,9 +267,9 @@
organization-id
"members/"
profile-id)
schema:profile-org params))
schema:profile-organization params))
(defn- get-org-membership-by-team-api
(defn- get-organization-membership-by-team-api
[cfg {:keys [profile-id team-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri
@ -277,28 +277,28 @@
team-id
"users/"
profile-id)
schema:profile-org params))
schema:profile-organization params))
(defn- get-org-summary-api
(defn- get-organization-summary-api
[cfg {:keys [organization-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri
"api/organizations/"
organization-id
"summary")
schema:org-summary params))
schema:organization-summary params))
(defn- get-owned-orgs-api
(defn- get-owned-organizations-api
[cfg {:keys [profile-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri
"api/users/"
profile-id
"owned-organizations")
[:vector schema:org-summary]
[:vector schema:organization-summary]
params))
(def ^:private schema:org-summary-counts
(def ^:private schema:organization-summary-counts
[:map
[:id ::sm/uuid]
[:name ::sm/text]
@ -308,20 +308,20 @@
[:avatar-bg-url {:optional true} [:maybe ::sm/uri]]
[:logo-id {:optional true} [:maybe ::sm/uuid]]])
(defn- get-owned-orgs-summary-api
(defn- get-owned-organizations-summary-api
[cfg {:keys [profile-id] :as params}]
(let [orgs (request-to-nitrate cfg :get
(generate-nitrate-uri
"api/users/"
profile-id
"owned-organizations-summary")
[:vector schema:org-summary-counts]
params)]
(mapv (fn [org]
(if-let [logo-id (:logo-id org)]
(assoc org :custom-photo (generate-public-uri "assets/by-id/" logo-id))
org))
orgs)))
(let [organizations (request-to-nitrate cfg :get
(generate-nitrate-uri
"api/users/"
profile-id
"owned-organizations-summary")
[:vector schema:organization-summary-counts]
params)]
(mapv (fn [organization]
(if-let [logo-id (:logo-id organization)]
(assoc organization :custom-photo (generate-public-uri "assets/by-id/" logo-id))
organization))
organizations)))
(defn- cleanup-deleted-penpot-user-api
[cfg {:keys [profile-id] :as params}]
@ -332,7 +332,7 @@
"cleanup-after-deletion")
nil params))
(defn- set-team-org-api
(defn- set-team-organization-api
[cfg {:keys [organization-id team-id is-default] :as params}]
(let [params (assoc params :request-params {:team-id team-id
:is-your-penpot (true? is-default)})
@ -348,7 +348,7 @@
custom-photo
(assoc-in [:organization :custom-photo] custom-photo))))
(defn- add-profile-to-org-api
(defn- add-profile-to-organization-api
[cfg {:keys [profile-id organization-id team-id email] :as params}]
(let [request-params (cond-> {:user-id profile-id :team-id team-id}
(some? email) (assoc :email email))
@ -358,9 +358,9 @@
"api/organizations/"
organization-id
"add-user")
schema:profile-org params)))
schema:profile-organization params)))
(defn- remove-profile-from-org-api
(defn- remove-profile-from-organization-api
[cfg {:keys [profile-id organization-id user-who-delete-member deleted-by-role] :as params}]
(let [request-params (cond-> {:user-id profile-id}
(some? user-who-delete-member)
@ -375,7 +375,7 @@
"remove-user")
nil params)))
(defn- remove-team-from-org-api
(defn- remove-team-from-organization-api
[cfg {:keys [team-id organization-id] :as params}]
(let [params (assoc params :request-params {:team-id team-id})]
(request-to-nitrate cfg :post
@ -433,7 +433,7 @@
[:map
[:cancel-at [:maybe schema:timestamp]]])
(defn- get-org-permissions-api
(defn- get-organization-permissions-api
[cfg {:keys [organization-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri
@ -446,7 +446,7 @@
[:permissions [:map-of :keyword :string]]]
params))
(defn- get-org-sso-api
(defn- get-organization-sso-api
"Fetches the SSO configuration for an organization from Nitrate."
[cfg {:keys [organization-id] :as params}]
(request-to-nitrate cfg :get
@ -457,14 +457,14 @@
schema:nitrate-sso
params))
(defn- get-org-sso-by-team-api
(defn- get-organization-sso-by-team-api
[cfg {:keys [team-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri "api/teams/" team-id "sso")
schema:nitrate-sso
params))
(defn- get-org-members-api
(defn- get-organization-members-api
[cfg {:keys [organization-id] :as params}]
(request-to-nitrate cfg :get
(generate-nitrate-uri
@ -488,22 +488,22 @@
(defmethod ig/init-key ::client
[_ cfg]
(when (contains? cf/flags :nitrate)
{:get-team-org (partial get-team-org-api cfg)
:set-team-org (partial set-team-org-api cfg)
:get-org-membership (partial get-org-membership-api cfg)
:get-org-membership-by-team (partial get-org-membership-by-team-api cfg)
:get-org-summary (partial get-org-summary-api cfg)
:get-owned-orgs (partial get-owned-orgs-api cfg)
:get-owned-orgs-summary (partial get-owned-orgs-summary-api cfg)
:get-org-members (partial get-org-members-api cfg)
{:get-team-organization (partial get-team-organization-api cfg)
:set-team-organization (partial set-team-organization-api cfg)
:get-organization-membership (partial get-organization-membership-api cfg)
:get-organization-membership-by-team (partial get-organization-membership-by-team-api cfg)
:get-organization-summary (partial get-organization-summary-api cfg)
:get-owned-organizations (partial get-owned-organizations-api cfg)
:get-owned-organizations-summary (partial get-owned-organizations-summary-api cfg)
:get-organization-members (partial get-organization-members-api cfg)
:cleanup-deleted-penpot-user (partial cleanup-deleted-penpot-user-api cfg)
:add-profile-to-org (partial add-profile-to-org-api cfg)
:remove-profile-from-org (partial remove-profile-from-org-api cfg)
:get-org-permissions (partial get-org-permissions-api cfg)
:get-org-sso-by-team (partial get-org-sso-by-team-api cfg)
:get-org-sso (partial get-org-sso-api cfg)
:add-profile-to-organization (partial add-profile-to-organization-api cfg)
:remove-profile-from-organization (partial remove-profile-from-organization-api cfg)
:get-organization-permissions (partial get-organization-permissions-api cfg)
:get-organization-sso-by-team (partial get-organization-sso-by-team-api cfg)
:get-organization-sso (partial get-organization-sso-api cfg)
:delete-team (partial delete-team-api cfg)
:remove-team-from-org (partial remove-team-from-org-api cfg)
:remove-team-from-organization (partial remove-team-from-organization-api cfg)
:get-subscription (partial get-subscription-api cfg)
:get-subscription-warning (partial get-subscription-warning-api cfg)
:connectivity (partial get-connectivity-api cfg)
@ -514,9 +514,9 @@
;; UTILS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defonce ^:private team-org-owner-cache
(defonce ^:private team-organization-owner-cache
;; Short TTL: permission checks run on the read path, so we avoid an
;; HTTP call to nitrate per check. The org owner of a team rarely
;; HTTP call to nitrate per check. The organization owner of a team rarely
;; changes, and stale entries only grant read access for a few seconds.
(cache/create :expire "30s" :max-size 2048))
@ -526,24 +526,24 @@
[cfg]
(and (map? cfg) (some? (get cfg ::client))))
(def ^:private cache-miss ::no-org-owner)
(def ^:private cache-miss ::no-organization-owner)
(defn- get-team-org-owner-id
(defn- get-team-organization-owner-id
"Returns the organization owner-id for `team-id`, or nil. Cached
briefly, including negative results (teams with no organization) so
repeated unauthorized probes don't each hit nitrate."
[cfg team-id]
(let [owner-id (cache/get team-org-owner-cache team-id
(let [owner-id (cache/get team-organization-owner-cache team-id
(fn [team-id]
(let [team-with-org (call cfg :get-team-org {:team-id team-id})]
(or (get-in team-with-org [:organization :owner-id])
(let [team-with-organization (call cfg :get-team-organization {:team-id team-id})]
(or (get-in team-with-organization [:organization :owner-id])
cache-miss))))]
(when-not (= owner-id cache-miss)
owner-id)))
(defn organization-owner-of-team?
"True if `profile-id` is the owner of the organization that owns
`team-id`. Used to grant non-member org owners read-only access to the
`team-id`. Used to grant non-member organization owners read-only access to the
teams of their organizations. `cfg` must be a config map with the
nitrate client; raw db connections/pools yield false so internal
callers are unaffected. Returns false when the :nitrate flag is off."
@ -553,17 +553,17 @@
(nitrate-client? cfg)
(some? team-id)
(some? profile-id))
(= profile-id (get-team-org-owner-id cfg team-id)))))
(= profile-id (get-team-organization-owner-id cfg team-id)))))
(defn sso-session-authorized?
"Fetches the org-SSO config for the given organization or team and checks
"Fetches the organization-SSO config for the given organization or team and checks
whether the HTTP request has a valid session entry for it. Returns a map
with :authorized and :sso keys."
[cfg organization-id team-id request]
(let [session (session/get-session request)
sso (if organization-id
(call cfg :get-org-sso {:organization-id organization-id})
(call cfg :get-org-sso-by-team {:team-id team-id}))]
(call cfg :get-organization-sso {:organization-id organization-id})
(call cfg :get-organization-sso-by-team {:team-id team-id}))]
(if-not (:active sso)
{:authorized true :sso sso}
(if-not (str/blank? (:issuer sso))
@ -596,21 +596,21 @@
:cause cause)
profile)))))
(defn add-org-info-to-team
(defn add-organization-info-to-team
"Enriches a team map with organization information from Nitrate.
Adds organization-id, organization-name, organization-slug, organization-owner-id, and your-penpot fields.
Returns the original team unchanged if the request fails or org data is nil.
Returns the original team unchanged if the request fails or organization data is nil.
Propagates `:nitrate-unavailable` so the request is rejected when Nitrate is unreachable."
[cfg team params]
(try
(let [params (assoc (or params {}) :team-id (:id team))
team-with-org (call cfg :get-team-org params)
org (:organization team-with-org)]
(if (some? org)
(-> (cto/apply-organization team (assoc org :custom-photo
(when-let [logo-id (:logo-id org)]
team-with-organization (call cfg :get-team-organization params)
organization (:organization team-with-organization)]
(if (some? organization)
(-> (cto/apply-organization team (assoc organization :custom-photo
(when-let [logo-id (:logo-id organization)]
(generate-public-uri "assets/by-id/" logo-id))))
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-org)))))
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-organization)))))
team))
(catch Throwable cause
(if (= :nitrate-unavailable (-> cause ex-data :type))
@ -630,10 +630,10 @@
:team-id (:id team)
:organization-id (:organization-id params)
:is-default (:is-default params))
result (call cfg :set-team-org params)]
result (call cfg :set-team-organization params)]
(when (nil? result)
(ex/raise :type :internal
:code :failed-to-set-team-org
:code :failed-to-set-team-organization
:context {:team-id (:id team)
:organization-id (:organization-id params)}))
team))

View File

@ -250,13 +250,13 @@
f))
(defonce ^:private org-sso-auth-cache
(defonce ^:private organization-sso-auth-cache
(cache/create :expire "15m" :max-size 1024))
(defn invalidate-org-sso-cache-by-org!
"Invalidates all org-SSO authorization cache entries for the given organization-id."
(defn invalidate-organization-sso-cache-by-organization!
"Invalidates all organization-SSO authorization cache entries for the given organization-id."
[organization-id]
(cache/invalidate-if org-sso-auth-cache #(= (:organization-id %) organization-id)))
(cache/invalidate-if organization-sso-auth-cache #(= (:organization-id %) organization-id)))
(defn- wrap-nitrate-sso
"Enforce Nitrate organization SSO authentication for RPC handlers.
@ -268,16 +268,16 @@
4. Explicit :file-id param -> lookup file's team via join
5. :id param dispatched by ::rpc/id-type metadata (:team, :project, or :file)
Once the context is resolved, checks if the user is authorized within that org's
Once the context is resolved, checks if the user is authorized within that organization's
SSO session using nitrate/sso-session-authorized?. Authorized results are cached
by [profile-id cache-ref] for 15 minutes to avoid repeated lookups.
Only activates when:
- Nitrate flag is enabled
- Endpoint requires authentication (::auth true by default)
- Endpoint is not marked with ::nitrate/org-sso false
- Endpoint is not marked with ::nitrate/organization-sso false
Raises :nitrate-sso-required error if user is not authorized in the org."
Raises :nitrate-sso-required error if user is not authorized in the organization."
[_ f mdata]
(if (and (contains? cf/flags :nitrate)
(::auth mdata true) ;; only for endpoints that needs auth
@ -299,7 +299,7 @@
(let [cache-ref (or organization-id team-id project-id file-id)
cache-key [profile-id cache-ref]
cached (cache/get org-sso-auth-cache cache-key)
cached (cache/get organization-sso-auth-cache cache-key)
result (if (some? cached)
cached
(let [team-id (when-not organization-id
@ -314,7 +314,7 @@
entry {:authorized authorized
:organization-id (:organization-id sso)}]
(when authorized
(cache/get org-sso-auth-cache cache-key (constantly entry)))
(cache/get organization-sso-auth-cache cache-key (constantly entry)))
entry))]
(if (:authorized result)
(f cfg params)

View File

@ -42,11 +42,11 @@
:code :cant-move-default-team))))
(defn assert-membership [cfg profile-id organization-id]
(let [membership (nitrate/call cfg :get-org-membership {:profile-id profile-id
:organization-id organization-id})]
(let [membership (nitrate/call cfg :get-organization-membership {:profile-id profile-id
:organization-id organization-id})]
(when-not (:organization-id membership)
(ex/raise :type :validation
:code :organization-doesnt-exists))
:code :organization-does-not-exist))
(when-not (:is-member membership)
(ex/raise :type :validation
@ -181,7 +181,7 @@
{})))
{}))
(defn- build-leave-org-plan
(defn- build-leave-organization-plan
[{:keys [::db/conn]} default-team-id teams-to-delete keep-default-team-requested?]
(let [all-teams (cond-> (set teams-to-delete) default-team-id (conj default-team-id))
files-counts (get-team-files-counts conn all-teams)
@ -194,18 +194,18 @@
{:deletable-team-ids deletable
:keep-default-team? keep-default?
:delete-default-team? (boolean (and default-team-id (not keep-default?)))
:detach-from-org-team-ids to-detach}))
:detach-from-organization-team-ids to-detach}))
(defn get-leave-org-summary
(defn get-leave-organization-summary
[cfg default-team-id teams-to-delete teams-to-transfer-count teams-to-exit-count]
(let [{:keys [deletable-team-ids detach-from-org-team-ids]}
(build-leave-org-plan cfg default-team-id teams-to-delete nil)]
(let [{:keys [deletable-team-ids detach-from-organization-team-ids]}
(build-leave-organization-plan cfg default-team-id teams-to-delete nil)]
{:teams-to-delete (count deletable-team-ids)
:teams-to-transfer teams-to-transfer-count
:teams-to-exit teams-to-exit-count
:teams-to-detach (count detach-from-org-team-ids)}))
:teams-to-detach (count detach-from-organization-team-ids)}))
(def ^:private schema:leave-org
(def ^:private schema:leave-organization
[:map
[:id ::sm/uuid]
[:name ::sm/text]
@ -218,7 +218,7 @@
[:id ::sm/uuid]
[:reassign-to {:optional true} ::sm/uuid]]]]])
(def ^:private schema:get-leave-org-summary-result
(def ^:private schema:get-leave-organization-summary-result
[:map
[:teams-to-delete ::sm/int]
[:teams-to-transfer ::sm/int]
@ -227,40 +227,40 @@
[:member-added-at [:maybe ct/schema:inst]]
[:organization-member-count-before ::sm/int]])
(def ^:private schema:get-leave-org-summary
(def ^:private schema:get-leave-organization-summary
[:map
[:id ::sm/uuid]
[:default-team-id ::sm/uuid]])
(defn- get-organization-teams-for-user
[{:keys [::db/conn] :as cfg} org-summary profile-id]
(let [org-team-ids (->> (:teams org-summary)
(map :id))
ids-array (db/create-array conn "uuid" org-team-ids)]
[{:keys [::db/conn] :as cfg} organization-summary profile-id]
(let [organization-team-ids (->> (:teams organization-summary)
(map :id))
ids-array (db/create-array conn "uuid" organization-team-ids)]
(db/exec! conn [sql:get-member-teams-info profile-id ids-array])))
(defn- calculate-valid-teams
([org-teams default-team-id]
([organization-teams default-team-id]
(let [;; valid default team is the one which id is default-team-id
valid-default-team (d/seek #(= default-team-id (:id %)) org-teams)
valid-default-team (d/seek #(= default-team-id (:id %)) organization-teams)
;; Remove your-penpot for the rest of validations
org-teams (remove #(= default-team-id (:id %)) org-teams)
organization-teams (remove #(= default-team-id (:id %)) organization-teams)
;; valid teams to delete are those that the user is owner, and only have one member
valid-teams-to-delete-ids (->> org-teams
valid-teams-to-delete-ids (->> organization-teams
(filter #(and (:is-owner %)
(= (:num-members %) 1)))
(map :id)
(into #{}))
;; valid teams to transfer are those that the user is owner, and have more than one member
valid-teams-to-transfer (->> org-teams
valid-teams-to-transfer (->> organization-teams
(filter #(and (:is-owner %)
(> (:num-members %) 1))))
;; valid teams to exit are those that the user isn't owner, and have more than one member
valid-teams-to-exit (->> org-teams
valid-teams-to-exit (->> organization-teams
(filter #(and (not (:is-owner %))
(> (:num-members %) 1))))]
{:valid-teams-to-delete-ids valid-teams-to-delete-ids
@ -269,17 +269,17 @@
:valid-default-team valid-default-team})))
(defn get-valid-teams [cfg organization-id profile-id default-team-id]
(let [org-summary (nitrate/call cfg :get-org-summary {:organization-id organization-id})
org-teams (get-organization-teams-for-user cfg org-summary profile-id)]
(calculate-valid-teams org-teams default-team-id)))
(let [organization-summary (nitrate/call cfg :get-organization-summary {:organization-id organization-id})
organization-teams (get-organization-teams-for-user cfg organization-summary profile-id)]
(calculate-valid-teams organization-teams default-team-id)))
(defn- assert-valid-teams [cfg profile-id organization-id default-team-id teams-to-delete teams-to-leave]
(let [org-summary (nitrate/call cfg :get-org-summary {:organization-id organization-id})
org-teams (get-organization-teams-for-user cfg org-summary profile-id)
(let [organization-summary (nitrate/call cfg :get-organization-summary {:organization-id organization-id})
organization-teams (get-organization-teams-for-user cfg organization-summary profile-id)
{:keys [valid-teams-to-delete-ids
valid-teams-to-transfer
valid-teams-to-exit
valid-default-team]} (calculate-valid-teams org-teams default-team-id)
valid-default-team]} (calculate-valid-teams organization-teams default-team-id)
@ -297,7 +297,7 @@
;; - if it has a reassign-to, it belongs to valid-teams-to-transfer and
;; the reassign-to is a member of the team and not the current user;
;; - if it hasn't a reassign-to, check that it belongs to valid-teams-to-exit
teams-by-id (d/index-by :id org-teams)
teams-by-id (d/index-by :id organization-teams)
valid-teams-to-leave? (and
(= valid-teams-to-leave-ids (->> teams-to-leave (map :id) (into #{})))
(every? (fn [{:keys [id reassign-to]}]
@ -308,10 +308,10 @@
(contains? members reassign-to)))
(contains? valid-teams-to-exit-ids id)))
teams-to-leave))]
;; the org owner cannot leave
(when (= (:owner-id org-summary) profile-id)
;; the organization owner cannot leave
(when (= (:owner-id organization-summary) profile-id)
(ex/raise :type :validation
:code :org-owner-cannot-leave))
:code :organization-owner-cannot-leave))
(when (or
(not valid-teams-to-delete?)
@ -322,14 +322,14 @@
(defn leave-org
(defn leave-organization
[{:keys [::db/conn] :as cfg}
{:keys [profile-id id name default-team-id teams-to-delete teams-to-leave skip-validation keep-default-team-requested?
user-who-delete-member deleted-by-role]}]
(let [org-prefix (str "[" (d/sanitize-string name) "] ")
(let [organization-prefix (str "[" (d/sanitize-string name) "] ")
{:keys [deletable-team-ids
keep-default-team?
detach-from-org-team-ids]} (build-leave-org-plan cfg default-team-id teams-to-delete keep-default-team-requested?)]
detach-from-organization-team-ids]} (build-leave-organization-plan cfg default-team-id teams-to-delete keep-default-team-requested?)]
;; assert that the received teams are valid, checking the different constraints
(when-not skip-validation
@ -346,21 +346,21 @@
(doseq [{:keys [id reassign-to]} teams-to-leave]
(teams/leave-team cfg {:profile-id profile-id :id id :reassign-to reassign-to}))
;; Process org "Your Penpot" team: keep with prefix if needed, otherwise delete.
;; Process organization "Your Penpot" team: keep with prefix if needed, otherwise delete.
(when default-team-id
(if keep-default-team?
(db/exec! conn [sql:prefix-team-name-and-unset-default org-prefix default-team-id])
(db/exec! conn [sql:prefix-team-name-and-unset-default organization-prefix default-team-id])
(teams/delete-team cfg {:profile-id profile-id
:team-id default-team-id})))
;; Detach retained owned teams from the organization in Nitrate.
;; Nitrate will rehome them to its fallback/default org.
(doseq [team-id detach-from-org-team-ids]
(nitrate/call cfg :remove-team-from-org {:team-id team-id
:organization-id id}))
;; Nitrate will rehome them to its fallback/default organization.
(doseq [team-id detach-from-organization-team-ids]
(nitrate/call cfg :remove-team-from-organization {:team-id team-id
:organization-id id}))
;; Api call to nitrate
(nitrate/call cfg :remove-profile-from-org
(nitrate/call cfg :remove-profile-from-organization
{:profile-id profile-id
:organization-id id
:user-who-delete-member user-who-delete-member
@ -369,33 +369,33 @@
nil))
(sv/defmethod ::leave-org
(sv/defmethod ::leave-organization
{::rpc/auth true
::doc/added "2.15"
::sm/params schema:leave-org
::sm/params schema:leave-organization
::db/transaction true}
[cfg {:keys [::rpc/profile-id] :as params}]
(leave-org cfg (assoc params
:profile-id profile-id
:user-who-delete-member profile-id
:deleted-by-role "organization-member")))
(leave-organization cfg (assoc params
:profile-id profile-id
:user-who-delete-member profile-id
:deleted-by-role "organization-member")))
(sv/defmethod ::get-leave-org-summary
(sv/defmethod ::get-leave-organization-summary
{::rpc/auth true
::doc/added "2.18"
::sm/params schema:get-leave-org-summary
::sm/result schema:get-leave-org-summary-result
::sm/params schema:get-leave-organization-summary
::sm/result schema:get-leave-organization-summary-result
::db/transaction true}
[cfg {:keys [::rpc/profile-id id default-team-id]}]
(let [{:keys [valid-teams-to-delete-ids
valid-teams-to-transfer
valid-teams-to-exit
valid-default-team]} (get-valid-teams cfg id profile-id default-team-id)
membership (nitrate/call cfg :get-org-membership
membership (nitrate/call cfg :get-organization-membership
{:profile-id profile-id
:organization-id id})
organization-members (nitrate/call cfg :get-org-members
organization-members (nitrate/call cfg :get-organization-members
{:organization-id id})
teams-to-transfer-count (count valid-teams-to-transfer)
teams-to-exit-count (count valid-teams-to-exit)]
@ -403,20 +403,20 @@
(ex/raise :type :validation
:code :not-valid-teams))
(assoc
(get-leave-org-summary cfg default-team-id valid-teams-to-delete-ids teams-to-transfer-count teams-to-exit-count)
(get-leave-organization-summary cfg default-team-id valid-teams-to-delete-ids teams-to-transfer-count teams-to-exit-count)
:member-added-at (:created-at membership)
:organization-member-count-before (count organization-members))))
(def ^:private schema:remove-team-from-org
(def ^:private schema:remove-team-from-organization
[:map
[:team-id ::sm/uuid]
[:organization-id ::sm/uuid]
[:organization-name ::sm/text]])
(sv/defmethod ::remove-team-from-org
(sv/defmethod ::remove-team-from-organization
{::doc/added "2.17"
::sm/params schema:remove-team-from-org}
::sm/params schema:remove-team-from-organization}
[cfg {:keys [::rpc/profile-id team-id organization-id organization-name]}]
(assert-is-owner cfg profile-id team-id)
@ -424,24 +424,24 @@
(assert-membership cfg profile-id organization-id)
;; Check moveTeams permission on the source organization
(when (contains? cf/flags :nitrate)
(let [org-perms (nitrate/call cfg :get-org-permissions
{:organization-id organization-id})]
(if (nil? org-perms)
(let [organization-perms (nitrate/call cfg :get-organization-permissions
{:organization-id organization-id})]
(if (nil? organization-perms)
(ex/raise :type :validation
:code :not-allowed
:hint "Unable to verify organization permissions")
(when-not (nitrate-perms/allowed? :move-team
{:org-perms org-perms
{:organization-perms organization-perms
:profile-id profile-id})
(ex/raise :type :validation
:code :not-allowed
:hint "You are not allowed to move teams that are part of this organization. If you need more information, contact the owner.")))))
;; Api call to nitrate
(nitrate/call cfg :remove-team-from-org {:team-id team-id :organization-id organization-id})
(nitrate/call cfg :remove-team-from-organization {:team-id team-id :organization-id organization-id})
;; Notify connected users
(notifications/notify-team-change cfg {:id team-id :organization {:name organization-name}} "dashboard.team-no-longer-belong-org")
(notifications/notify-team-change cfg {:id team-id :organization {:name organization-name}} "dashboard.team-no-longer-belong-organization")
nil)
(def ^:private sql:delete-team-external-invitations
@ -457,12 +457,12 @@
AND deleted_at IS NULL")
(defn- get-external-invitation-info
"Returns info about external (non-org-member) invitations pending for a team.
External invitations are those sent to users who are not members of the given org.
"Returns info about external (non-organization-member) invitations pending for a team.
External invitations are those sent to users who are not members of the given organization.
Returns {:allows-anybody bool :external-emails [...]}"
[{:keys [::db/conn] :as cfg} team-id organization-id]
(let [org-perms (nitrate/call cfg :get-org-permissions {:organization-id organization-id})
allows-anybody (nitrate-perms/allowed? :add-anybody-to-team {:org-perms org-perms})]
(let [organization-perms (nitrate/call cfg :get-organization-permissions {:organization-id organization-id})
allows-anybody (nitrate-perms/allowed? :add-anybody-to-team {:organization-perms organization-perms})]
(if allows-anybody
{:allows-anybody true :external-emails []}
(let [emails (map :email (noh/get-team-invitation-emails conn team-id))]
@ -470,9 +470,9 @@
{:allows-anybody false :external-emails []}
(let [emails-array (db/create-array conn "text" (vec emails))
profiles (db/exec! conn [sql:get-profiles-by-emails emails-array])
org-member-ids (into #{} (nitrate/call cfg :get-org-members {:organization-id organization-id}))
organization-member-ids (into #{} (nitrate/call cfg :get-organization-members {:organization-id organization-id}))
external-emails (->> profiles
(remove #(contains? org-member-ids (:id %)))
(remove #(contains? organization-member-ids (:id %)))
(map :email)
(vec))]
{:allows-anybody false :external-emails external-emails}))))))
@ -494,60 +494,60 @@
(assert-membership cfg profile-id organization-id)
(when (contains? cf/flags :nitrate)
(let [org-member-ids-before (into #{} (nitrate/call cfg :get-org-members {:organization-id organization-id}))
team-with-org (nitrate/call cfg :get-team-org {:team-id team-id})
source-org-id (get-in team-with-org [:organization :id])
source-org-perms (when source-org-id
(nitrate/call cfg :get-org-permissions
{:organization-id source-org-id}))
target-org-perms (nitrate/call cfg :get-org-permissions
{:organization-id organization-id})
target-org-same-owner? (and (some? source-org-perms)
(some? target-org-perms)
(= (:owner-id source-org-perms)
(:owner-id target-org-perms)))]
(when (nil? target-org-perms)
(let [organization-member-ids-before (into #{} (nitrate/call cfg :get-organization-members {:organization-id organization-id}))
team-with-organization (nitrate/call cfg :get-team-organization {:team-id team-id})
source-organization-id (get-in team-with-organization [:organization :id])
source-organization-perms (when source-organization-id
(nitrate/call cfg :get-organization-permissions
{:organization-id source-organization-id}))
target-organization-perms (nitrate/call cfg :get-organization-permissions
{:organization-id organization-id})
target-organization-same-owner? (and (some? source-organization-perms)
(some? target-organization-perms)
(= (:owner-id source-organization-perms)
(:owner-id target-organization-perms)))]
(when (nil? target-organization-perms)
(ex/raise :type :validation
:code :not-allowed
:hint "Unable to verify organization permissions"))
;; Team already belongs to an organization: check move-teams on source org.
(when (some? source-org-id)
(when (nil? source-org-perms)
;; Team already belongs to an organization: check move-teams on the source organization.
(when (some? source-organization-id)
(when (nil? source-organization-perms)
(ex/raise :type :validation
:code :not-allowed
:hint "Unable to verify organization permissions"))
(when-not (nitrate-perms/allowed? :move-team
{:org-perms source-org-perms
{:organization-perms source-organization-perms
:profile-id profile-id
:target-org-same-owner? target-org-same-owner?})
:target-organization-same-owner? target-organization-same-owner?})
(ex/raise :type :validation
:code :not-allowed
:hint "You are not allowed to move teams that are part of this organization. If you need more information, contact the owner.")))
;; Always check target create-teams permission (new/add and move flows).
(when-not (nitrate-perms/allowed? :create-team
{:org-perms target-org-perms
{:organization-perms target-organization-perms
:profile-id profile-id})
(ex/raise :type :validation
:code :not-allowed
:hint "You are not allowed to add teams in this organization"))
;; Add teammates to the org if needed
;; Add teammates to the organization if needed
(let [team-members (db/query cfg :team-profile-rel {:team-id team-id})
new-member-ids (->> team-members
(map :profile-id)
(remove #{profile-id})
(remove org-member-ids-before))]
(remove organization-member-ids-before))]
(doseq [member-id new-member-ids]
(teams/initialize-user-in-nitrate-org cfg member-id organization-id)))
(teams/initialize-user-in-nitrate-organization cfg member-id organization-id)))
;; Api call to nitrate
(let [team (nitrate/call cfg :set-team-org {:team-id team-id
:organization-id organization-id
:is-default false})]
(let [team (nitrate/call cfg :set-team-organization {:team-id team-id
:organization-id organization-id
:is-default false})]
;; Notify connected users
(notifications/notify-team-change cfg team "dashboard.team-belong-org"))
(notifications/notify-team-change cfg team "dashboard.team-belong-organization"))
;; Delete pending invitations for users who are not members of the target organization
(let [{:keys [allows-anybody external-emails]} (get-external-invitation-info cfg team-id organization-id)]
@ -556,21 +556,21 @@
emails-array (db/create-array conn "text" external-emails)]
(db/exec! conn [sql:delete-team-external-invitations team-id emails-array]))))
;; Send warnings via email if the org has sso
;; Send warnings via email if the organization has sso
(neh/send-organization-setup-sso-emails-for-team!
cfg organization-id team-id org-member-ids-before)))
cfg organization-id team-id organization-member-ids-before)))
nil)
(def ^:private schema:check-org-members-params
[:map {:title "CheckOrgMembersParams"}
(def ^:private schema:check-organization-members-params
[:map {:title "CheckOrganizationMembersParams"}
[:organization-id ::sm/uuid]
[:emails [:vector ::sm/email]]])
(sv/defmethod ::check-org-members
(sv/defmethod ::check-organization-members
{::rpc/auth true
::doc/added "2.17"
::sm/params schema:check-org-members-params
::sm/params schema:check-organization-members-params
::sm/result [:map-of :string :boolean]
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id organization-id emails]}]
@ -579,23 +579,23 @@
(let [emails-array (db/create-array conn "text" emails)
profiles (db/exec! conn [sql:get-profiles-by-emails emails-array])
email->id (into {} (map (fn [p] [(:email p) (:id p)])) profiles)
org-member-ids (into #{} (nitrate/call cfg :get-org-members {:organization-id organization-id}))]
organization-member-ids (into #{} (nitrate/call cfg :get-organization-members {:organization-id organization-id}))]
(into {}
(map (fn [email]
(let [pid (get email->id email)]
[email (boolean (and pid (contains? org-member-ids pid)))])))
[email (boolean (and pid (contains? organization-member-ids pid)))])))
emails)))
{}))
(def ^:private schema:all-org-members-in-team-params
[:map {:title "CheckOrgMembersInTeamParams"}
(def ^:private schema:all-organization-members-in-team-params
[:map {:title "CheckOrganizationMembersInTeamParams"}
[:team-id ::sm/uuid]
[:organization-id ::sm/uuid]])
(sv/defmethod ::all-org-members-in-team
(sv/defmethod ::all-organization-members-in-team
{::rpc/auth true
::doc/added "2.17"
::sm/params schema:all-org-members-in-team-params
::sm/params schema:all-organization-members-in-team-params
::sm/result ::sm/boolean}
[cfg {:keys [::rpc/profile-id team-id organization-id]}]
(if (contains? cf/flags :nitrate)
@ -604,22 +604,22 @@
(ex/raise :type :validation
:code :insufficient-permissions))
(assert-membership cfg profile-id organization-id)
(let [org-members (nitrate/call cfg :get-org-members {:organization-id organization-id})
org-member-ids (into #{} org-members)
(let [organization-members (nitrate/call cfg :get-organization-members {:organization-id organization-id})
organization-member-ids (into #{} organization-members)
team-members (db/query cfg :team-profile-rel {:team-id team-id})
team-member-ids (into #{} (map :profile-id team-members))]
(every? #(contains? team-member-ids %) org-member-ids)))
(every? #(contains? team-member-ids %) organization-member-ids)))
false))
(def ^:private schema:all-team-members-in-orgs-params
[:map {:title "CheckTeamMembersInOrgsParams"}
(def ^:private schema:all-team-members-in-organizations-params
[:map {:title "CheckTeamMembersInOrganizationsParams"}
[:team-id ::sm/uuid]
[:organization-ids [:vector ::sm/uuid]]])
(sv/defmethod ::all-team-members-in-orgs
(sv/defmethod ::all-team-members-in-organizations
{::rpc/auth true
::doc/added "2.17"
::sm/params schema:all-team-members-in-orgs-params
::sm/params schema:all-team-members-in-organizations-params
::sm/result [:map-of ::sm/uuid ::sm/boolean]}
[cfg {:keys [::rpc/profile-id team-id organization-ids]}]
(if (contains? cf/flags :nitrate)
@ -630,15 +630,15 @@
(let [team-members (db/query cfg :team-profile-rel {:team-id team-id})
team-member-ids (into #{} (map :profile-id team-members))]
;; Validate requester membership in all orgs before fetching members.
;; Validate requester membership in all organizations before fetching members.
(run! #(assert-membership cfg profile-id %) organization-ids)
(into {}
(map (fn [organization-id]
(let [org-members (nitrate/call cfg :get-org-members {:organization-id organization-id})
org-member-ids (into #{} org-members)]
(let [organization-members (nitrate/call cfg :get-organization-members {:organization-id organization-id})
organization-member-ids (into #{} organization-members)]
[organization-id
(every? #(contains? org-member-ids %) team-member-ids)])))
(every? #(contains? organization-member-ids %) team-member-ids)])))
organization-ids)))
{}))
@ -682,7 +682,7 @@
(sv/defmethod ::check-nitrate-sso
"Check if a user needs to login into the organization SSO.
Accepts either team-id (to look up the org via the team) or organization-id directly.
Accepts either team-id (to look up the organization via the team) or organization-id directly.
Returns {:authorized true} when SSO is not active or the user cannot access the team.
Returns {:authorized false :redirect-uri <url>} when SSO is active;
the client must redirect there. The OIDC provider itself handles
@ -702,11 +702,11 @@
{:keys [authorized sso]} (nitrate/sso-session-authorized? cfg organization-id team-id request)]
(if authorized
{:authorized true}
(if (oidc/org-sso-discovery-uri sso)
(if (oidc/organization-sso-discovery-uri sso)
{:authorized false
:redirect-uri (oidc/build-org-sso-auth-redirect-uri cfg sso
:dest-url url
:organization-id organization-id)}
:redirect-uri (oidc/build-organization-sso-auth-redirect-uri cfg sso
:dest-url url
:organization-id organization-id)}
{:authorized false
:redirect-uri nil}))))
{:authorized true}))

View File

@ -495,10 +495,10 @@
{:id profile-id})
;; Delete owned organizations on the fly (no grace period).
;; Nitrate iterates the user's owned orgs and, per org, calls
;; Nitrate iterates the user's owned organizations and, per organization, calls
;; Penpot back through two paths: ::notify-user-organizations-deletion
;; (during delete-owned-orgs) and ::notify-organization-deletion.
;; Both preserve org teams unchanged and only prefix or delete
;; (during delete-owned-organizations) and ::notify-organization-deletion.
;; Both preserve organization teams unchanged and only prefix or delete
;; imported "Your Penpot" teams according to whether they still have files.
;; Let Nitrate clean up the data associated with the deleted Penpot user:
;; owned organizations, remaining memberships, and subscription cancellation.
@ -562,7 +562,7 @@
::sm/result schema:get-owned-organizations-summary-result}
[cfg {:keys [::rpc/profile-id]}]
(if (contains? cf/flags :nitrate)
(or (nitrate/call cfg :get-owned-orgs-summary {:profile-id profile-id}) [])
(or (nitrate/call cfg :get-owned-organizations-summary {:profile-id profile-id}) [])
[]))
;; --- HELPERS

View File

@ -198,7 +198,7 @@
(dm/with-open [conn (db/open pool)]
(cond->> (get-teams conn profile-id)
(contains? cf/flags :nitrate)
(map #(nitrate/add-org-info-to-team cfg % params))
(map #(nitrate/add-organization-info-to-team cfg % params))
(contains? cf/flags :nitrate)
(remove #(get-in % [:organization :expired-license])))))
@ -245,10 +245,10 @@
[cfg {:keys [::rpc/profile-id id file-id] :as params}]
(let [team (get-team cfg :profile-id profile-id :team-id id :file-id file-id)]
(if (contains? cf/flags :nitrate)
(nitrate/add-org-info-to-team cfg team params)
(nitrate/add-organization-info-to-team cfg team params)
team)))
(defn- get-org-owner-viewer-team
(defn- get-organization-owner-viewer-team
"When `profile-id` is a non-member owner of the organization that owns
the requested team, returns the team shaped with viewer permissions;
otherwise nil. `cfg` must carry the nitrate client."
@ -305,7 +305,7 @@
(-> result
(decode-row)
(process-permissions))
(or (get-org-owner-viewer-team cfg profile-id default-team-id params)
(or (get-organization-owner-viewer-team cfg profile-id default-team-id params)
(ex/raise :type :not-found
:code :team-does-not-exist)))))
@ -535,17 +535,17 @@
(quotes/check! cfg {::quotes/id ::quotes/teams-per-profile
::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 creating inside an organization, verify the user has permission to do so.
;; Fail closed: if organization 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)
(let [organization-perms (nitrate/call cfg :get-organization-permissions
{:organization-id organization-id})]
(if (nil? organization-perms)
(ex/raise :type :validation
:code :not-allowed
:hint "Unable to verify organization permissions")
(when-not (nitrate-perms/allowed? :create-team
{:org-perms org-perms
{:organization-perms organization-perms
:profile-id profile-id})
(ex/raise :type :validation
:code :not-allowed
@ -563,7 +563,7 @@
{::audit/props {:id (:id team)}})))
(defn create-default-org-team
(defn create-default-organization-team
[cfg profile-id organization-id]
(quotes/check! cfg {::quotes/id ::quotes/teams-per-profile
::quotes/profile-id profile-id})
@ -579,11 +579,11 @@
team (create-team cfg params)]
(select-keys team [:id])))
(defn initialize-user-in-nitrate-org
(defn initialize-user-in-nitrate-organization
"If needed, create a default team for the user on the organization,
and notify Nitrate that an user has been added to an org."
and notify Nitrate that a user has been added to an organization."
([cfg profile-id organization-id]
(initialize-user-in-nitrate-org cfg profile-id organization-id nil))
(initialize-user-in-nitrate-organization cfg profile-id organization-id nil))
([cfg profile-id organization-id email]
(assert (db/connection-map? cfg)
"expected cfg with valid connection")
@ -592,24 +592,24 @@
cfg
(fn [{:keys [::db/conn] :as tx-cfg}]
(let [membership (nitrate/call cfg :get-org-membership {:profile-id profile-id
:organization-id organization-id})]
(let [membership (nitrate/call cfg :get-organization-membership {:profile-id profile-id
:organization-id organization-id})]
;; Only when the user doesn't belong to the organization yet
(when (and
(some? (:organization-id membership)) ;; the organization exists
(not (:is-member membership))) ;; the user is not a member of the org yet
(not (:is-member membership))) ;; the user is not a member of the organization yet
(let [organization-id organization-id
default-team (create-default-org-team (assoc tx-cfg ::db/conn conn) profile-id organization-id)
default-team (create-default-organization-team (assoc tx-cfg ::db/conn conn) profile-id organization-id)
default-team-id (:id default-team)
result (nitrate/call tx-cfg :add-profile-to-org (cond-> {:profile-id profile-id
:team-id default-team-id
:organization-id organization-id}
(some? email) (assoc :email email)))]
result (nitrate/call tx-cfg :add-profile-to-organization (cond-> {:profile-id profile-id
:team-id default-team-id
:organization-id organization-id}
(some? email) (assoc :email email)))]
(when (not (:is-member result))
(ex/raise :type :internal
:code :failed-add-profile-org-nitrate
:code :failed-add-profile-organization-nitrate
:context {:profile-id profile-id
:organization-id organization-id
:default-team-id default-team-id}))
@ -622,12 +622,12 @@
(assert (db/connection-map? cfg)
"expected cfg with valid connection")
(when (contains? cf/flags :nitrate)
(let [membership (nitrate/call cfg :get-org-membership-by-team {:profile-id profile-id :team-id team-id})]
(let [membership (nitrate/call cfg :get-organization-membership-by-team {:profile-id profile-id :team-id team-id})]
;; Only when the team belong to an organization and the user is not a member
(when (and
(some? (:organization-id membership)) ;; the team do belong to an organization
(not (:is-member membership))) ;; the user is not a member of the org yet
(initialize-user-in-nitrate-org cfg profile-id (:organization-id membership)))))
(not (:is-member membership))) ;; the user is not a member of the organization yet
(initialize-user-in-nitrate-organization cfg profile-id (:organization-id membership)))))
(db/insert! conn :team-profile-rel (assoc params :id (uuid/next)) options)))
(defn create-team
@ -804,16 +804,16 @@
(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)
(nitrate/add-organization-info-to-team cfg team params)
team)
perms (get team :permissions)
org (:organization team)
in-org? (and (contains? cf/flags :nitrate) org)
organization (:organization team)
in-organization? (and (contains? cf/flags :nitrate) organization)
can-delete?
(if in-org?
(if in-organization?
(nitrate-perms/allowed? :delete-team
{:org-perms {:owner-id (dm/get-in team [:organization :owner-id])
:permissions (dm/get-in team [:organization :permissions])}
{:organization-perms {:owner-id (dm/get-in team [:organization :owner-id])
:permissions (dm/get-in team [:organization :permissions])}
:profile-id profile-id
:team-perms perms})
(boolean (:is-owner perms)))]
@ -823,8 +823,8 @@
:code :only-owner-can-delete-team))
;; Protect the user's personal default team from deletion.
;; Org-scoped default teams ("Your Penpot") are allowed to be deleted when they have no files.
(when (and (:is-default team) (not in-org?))
;; Organization-scoped default teams ("Your Penpot") are allowed to be deleted when they have no files.
(when (and (:is-default team) (not in-organization?))
(ex/raise :type :validation
:code :non-deletable-team
:hint "impossible to delete default team"))

View File

@ -44,7 +44,7 @@
update set role = ?, valid_until = ?, updated_at = now()
returning *")
(def sql:upsert-org-invitation
(def sql:upsert-organization-invitation
"insert into team_invitation(id, team_id, org_id, email_to, created_by, role, valid_until)
values (?, null, ?, ?, ?, ?, ?)
on conflict(org_id, email_to) where team_id is null do
@ -86,8 +86,8 @@
[:role types.team/schema:role]
[:email ::sm/email]])
(def ^:private schema:create-org-invitation
[:map {:title "params:create-org-invitation"}
(def ^:private schema:create-organization-invitation
[:map {:title "params:create-organization-invitation"}
[::rpc/profile-id ::sm/uuid]
[:organization
[:map
@ -107,8 +107,8 @@
(def ^:private check-create-invitation-params
(sm/check-fn schema:create-invitation))
(def ^:private check-create-org-invitation-params
(sm/check-fn schema:create-org-invitation))
(def ^:private check-create-organization-invitation-params
(sm/check-fn schema:create-organization-invitation))
(defn- allow-invitation-emails?
[member]
@ -116,24 +116,24 @@
(not= :none (:email-invites notifications))))
(defn- assert-email-can-be-invited
"Asserts that member is an org member when the org
"Asserts that member is an organization member when the organization
restricts who can be added to teams."
[member org-member-ids]
(when (some? org-member-ids)
(let [is-member? (and (some? member) (contains? org-member-ids (:id member)))]
[member organization-member-ids]
(when (some? organization-member-ids)
(let [is-member? (and (some? member) (contains? organization-member-ids (:id member)))]
(when-not is-member?
(ex/raise :type :validation
:code :email-not-org-member
:code :email-not-organization-member
:hint "The invited email is not a member of the organization")))))
(defn- create-invitation
[{:keys [::db/conn] :as cfg}
{:keys [team organization profile role email org-member-ids organization-member-ids] :as params}]
{:keys [team organization profile role email organization-member-ids all-organization-member-ids] :as params}]
(assert (db/connection-map? cfg)
"expected cfg with valid connection")
(if organization
(assert (check-create-org-invitation-params params))
(assert (check-create-organization-invitation-params params))
(assert (check-create-invitation-params params)))
(let [email (profile/clean-email email)
@ -145,11 +145,11 @@
:code :email-domain-is-not-allowed
:hint "email domain is in the blacklist"))
;; When nitrate is active and the team belongs to an org, check that
;; the email is already an org member unless the org explicitly allows adding anybody.
;; When nitrate is active and the team belongs to an organization, check that
;; the email is already an organization member unless the organization explicitly allows adding anybody.
(when (and (contains? cf/flags :nitrate)
(:organization team))
(assert-email-can-be-invited member org-member-ids))
(assert-email-can-be-invited member organization-member-ids))
;; When we have email verification disabled and invitation user is
@ -165,9 +165,9 @@
(get types.team/permissions-for-role role))]
(if organization
;; Insert the invited member to the org
;; Insert the invited member to the organization
(when (contains? cf/flags :nitrate)
(teams/initialize-user-in-nitrate-org cfg (:id member) (:id organization) email))
(teams/initialize-user-in-nitrate-organization cfg (:id member) (:id organization) email))
;; Insert the invited member to the team
(teams/add-profile-to-team! cfg params {::db/on-conflict-do-nothing? true}))
@ -190,7 +190,7 @@
(ct/in-future "876000h") ;; Organization invitations doesn't expire
(ct/in-future "168h")) ;; 7 days
invitation (db/exec-one! conn (if organization
[sql:upsert-org-invitation id
[sql:upsert-organization-invitation id
(:id organization)
(str/lower email)
(:id profile)
@ -233,7 +233,7 @@
(boolean
(and team-organization-id
member
(contains? organization-member-ids (:id member))))))
(contains? all-organization-member-ids (:id member))))))
itoken (create-invitation-token cfg tprops)
ptoken (create-profile-identity-token cfg profile-id)]
@ -255,7 +255,7 @@
(if organization
(when (contains? cf/flags :nitrate)
(eml/send! {::eml/conn conn
::eml/factory eml/invite-to-org
::eml/factory eml/invite-to-organization
:public-uri (cf/get :public-uri)
:to email
:invited-by (:fullname profile)
@ -275,7 +275,7 @@
itoken)))))
(defn create-org-invitation
(defn create-organization-invitation
[cfg {:keys [::rpc/profile-id] :as params}]
(let [profile (db/get-by-id cfg :profile profile-id)]
(create-invitation cfg
@ -345,21 +345,21 @@
- emails (set) + role (single role for all emails)
- invitations (vector of {:email :role} maps)"
[{:keys [::db/conn] :as cfg} {:keys [profile team role emails invitations] :as params}]
(let [;; Enrich team with org info once for all invitations when nitrate is active
(let [;; Enrich team with organization info once for all invitations when nitrate is active
team (if (contains? cf/flags :nitrate)
(nitrate/add-org-info-to-team cfg team {})
(nitrate/add-organization-info-to-team cfg team {})
team)
org (:organization team)
org-id (:id org)
restricted? (and org-id (not (nitrate-perms/allowed? :add-anybody-to-team {:org-perms org})))
organization-member-ids
(when org-id
(into #{} (nitrate/call cfg :get-org-members {:organization-id org-id})))
org-member-ids (when restricted? organization-member-ids)
organization (:organization team)
organization-id (:id organization)
restricted? (and organization-id (not (nitrate-perms/allowed? :add-anybody-to-team {:organization-perms organization})))
all-organization-member-ids
(when organization-id
(into #{} (nitrate/call cfg :get-organization-members {:organization-id organization-id})))
organization-member-ids (when restricted? all-organization-member-ids)
params (assoc params
:team team
:org-member-ids org-member-ids
:organization-member-ids organization-member-ids)
:organization-member-ids organization-member-ids
:all-organization-member-ids all-organization-member-ids)
;; Normalize input to a consistent format: [{:email :role}]
invitation-data (cond

View File

@ -101,6 +101,17 @@
;; --- Team Invitation
(def ^:private sql:get-organization-invitation
"SELECT *
FROM team_invitation
WHERE email_to = ?
AND org_id = ?")
(def ^:private sql:delete-organization-invitation
"DELETE FROM team_invitation
WHERE email_to = ?
AND org_id = ?")
(defn- accept-invitation
[{:keys [::db/conn] :as cfg}
{:keys [team-id organization-id role member-email] :as claims} invitation member]
@ -124,9 +135,9 @@
(get types.team/permissions-for-role role))
accepted-team-id (if organization-id
;; Insert the invited member to the org
;; Insert the invited member to the organization
(when (contains? cf/flags :nitrate)
(teams/initialize-user-in-nitrate-org cfg id-member organization-id member-email))
(teams/initialize-user-in-nitrate-organization cfg id-member organization-id member-email))
;; Insert the invited member to the team
(do (teams/add-profile-to-team! cfg params {::db/on-conflict-do-nothing? true})
team-id))]
@ -145,10 +156,11 @@
{:id id-member}))
;; Delete the invitation
(db/delete! conn :team-invitation
(cond-> {:email-to member-email}
team-id (assoc :team-id team-id)
organization-id (assoc :org-id organization-id)))
(if organization-id
(db/exec-one! conn [sql:delete-organization-invitation member-email organization-id])
(db/delete! conn :team-invitation
{:email-to member-email
:team-id team-id}))
;; Delete any request (only applicable for team invitations)
(when team-id
@ -184,16 +196,17 @@
:code :invalid-invitation-token
:hint "invitation token contains unexpected data"))
(let [invitation (db/get* conn :team-invitation
(cond-> {:email-to member-email}
team-id (assoc :team-id team-id)
organization-id (assoc :org-id organization-id)))
(let [invitation (if organization-id
(db/exec-one! conn [sql:get-organization-invitation member-email organization-id])
(db/get* conn :team-invitation
{:email-to member-email
:team-id team-id}))
profile (db/get* conn :profile
{:id profile-id}
{:columns [:id :email :default-team-id]})
registration-disabled? (not (contains? cf/flags :registration))
org-invitation? (and (contains? cf/flags :nitrate) organization-id)]
organization-invitation? (and (contains? cf/flags :nitrate) organization-id)]
(if profile
(do
@ -219,12 +232,12 @@
(when (contains? cf/flags :nitrate)
(cond
organization-id
(nitrate/call cfg :get-org-membership {:profile-id profile-id
:organization-id organization-id})
(nitrate/call cfg :get-organization-membership {:profile-id profile-id
:organization-id organization-id})
team-id
(nitrate/call cfg :get-org-membership-by-team {:profile-id profile-id
:team-id team-id})))
(nitrate/call cfg :get-organization-membership-by-team {:profile-id profile-id
:team-id team-id})))
organization-id-on-add
(when (and (:organization-id membership)
@ -246,19 +259,19 @@
organization-member-count-before
(when organization-id-on-add
(count
(nitrate/call cfg :get-org-members
(nitrate/call cfg :get-organization-members
{:organization-id organization-id-on-add})))]
(when (:is-member membership)
(when org-invitation?
(when organization-invitation?
(ex/raise :type :validation
:code :already-an-org-member
:code :already-an-organization-member
:team-id (:default-team-id membership)
:hint "the user is already a member of the organization")))
(when (and org-invitation? (not (:organization-id membership)))
(when (and organization-invitation? (not (:organization-id membership)))
(ex/raise :type :validation
:code :org-not-found
:code :organization-not-found
:team-id (:default-team-id profile)
:hint "the organization doesn't exist"))
@ -299,10 +312,10 @@
(audit/clean-props))))))
(cond-> (assoc claims :state :created)
;; when the invitation is to an org, instead of a team, add the
;; accepted-team-id as :org-team-id
;; when the invitation is to an organization, instead of a team, add the
;; accepted-team-id as :organization-team-id
(:organization-id claims)
(assoc :org-team-id accepted-team-id)
(assoc :organization-team-id accepted-team-id)
organization-id-on-add
(assoc :organization-invitation-audit
@ -320,7 +333,7 @@
;; If the user is not logged-in and the invitation has been canceled
;; we return a specific error code so the frontend can redirect to
;; login with an appropriate message instead of showing the error page.
;; This only applies to org invitations; team invitations keep the
;; This only applies to organization invitations; team invitations keep the
;; existing :invalid-token behavior.
(when (nil? invitation)
(ex/raise :type :validation

View File

@ -167,12 +167,12 @@
[:role ::sm/text]])
(sv/defmethod ::notify-user-added-to-organization
"Notify to Penpot that an user has joined an org from nitrate"
"Notify to Penpot that an user has joined an organization from nitrate"
{::doc/added "2.14"
::sm/params schema:notify-user-added-to-organization
::rpc/auth false}
[cfg {:keys [profile-id organization-id]}]
(db/tx-run! cfg teams/create-default-org-team profile-id organization-id))
(db/tx-run! cfg teams/create-default-organization-team profile-id organization-id))
;; ---- API: get-managed-profiles
@ -314,7 +314,7 @@ RETURNING id, deleted_at;")
nil)
(defn manage-deleted-organization-teams
"For a deleted organization, preserve org teams unchanged and only prefix or
"For a deleted organization, preserve organization teams unchanged and only prefix or
delete member Your Penpot teams depending on whether they still contain files."
[cfg {:keys [organization-id organization-name teams]}]
(let [all-team-ids (->> teams
@ -329,7 +329,7 @@ RETURNING id, deleted_at;")
distinct
(into []))]
(when (seq all-team-ids)
(let [org-prefix (str "[" (d/sanitize-string organization-name) "] ")]
(let [organization-prefix (str "[" (d/sanitize-string organization-name) "] ")]
(db/tx-run!
cfg
(fn [{:keys [::db/conn] :as cfg}]
@ -343,11 +343,11 @@ RETURNING id, deleted_at;")
teams-to-prefix (->> your-penpot-team-ids (filter teams-with-files) (into []))
teams-to-delete (->> your-penpot-team-ids (remove teams-with-files) (into []))]
;; Org teams move to the fallback org unchanged. Only imported
;; Your Penpot teams keep the org prefix when they still have files.
;; Organization teams move to the fallback organization unchanged. Only imported
;; Your Penpot teams keep the organization prefix when they still have files.
(when (seq teams-to-prefix)
(db/exec! conn [sql:prefix-teams-name-and-unset-default
org-prefix
organization-prefix
(db/create-array conn "uuid" teams-to-prefix)]))
;; Empty imported Your Penpot teams disappear entirely.
@ -358,16 +358,16 @@ RETURNING id, deleted_at;")
(sv/defmethod ::notify-organization-deletion
"For a deleted organization, preserve org teams and only prefix or delete
"For a deleted organization, preserve organization teams and only prefix or delete
imported Your Penpot teams before notifying connected users."
{::doc/added "2.15"
::sm/params schema:notify-organization-deletion
::rpc/auth false}
[cfg {:keys [organization-id]}]
(let [org-summary (nitrate/call cfg :get-org-summary {:organization-id organization-id})
teams (:teams org-summary)]
(manage-deleted-organization-teams cfg {:organization-name (:name org-summary)
:organization-id (:id org-summary)
(let [organization-summary (nitrate/call cfg :get-organization-summary {:organization-id organization-id})
teams (:teams organization-summary)]
(manage-deleted-organization-teams cfg {:organization-name (:name organization-summary)
:organization-id (:id organization-summary)
:teams teams})
nil))
@ -378,18 +378,18 @@ RETURNING id, deleted_at;")
[:profile-id ::sm/uuid]])
(sv/defmethod ::notify-user-organizations-deletion
"For a given user, find all owned organizations and apply the deleted-org
"For a given user, find all owned organizations and apply the deleted-organization
transfer rules to their imported Your Penpot teams."
{::doc/added "2.18"
::sm/params schema:notify-user-organizations-deletion
::nitrate/sso false}
[cfg {:keys [profile-id]}]
(let [owned-orgs (nitrate/call cfg :get-owned-orgs {:profile-id profile-id})]
(doseq [org owned-orgs]
(let [organization-name (:name org)
teams (:teams org)]
(let [owned-organizations (nitrate/call cfg :get-owned-organizations {:profile-id profile-id})]
(doseq [organization owned-organizations]
(let [organization-name (:name organization)
teams (:teams organization)]
(manage-deleted-organization-teams cfg {:organization-name organization-name
:organization-id (:id org)
:organization-id (:id organization)
:teams teams}))))
nil)
@ -496,7 +496,7 @@ RETURNING id, deleted_at;")
[:organization schema:organization-with-avatar]]
::nitrate/sso false}
[cfg params]
(db/tx-run! cfg ti/create-org-invitation params)
(db/tx-run! cfg ti/create-organization-invitation params)
nil)
@ -524,9 +524,9 @@ RETURNING id, deleted_at;")
::sm/result schema:get-organization-invitations-result
::nitrate/sso false}
[cfg {:keys [organization-id]}]
(let [team-ids (noh/get-org-team-ids cfg organization-id)]
(let [team-ids (noh/get-organization-team-ids cfg organization-id)]
(db/run! cfg (fn [{:keys [::db/conn]}]
(->> (noh/get-org-invitations conn organization-id team-ids)
(->> (noh/get-organization-invitations conn organization-id team-ids)
(mapv (fn [{:keys [photo-id] :as invitation}]
(cond-> (dissoc invitation :photo-id)
photo-id
@ -546,13 +546,13 @@ RETURNING id, deleted_at;")
[:email ::sm/email]])
(sv/defmethod ::delete-organization-invitations
"Delete all invitations for one email in an organization scope (org + org teams)."
"Delete all invitations for one email in an organization scope (organization + organization teams)."
{::doc/added "2.16"
::sm/params schema:delete-organization-invitations-params
::nitrate/sso false}
[cfg {:keys [organization-id email]}]
(let [clean-email (profile/clean-email email)
team-ids (noh/get-org-team-ids cfg organization-id)]
team-ids (noh/get-organization-team-ids cfg organization-id)]
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [ids-array (db/create-array conn "uuid" team-ids)]
(db/exec! conn [sql:delete-organization-invitations clean-email organization-id ids-array]))))
@ -571,14 +571,14 @@ RETURNING id, deleted_at;")
[:organization-id ::sm/uuid]])
(sv/defmethod ::delete-all-organization-invitations
"Delete every pending invitation associated with an organization (org-level + team-level).
"Delete every pending invitation associated with an organization (organization-level + team-level).
Called from Nitrate when an organization is about to be deleted, so users that click
their invitation token hit the existing invalid-token landing page."
{::doc/added "2.18"
::sm/params schema:delete-all-organization-invitations-params
::rpc/auth false}
[cfg {:keys [organization-id]}]
(let [team-ids (noh/get-org-team-ids cfg organization-id)]
(let [team-ids (noh/get-organization-team-ids cfg organization-id)]
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [ids-array (db/create-array conn "uuid" team-ids)]
(db/exec! conn [sql:delete-all-organization-invitations organization-id ids-array]))))
@ -633,16 +633,16 @@ RETURNING id, deleted_at;")
valid-teams-to-leave (into valid-teams-to-exit
(map add-reassign-to valid-teams-to-transfer))]
(cnit/leave-org cfg (assoc params
:id organization-id
:name organization-name
:teams-to-delete valid-teams-to-delete-ids
:teams-to-leave valid-teams-to-leave
:skip-validation true
:user-who-delete-member actor-profile-id
:deleted-by-role (when actor-profile-id
"organization-owner")))
(notifications/notify-user-org-change cfg profile-id organization-id organization-name "dashboard.user-no-longer-belong-org")
(cnit/leave-organization cfg (assoc params
:id organization-id
:name organization-name
:teams-to-delete valid-teams-to-delete-ids
:teams-to-leave valid-teams-to-leave
:skip-validation true
:user-who-delete-member actor-profile-id
:deleted-by-role (when actor-profile-id
"organization-owner")))
(notifications/notify-user-organization-change cfg profile-id organization-id organization-name "dashboard.user-no-longer-belong-organization")
nil))
;; API: get-remove-from-organization-summary
@ -673,11 +673,11 @@ RETURNING id, deleted_at;")
(when-not valid-default-team
(ex/raise :type :validation
:code :not-valid-teams))
(cnit/get-leave-org-summary cfg
default-team-id
valid-teams-to-delete-ids
(count valid-teams-to-transfer)
(count valid-teams-to-exit))))
(cnit/get-leave-organization-summary cfg
default-team-id
valid-teams-to-delete-ids
(count valid-teams-to-transfer)
(count valid-teams-to-exit))))
;; API: send-renewal-email
@ -720,7 +720,7 @@ RETURNING id, deleted_at;")
WHERE id = ANY(?)
AND deleted_at IS NULL")
(def ^:private sql:exists-non-member-org-team-invitations
(def ^:private sql:exists-non-member-organization-team-invitations
"SELECT EXISTS (
SELECT 1
FROM team_invitation
@ -728,13 +728,13 @@ RETURNING id, deleted_at;")
AND email_to <> ALL(?)
) AS non_member")
(def ^:private sql:delete-non-member-org-team-invitations
(def ^:private sql:delete-non-member-organization-team-invitations
"DELETE FROM team_invitation
WHERE team_id = ANY(?)
AND email_to <> ALL(?)
RETURNING email_to")
(def ^:private schema:org-team-invitations-for-non-members-params
(def ^:private schema:organization-team-invitations-for-non-members-params
[:map
[:team-ids [:vector ::sm/uuid]]
[:member-ids [:vector ::sm/uuid]]])
@ -742,8 +742,8 @@ RETURNING id, deleted_at;")
(def ^:private schema:exists-organization-team-invitations-for-non-members-result
[:map [:exists ::sm/boolean]])
(defn- org-team-invitations-for-non-members-arrays
"Member emails and PG arrays used by exists/delete org team invitation endpoints."
(defn- organization-team-invitations-for-non-members-arrays
"Member emails and PG arrays used by exists/delete organization team invitation endpoints."
[conn {:keys [team-ids member-ids]}]
(let [member-ids-array (db/create-array conn "uuid" member-ids)
member-emails (->> (db/exec! conn [sql:get-profile-emails-by-ids member-ids-array])
@ -752,11 +752,11 @@ RETURNING id, deleted_at;")
{:emails-array (db/create-array conn "text" (vec member-emails))
:teams-array (db/create-array conn "uuid" team-ids)}))
(defn- non-member-org-team-invitations-exist?
(defn- non-member-organization-team-invitations-exist?
[conn params]
(let [{:keys [emails-array teams-array]}
(org-team-invitations-for-non-members-arrays conn params)]
(-> (db/exec-one! conn [sql:exists-non-member-org-team-invitations
(organization-team-invitations-for-non-members-arrays conn params)]
(-> (db/exec-one! conn [sql:exists-non-member-organization-team-invitations
teams-array
emails-array])
:non-member)))
@ -764,24 +764,24 @@ RETURNING id, deleted_at;")
(sv/defmethod ::exists-organization-team-invitations-for-non-members
"Return if there are any team invitations for emails that are not organization members."
{::doc/added "2.18"
::sm/params schema:org-team-invitations-for-non-members-params
::sm/params schema:organization-team-invitations-for-non-members-params
::sm/result schema:exists-organization-team-invitations-for-non-members-result
::nitrate/sso false}
[cfg params]
(db/run! cfg (fn [{:keys [::db/conn]}]
{:exists (boolean (non-member-org-team-invitations-exist? conn params))})))
{:exists (boolean (non-member-organization-team-invitations-exist? conn params))})))
(sv/defmethod ::delete-organization-team-invitations-for-non-members
"Delete team invitations for emails that are not organization members."
{::doc/added "2.18"
::sm/params schema:org-team-invitations-for-non-members-params
::sm/params schema:organization-team-invitations-for-non-members-params
::db/transaction true
::nitrate/sso false}
[cfg params]
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [{:keys [emails-array teams-array]}
(org-team-invitations-for-non-members-arrays conn params)]
(db/exec! conn [sql:delete-non-member-org-team-invitations
(organization-team-invitations-for-non-members-arrays conn params)]
(db/exec! conn [sql:delete-non-member-organization-team-invitations
teams-array
emails-array])
nil))))
@ -917,8 +917,8 @@ RETURNING id, deleted_at;")
::sm/result schema:get-teams-detail-result
::nitrate/sso false}
[cfg {:keys [organization-id]}]
(let [org-summary (nitrate/call cfg :get-org-summary {:organization-id organization-id})
team-ids (into [] (comp d/xf:map-id (filter uuid?)) (:teams org-summary))]
(let [organization-summary (nitrate/call cfg :get-organization-summary {:organization-id organization-id})
team-ids (into [] (comp d/xf:map-id (filter uuid?)) (:teams organization-summary))]
(if (empty? team-ids)
[]
(db/run! cfg
@ -958,8 +958,8 @@ RETURNING id, deleted_at;")
::rpc/auth false}
[{:keys [::db/pool] :as cfg} {:keys [organization-id updated-props announce-activation]}]
(when updated-props
(rpc/invalidate-org-sso-cache-by-org! organization-id)
(session/clear-org-sso-sessions! pool organization-id))
(rpc/invalidate-organization-sso-cache-by-organization! organization-id)
(session/clear-organization-sso-sessions! pool organization-id))
(notifications/notify-organization-change-sso cfg organization-id)
(when announce-activation
(neh/send-organization-setup-sso-emails! cfg organization-id))

View File

@ -28,11 +28,11 @@
WHERE email = ANY(?)
AND deleted_at IS NULL")
(defn- org-sso-active?
(defn- organization-sso-active?
"Return whether SSO is enabled for the organization."
[cfg organization-id]
(when (contains? cf/flags :nitrate)
(true? (:active (nitrate/call cfg :get-org-sso {:organization-id organization-id})))))
(true? (:active (nitrate/call cfg :get-organization-sso {:organization-id organization-id})))))
(def ^:private xf:map-email (map :email))
@ -61,44 +61,44 @@
:to email
:organization-name organization-name})))
(defn- get-org-sso-notify-recipients
"Unique org members and pending org/team invitees for SSO activation emails."
[conn cfg organization-id org-summary]
(let [member-ids (nitrate/call cfg :get-org-members {:organization-id organization-id})
team-ids (neh/get-org-team-ids org-summary)
(defn- get-organization-sso-notify-recipients
"Unique organization members and pending organization/team invitees for SSO activation emails."
[conn cfg organization-id organization-summary]
(let [member-ids (nitrate/call cfg :get-organization-members {:organization-id organization-id})
team-ids (neh/get-organization-team-ids organization-summary)
member-emails (if (seq member-ids)
(let [ids-array (db/create-array conn "uuid" member-ids)]
(into #{} (map :email (db/exec! conn [sql:get-profile-emails-by-ids ids-array]))))
#{})
invite-emails (into #{} (map :email
(neh/get-org-invitations conn organization-id team-ids)))
(neh/get-organization-invitations conn organization-id team-ids)))
emails (into #{} (concat member-emails invite-emails))]
(recipients-by-emails conn emails)))
(defn- get-team-sso-notify-recipients
"Team members who are not in `org-member-ids`, plus pending team invitations."
[conn team-id org-member-ids]
"Team members who are not in `organization-member-ids`, plus pending team invitations."
[conn team-id organization-member-ids]
(let [team-members (->> (teams/get-team-members conn team-id)
(remove #(contains? org-member-ids (:id %))))
(remove #(contains? organization-member-ids (:id %))))
invitations (neh/get-team-invitation-emails conn team-id)]
(->> (sequence xf:map-email (concat team-members invitations))
(recipients-by-emails conn))))
(defn send-organization-setup-sso-emails!
"Notify all org members and pending org/team invitees that SSO is active."
"Notify all organization members and pending organization/team invitees that SSO is active."
[cfg organization-id]
(let [org-summary (nitrate/call cfg :get-org-summary {:organization-id organization-id})]
(let [organization-summary (nitrate/call cfg :get-organization-summary {:organization-id organization-id})]
(db/tx-run! cfg
(fn [{:keys [::db/conn]}]
(doseq [recipient (get-org-sso-notify-recipients conn cfg organization-id org-summary)]
(send-organization-setup-sso-email! conn (:name org-summary) recipient))))))
(doseq [recipient (get-organization-sso-notify-recipients conn cfg organization-id organization-summary)]
(send-organization-setup-sso-email! conn (:name organization-summary) recipient))))))
(defn send-organization-setup-sso-emails-for-team!
"Notify team members who are not in `org-member-ids-before` and pending team invitees."
[cfg organization-id team-id org-member-ids-before]
(when (org-sso-active? cfg organization-id)
(let [org-summary (nitrate/call cfg :get-org-summary {:organization-id organization-id})]
"Notify team members who are not in `organization-member-ids-before` and pending team invitees."
[cfg organization-id team-id organization-member-ids-before]
(when (organization-sso-active? cfg organization-id)
(let [organization-summary (nitrate/call cfg :get-organization-summary {:organization-id organization-id})]
(db/tx-run! cfg
(fn [{:keys [::db/conn]}]
(doseq [recipient (get-team-sso-notify-recipients conn team-id org-member-ids-before)]
(send-organization-setup-sso-email! conn (:name org-summary) recipient)))))))
(doseq [recipient (get-team-sso-notify-recipients conn team-id organization-member-ids-before)]
(send-organization-setup-sso-email! conn (:name organization-summary) recipient)))))))

View File

@ -10,7 +10,7 @@
[app.db :as db]
[app.nitrate :as nitrate]))
(def ^:private sql:get-org-invitations
(def ^:private sql:get-organization-invitations
"SELECT DISTINCT ON (email_to)
ti.id,
ti.org_id AS organization_id,
@ -35,24 +35,24 @@ LEFT JOIN profile AS p
AND ti.valid_until >= now()
ORDER BY ti.email_to, ti.valid_until DESC, ti.created_at DESC;")
(defn get-org-team-ids
(defn get-organization-team-ids
"Return team ids for an organization.
Accepts either `cfg` and `organization-id` (fetches the org summary from
Nitrate) or an already-resolved org summary map."
Accepts either `cfg` and `organization-id` (fetches the organization summary from
Nitrate) or an already-resolved organization summary map."
([cfg organization-id]
(get-org-team-ids (nitrate/call cfg :get-org-summary {:organization-id organization-id})))
([org-summary]
(->> (:teams org-summary)
(get-organization-team-ids (nitrate/call cfg :get-organization-summary {:organization-id organization-id})))
([organization-summary]
(->> (:teams organization-summary)
(map :id)
(filter uuid?)
(vec))))
(defn get-org-invitations
"Fetch valid org-level and team-level invitations for an organization."
(defn get-organization-invitations
"Fetch valid organization-level and team-level invitations for an organization."
[conn organization-id team-ids]
(let [ids-array (db/create-array conn "uuid" team-ids)]
(db/exec! conn [sql:get-org-invitations organization-id ids-array])))
(db/exec! conn [sql:get-organization-invitations organization-id ids-array])))
(defn get-team-invitation-emails
"Return distinct valid team invitation recipient emails."

View File

@ -16,17 +16,17 @@
;;TODO There is a bug on dashboard with teams notifications.
;;For now we send it to uuid/zero instead of team-id
:topic uuid/zero
:message {:type :team-org-change
:message {:type :team-organization-change
:team team
:notification notification})))
(defn notify-user-org-change
(defn notify-user-organization-change
[cfg profile-id organization-id organization-name notification]
(let [msgbus (::mbus/msgbus cfg)]
(mbus/pub! msgbus
:topic profile-id
:message {:type :user-org-change
:message {:type :user-organization-change
:topic profile-id
:organization-id organization-id
:organization-name organization-name

View File

@ -42,15 +42,15 @@
:token "test-token"
:organization organization})
(t/deftest invite-to-org-includes-sso-notice-when-active
(let [result (emails/render emails/invite-to-org
(t/deftest invite-to-organization-includes-sso-notice-when-active
(let [result (emails/render emails/invite-to-organization
(invite-email-params {:name "Acme Inc"
:sso-active true}))]
(t/is (str/includes? (email-text-body result) sso-notice-snippet))
(t/is (str/includes? (get-in result [:body "text/html"]) sso-notice-snippet))))
(t/deftest invite-to-org-omits-sso-notice-when-inactive
(let [result (emails/render emails/invite-to-org
(t/deftest invite-to-organization-omits-sso-notice-when-inactive
(let [result (emails/render emails/invite-to-organization
(invite-email-params {:name "Acme Inc"
:sso-active false}))]
(t/is (not (str/includes? (email-text-body result) sso-notice-snippet)))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns backend-tests.rpc-org-owner-permissions-test
(ns backend-tests.rpc-organization-owner-permissions-test
(:require
[app.common.uuid :as uuid]
[app.config :as cf]
@ -17,37 +17,37 @@
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
(defn- org-data
[org-id owner-id]
{:id org-id
(defn- organization-data
[organization-id owner-id]
{:id organization-id
:name "Acme"
:slug "acme"
:owner-id owner-id
:avatar-bg-url "http://example.com/avatar.png"
:permissions {}})
(defn- with-org-owner-access
[{:keys [org-owner-id org-id team-id]} f]
(defn- with-organization-owner-access
[{:keys [organization-owner-id organization-id team-id]} f]
(with-redefs [cf/flags (conj cf/flags :nitrate)
nitrate/organization-owner-of-team?
(fn [_cfg profile-id candidate-team-id]
(and (= org-owner-id profile-id)
(and (= organization-owner-id profile-id)
(= team-id candidate-team-id)))
nitrate/call
(fn [_cfg method params]
(case method
:get-owned-orgs
[{:id org-id
:get-owned-organizations
[{:id organization-id
:name "Acme"
:owner-id org-owner-id
:owner-id organization-owner-id
:teams [{:id team-id :is-your-penpot false}]}]
:get-team-org
:get-team-organization
(if (= team-id (:team-id params))
{:id team-id
:is-your-penpot false
:organization (org-data org-id org-owner-id)}
:organization (organization-data organization-id organization-owner-id)}
{:id (:team-id params)
:is-your-penpot false
:organization nil})))]
@ -67,41 +67,41 @@
(filter #(= profile-id (:topic %)))
(mapv :message)))
(t/deftest org-owner-access-disabled-without-nitrate-flag
(t/deftest organization-owner-access-disabled-without-nitrate-flag
(let [team-owner (th/create-profile* 1)
org-owner (th/create-profile* 2)
organization-owner (th/create-profile* 2)
target-team (th/create-team* 1 {:profile-id (:id team-owner)})]
(let [out (th/command! {::th/type :get-projects
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:team-id (:id target-team)})
error (:error out)]
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found)))))
(t/deftest non-member-org-owner-gets-viewer-access-to-org-team
(t/deftest non-member-organization-owner-gets-viewer-access-to-organization-team
(let [team-owner (th/create-profile* 1)
org-owner (th/create-profile* 2)
organization-owner (th/create-profile* 2)
target-team (th/create-team* 1 {:profile-id (:id team-owner)})
project (th/create-project* 1 {:profile-id (:id team-owner)
:team-id (:id target-team)})
file (th/create-file* 1 {:profile-id (:id team-owner)
:project-id (:id project)})
org-id (uuid/next)]
organization-id (uuid/next)]
(with-org-owner-access {:org-owner-id (:id org-owner)
:org-id org-id
:team-id (:id target-team)}
(with-organization-owner-access {:organization-owner-id (:id organization-owner)
:organization-id organization-id
:team-id (:id target-team)}
(fn []
;; The team is not listed for a non-member, even though the org
;; The team is not listed for a non-member, even though the organization
;; owner can access it directly.
(let [out (th/command! {::th/type :get-teams
::rpc/profile-id (:id org-owner)})]
::rpc/profile-id (:id organization-owner)})]
(t/is (nil? (:error out)))
(t/is (not-any? #(= (:id target-team) (:id %)) (:result out))))
(let [out (th/command! {::th/type :get-team
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:id (:id target-team)})
team (:result out)]
(t/is (nil? (:error out)))
@ -109,54 +109,54 @@
(t/is (false? (get-in team [:permissions :is-owner])))
(t/is (false? (get-in team [:permissions :is-admin])))
(t/is (false? (get-in team [:permissions :can-edit])))
(t/is (= org-id (get-in team [:organization :id])))
(t/is (= organization-id (get-in team [:organization :id])))
(t/is (= "Acme" (get-in team [:organization :name]))))
(let [out (th/command! {::th/type :get-team-members
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:team-id (:id target-team)})
members (:result out)]
(t/is (nil? (:error out)))
(t/is (some #(= (:id team-owner) (:id %)) members))
(t/is (not-any? #(= (:id org-owner) (:id %)) members)))
(t/is (not-any? #(= (:id organization-owner) (:id %)) members)))
(let [out (th/command! {::th/type :get-projects
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:team-id (:id target-team)})]
(t/is (nil? (:error out)))
(t/is (some #(= (:id project) (:id %)) (:result out))))
(let [out (th/command! {::th/type :get-file
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:id (:id file)})]
(t/is (nil? (:error out)))
(t/is (= (:id file) (get-in out [:result :id])))
(t/is (false? (get-in out [:result :permissions :can-edit]))))
(let [out (th/command! {::th/type :rename-project
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:id (:id project)
:name "Nope"})
error (:error out)]
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found)))))))
(t/deftest org-owner-member-keeps-team-role
(t/deftest organization-owner-member-keeps-team-role
(let [team-owner (th/create-profile* 1)
org-owner (th/create-profile* 2)
organization-owner (th/create-profile* 2)
target-team (th/create-team* 1 {:profile-id (:id team-owner)})
org-id (uuid/next)]
organization-id (uuid/next)]
(th/create-team-role* {:team-id (:id target-team)
:profile-id (:id org-owner)
:profile-id (:id organization-owner)
:role :editor})
(with-org-owner-access {:org-owner-id (:id org-owner)
:org-id org-id
:team-id (:id target-team)}
(with-organization-owner-access {:organization-owner-id (:id organization-owner)
:organization-id organization-id
:team-id (:id target-team)}
(fn []
(let [out (th/command! {::th/type :get-team
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:id (:id target-team)})
team (:result out)]
(t/is (nil? (:error out)))
@ -164,31 +164,31 @@
(t/is (false? (get-in team [:permissions :is-admin])))
(t/is (true? (get-in team [:permissions :can-edit]))))))))
(t/deftest removed-org-owner-is-degraded-to-viewer
(t/deftest removed-organization-owner-is-degraded-to-viewer
(let [team-owner (th/create-profile* 1)
org-owner (th/create-profile* 2)
organization-owner (th/create-profile* 2)
target-team (th/create-team* 1 {:profile-id (:id team-owner)})
org-id (uuid/next)
organization-id (uuid/next)
messages (atom [])]
(th/create-team-role* {:team-id (:id target-team)
:profile-id (:id org-owner)
:profile-id (:id organization-owner)
:role :editor})
(with-org-owner-access {:org-owner-id (:id org-owner)
:org-id org-id
:team-id (:id target-team)}
(with-organization-owner-access {:organization-owner-id (:id organization-owner)
:organization-id organization-id
:team-id (:id target-team)}
(fn []
(let [out (with-captured-messages messages
#(th/command! {::th/type :delete-team-member
::rpc/profile-id (:id team-owner)
:team-id (:id target-team)
:member-id (:id org-owner)}))]
:member-id (:id organization-owner)}))]
(t/is (nil? (:error out))))
;; The org owner keeps read-only access, so they are notified
;; The organization owner keeps read-only access, so they are notified
;; with a role change instead of being kicked out of the team.
(let [notified (messages-for messages (:id org-owner))]
(let [notified (messages-for messages (:id organization-owner))]
(t/is (= 1 (count notified)))
(t/is (= :team-role-change (:type (first notified))))
(t/is (= :viewer (:role (first notified))))
@ -196,7 +196,7 @@
(t/is (not-any? #(= :team-membership-change (:type %)) notified)))
(let [out (th/command! {::th/type :get-team
::rpc/profile-id (:id org-owner)
::rpc/profile-id (:id organization-owner)
:id (:id target-team)})
team (:result out)]
(t/is (nil? (:error out)))
@ -206,19 +206,19 @@
(t/deftest removed-regular-member-is-still-kicked-out
(let [team-owner (th/create-profile* 1)
org-owner (th/create-profile* 2)
organization-owner (th/create-profile* 2)
member (th/create-profile* 3)
target-team (th/create-team* 1 {:profile-id (:id team-owner)})
org-id (uuid/next)
organization-id (uuid/next)
messages (atom [])]
(th/create-team-role* {:team-id (:id target-team)
:profile-id (:id member)
:role :editor})
(with-org-owner-access {:org-owner-id (:id org-owner)
:org-id org-id
:team-id (:id target-team)}
(with-organization-owner-access {:organization-owner-id (:id organization-owner)
:organization-id organization-id
:team-id (:id target-team)}
(fn []
(let [out (with-captured-messages messages
#(th/command! {::th/type :delete-team-member

View File

@ -110,24 +110,24 @@
audit-mock {:target 'app.loggers.audit/submit :return nil}]
(let [owner (th/create-profile* 101 {:is-active true})
invitee (th/create-profile* 102 {:is-active true})
org-team (th/create-team* 101 {:profile-id (:id owner)})
organization-team (th/create-team* 101 {:profile-id (:id owner)})
plain-team (th/create-team* 102 {:profile-id (:id owner)})
org-id (uuid/random)
org {:id org-id
:name "Acme"
:slug "acme"
:owner-id (:id owner)
:avatar-bg-url "https://example.com/avatar.svg"
:permissions {:new-team-members "anyone"}}
organization-id (uuid/random)
organization {:id organization-id
:name "Acme"
:slug "acme"
:owner-id (:id owner)
:avatar-bg-url "https://example.com/avatar.svg"
:permissions {:new-team-members "anyone"}}
nitrate-call
(fn [_cfg method params]
(case method
:get-team-org
(if (= (:team-id params) (:id org-team))
{:organization org :is-your-penpot false}
:get-team-organization
(if (= (:team-id params) (:id organization-team))
{:organization organization :is-your-penpot false}
{:organization nil :is-your-penpot false})
:get-org-members
:get-organization-members
[(:id invitee)]
nil))
@ -139,21 +139,21 @@
:emails [email]}))]
(with-redefs [cf/flags (conj cf/flags :nitrate :email-verification)
nitrate/call nitrate-call]
(t/is (th/success? (invite! org-team (:email invitee))))
(t/is (th/success? (invite! org-team (:email invitee))))
(t/is (th/success? (invite! organization-team (:email invitee))))
(t/is (th/success? (invite! organization-team (:email invitee))))
(t/is (th/success? (invite! plain-team "external@example.com"))))
(let [events (mapv second (:call-args-list @audit-mock))
create-org (first (filter #(and (= "create-team-invitation" (:name %))
(= (:email invitee)
(get-in % [:props :member-email])))
events))
update-org (first (filter #(= "update-team-invitation" (:name %)) events))
create-organization (first (filter #(and (= "create-team-invitation" (:name %))
(= (:email invitee)
(get-in % [:props :member-email])))
events))
update-organization (first (filter #(= "update-team-invitation" (:name %)) events))
create-plain (first (filter #(and (= "create-team-invitation" (:name %))
(= "external@example.com"
(get-in % [:props :member-email])))
events))]
(doseq [event [create-org update-org]]
(doseq [event [create-organization update-organization]]
(t/is (true? (get-in event [:props :team-belongs-to-organization])))
(t/is (true? (get-in event [:props :adds-invitee-to-organization])))
(t/is (true? (get-in event [:props :invitee-already-organization-member]))))
@ -486,11 +486,11 @@
nitrate/call
(fn [_cfg method _params]
(case method
:get-org-membership {:organization-id organization-id
:is-member false}
:get-org-members [(:id inviter) (uuid/random) (uuid/random)]
:get-organization-membership {:organization-id organization-id
:is-member false}
:get-organization-members [(:id inviter) (uuid/random) (uuid/random)]
nil))
teams/initialize-user-in-nitrate-org
teams/initialize-user-in-nitrate-organization
(fn [& _] default-team-id)]
(let [out (verify! direct-token)]
(t/is (th/success? out))
@ -531,10 +531,10 @@
nitrate/call
(fn [_cfg method _params]
(case method
:get-org-membership-by-team {:organization-id organization-id
:is-member false}
:get-org-members (into [(:id inviter)]
(repeatedly 4 uuid/random))
:get-organization-membership-by-team {:organization-id organization-id
:is-member false}
:get-organization-members (into [(:id inviter)]
(repeatedly 4 uuid/random))
nil))
teams/add-profile-to-team! (fn [& _] nil)]
(let [out (verify! team-token)]
@ -574,9 +574,9 @@
nitrate/call
(fn [_cfg method _params]
(case method
:get-org-membership-by-team {:organization-id organization-id
:is-member true}
:get-org-members (throw (ex-info "unexpected member count" {}))
:get-organization-membership-by-team {:organization-id organization-id
:is-member true}
:get-organization-members (throw (ex-info "unexpected member count" {}))
nil))
teams/add-profile-to-team! (fn [& _] nil)]
(let [out (verify! team-token)]

View File

@ -14,29 +14,29 @@
:new-team-members "anyone"})
(defn- can-create-team?
[{:keys [is-org-owner? permission-value]}]
(or is-org-owner?
[{:keys [is-organization-owner? permission-value]}]
(or is-organization-owner?
(= permission-value "any")))
(defn- can-delete-team?
[{:keys [is-org-owner? permission-value team-perms]}]
[{:keys [is-organization-owner? permission-value team-perms]}]
(cond
;; Org owners can always delete teams inside their organizations.
is-org-owner?
;; Organization owners can always delete teams inside their organizations.
is-organization-owner?
true
(= permission-value "onlyOwners")
(boolean (:is-owner team-perms))
:else false))
(defn- can-move-team?
[{:keys [permission-value target-org-same-owner?]}]
[{:keys [permission-value target-organization-same-owner?]}]
(cond
(= permission-value "never")
false
(= permission-value "always")
true
(= permission-value "myOrganizations")
(true? target-org-same-owner?)
(true? target-organization-same-owner?)
:else false))
(defn- can-invite-to-team?
@ -67,36 +67,36 @@
:add-anybody-to-team {:permission-key :new-team-members
:check-fn can-add-anybody-to-team?}})
(defn- normalize-org-permissions
[org-perms]
(merge defaults (or (:permissions org-perms) {})))
(defn- normalize-organization-permissions
[organization-perms]
(merge defaults (or (:permissions organization-perms) {})))
(defn- owner?
[org-perms profile-id]
(= profile-id (:owner-id org-perms)))
[organization-perms profile-id]
(= profile-id (:owner-id organization-perms)))
(defn allowed?
"Returns true only for explicitly allowed actions (fail-closed)."
[action {:keys [org-perms profile-id team-perms target-org-same-owner?]}]
[action {:keys [organization-perms profile-id team-perms target-organization-same-owner?]}]
(let [{:keys [permission-key check-fn] :as rule}
(get action-rules action)
permissions (normalize-org-permissions org-perms)
is-org-owner? (owner? org-perms profile-id)
permissions (normalize-organization-permissions organization-perms)
is-organization-owner? (owner? organization-perms profile-id)
permission-value (get permissions permission-key)]
(cond
(nil? rule) false
:else (boolean (check-fn {:is-org-owner? is-org-owner?
:else (boolean (check-fn {:is-organization-owner? is-organization-owner?
:permission-value permission-value
:team-perms team-perms
:target-org-same-owner? target-org-same-owner?})))))
:target-organization-same-owner? target-organization-same-owner?})))))
(defn can-send-invitations?
[{:keys [nitrate-enabled? organization profile-id team-permissions]}]
(let [in-org? (and nitrate-enabled? organization)]
(if in-org?
(let [in-organization? (and nitrate-enabled? organization)]
(if in-organization?
(allowed? :send-invitations
{:org-perms {:owner-id (:owner-id organization)
:permissions (:permissions organization)}
{:organization-perms {:owner-id (:owner-id organization)
:permissions (:permissions organization)}
:profile-id profile-id
:team-perms team-permissions})
(or (boolean (:is-owner team-permissions))

View File

@ -9,7 +9,7 @@
[app.common.types.nitrate-permissions :as nitrate-perms]
[clojure.test :as t]))
(def org-perms
(def organization-perms
{:owner-id :owner
:permissions {:create-teams "any"
:delete-teams "onlyOwners"
@ -17,162 +17,162 @@
(t/deftest unknown-action-is-denied
(t/is (false? (nitrate-perms/allowed? :unknown
{:org-perms org-perms
{:organization-perms organization-perms
:profile-id :member
:team-perms {:is-admin true}}))))
(t/deftest org-owner-is-allowed-for-create-and-delete
(t/deftest organization-owner-is-allowed-for-create-and-delete
(t/is (true? (nitrate-perms/allowed? :create-team
{:org-perms org-perms
{:organization-perms organization-perms
:profile-id :owner
:team-perms {:is-admin false}})))
(t/is (true? (nitrate-perms/allowed? :delete-team
{:org-perms org-perms
{:organization-perms organization-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
{:organization-perms organization-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 "onlyOwners"})
{:organization-perms (assoc organization-perms :permissions {:create-teams "none"
:delete-teams "onlyOwners"})
:profile-id :member
:team-perms {:is-admin false}}))))
(t/deftest delete-team-onlyowners-allows-only-team-owners
(t/is (true? (nitrate-perms/allowed? :delete-team
{:org-perms org-perms
{:organization-perms organization-perms
:profile-id :member
:team-perms {:is-owner true :is-admin true}})))
(t/is (false? (nitrate-perms/allowed? :delete-team
{:org-perms org-perms
{:organization-perms organization-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 "invalid-value"})
{:organization-perms (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "invalid-value"})
:profile-id :member
:team-perms {:is-admin true}}))))
(t/deftest delete-team-onlyme-still-allows-org-owner
(let [only-me-org (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyMe"})]
(t/deftest delete-team-onlyme-still-allows-organization-owner
(let [only-me-organization (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyMe"})]
(t/is (true? (nitrate-perms/allowed? :delete-team
{:org-perms only-me-org
{:organization-perms only-me-organization
:profile-id :owner
:team-perms {:is-owner false :is-admin false}})))
(t/is (false? (nitrate-perms/allowed? :delete-team
{:org-perms only-me-org
{:organization-perms only-me-organization
:profile-id :member
:team-perms {:is-owner true :is-admin true}})))))
(t/deftest move-team-always-allows-any-org-owner-or-all-users
(let [always-org (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:move-teams "always"})]
;; Org owner should always be allowed
(t/deftest move-team-always-allows-any-organization-owner-or-all-users
(let [always-organization (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:move-teams "always"})]
;; Organization owner should always be allowed
(t/is (true? (nitrate-perms/allowed? :move-team
{:org-perms always-org
{:organization-perms always-organization
:profile-id :owner
:team-perms {}})))
;; Regular member should be allowed when move-teams is "always"
(t/is (true? (nitrate-perms/allowed? :move-team
{:org-perms always-org
{:organization-perms always-organization
:profile-id :member
:team-perms {}})))))
(t/deftest move-team-myorganizations-allows-only-within-same-owner
(let [my-orgs (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:move-teams "myOrganizations"})]
;; Org owner must also stay within same-owner organizations
(let [my-organizations (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:move-teams "myOrganizations"})]
;; Organization owner must also stay within same-owner organizations
(t/is (false? (nitrate-perms/allowed? :move-team
{:org-perms my-orgs
{:organization-perms my-organizations
:profile-id :owner
:team-perms {}
:target-org-same-owner? false})))
:target-organization-same-owner? false})))
(t/is (true? (nitrate-perms/allowed? :move-team
{:org-perms my-orgs
{:organization-perms my-organizations
:profile-id :owner
:team-perms {}
:target-org-same-owner? true})))
:target-organization-same-owner? true})))
;; Regular member should be allowed only if target has same owner
(t/is (true? (nitrate-perms/allowed? :move-team
{:org-perms my-orgs
{:organization-perms my-organizations
:profile-id :member
:team-perms {}
:target-org-same-owner? true})))
:target-organization-same-owner? true})))
(t/is (false? (nitrate-perms/allowed? :move-team
{:org-perms my-orgs
{:organization-perms my-organizations
:profile-id :member
:team-perms {}
:target-org-same-owner? false})))))
:target-organization-same-owner? false})))))
(t/deftest move-team-never-denies-all
(let [never-org (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:move-teams "never"})]
;; Even org owner should be denied
(let [never-organization (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:move-teams "never"})]
;; Even organization owner should be denied
(t/is (false? (nitrate-perms/allowed? :move-team
{:org-perms never-org
{:organization-perms never-organization
:profile-id :owner
:team-perms {}})))
;; Regular member should be denied
(t/is (false? (nitrate-perms/allowed? :move-team
{:org-perms never-org
{:organization-perms never-organization
:profile-id :member
:team-perms {}})))))
(t/deftest move-team-defaults-to-always
(let [default-org (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"})]
(let [default-organization (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"})]
;; Should default to "always" when not specified
(t/is (true? (nitrate-perms/allowed? :move-team
{:org-perms default-org
{:organization-perms default-organization
:profile-id :member
:team-perms {}})))))
(t/deftest send-invitations-defaults-to-owners-and-admins
(let [default-org (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"})]
(let [default-organization (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"})]
(t/is (true? (nitrate-perms/allowed? :send-invitations
{:org-perms default-org
{:organization-perms default-organization
:profile-id :owner
:team-perms {:is-owner true :is-admin false}})))
(t/is (true? (nitrate-perms/allowed? :send-invitations
{:org-perms default-org
{:organization-perms default-organization
:profile-id :member
:team-perms {:is-owner false :is-admin true}})))
(t/is (false? (nitrate-perms/allowed? :send-invitations
{:org-perms default-org
{:organization-perms default-organization
:profile-id :member
:team-perms {:is-owner false :is-admin false}})))))
(t/deftest send-invitations-owners-allows-only-team-owners
(let [only-owners-org (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:send-invitations "owners"})]
(let [only-owners-organization (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:send-invitations "owners"})]
(t/is (true? (nitrate-perms/allowed? :send-invitations
{:org-perms only-owners-org
{:organization-perms only-owners-organization
:profile-id :member
:team-perms {:is-owner true :is-admin true}})))
(t/is (false? (nitrate-perms/allowed? :send-invitations
{:org-perms only-owners-org
{:organization-perms only-owners-organization
:profile-id :owner
:team-perms {:is-owner false :is-admin false}})))
(t/is (false? (nitrate-perms/allowed? :send-invitations
{:org-perms only-owners-org
{:organization-perms only-owners-organization
:profile-id :member
:team-perms {:is-owner false :is-admin true}})))))
(t/deftest send-invitations-invalid-value-is-denied
(let [invalid-org (assoc org-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:send-invitations "invalid-value"})]
(let [invalid-organization (assoc organization-perms :permissions {:create-teams "any"
:delete-teams "onlyOwners"
:send-invitations "invalid-value"})]
(t/is (false? (nitrate-perms/allowed? :send-invitations
{:org-perms invalid-org
{:organization-perms invalid-organization
:profile-id :member
:team-perms {:is-owner true :is-admin true}})))))

View File

@ -245,11 +245,11 @@
(rx/of (rt/nav-raw :uri (str redirect-uri)))
(rx/empty))))))))
(defn handle-change-team-org
"Handle :team-org-change websocket messages on dashboard and workspace.
Updates local team org data and redirects to SSO when required."
(defn handle-change-team-organization
"Handle :team-organization-change websocket messages on dashboard and workspace.
Updates local team organization data and redirects to SSO when required."
[{:keys [team notification]}]
(ptk/reify ::handle-change-team-org
(ptk/reify ::handle-change-team-organization
ptk/WatchEvent
(watch [_ state _]
(let [current-team-id (:current-team-id state)
@ -279,7 +279,7 @@
(defn handle-organization-change-sso
"Handle :organization-change-sso websocket messages on dashboard and workspace.
Redirects to the org SSO login when the current team belongs to that org."
Redirects to the organization SSO login when the current team belongs to that organization."
[{:keys [organization-id]}]
(ptk/reify ::handle-organization-change-sso
ptk/WatchEvent
@ -287,8 +287,8 @@
(when (contains? cf/flags :nitrate)
(let [team-id (:current-team-id state)
team (dm/get-in state [:teams team-id])
org-id (dm/get-in team [:organization :id])]
(when (= organization-id org-id)
team-organization-id (dm/get-in team [:organization :id])]
(when (= organization-id team-organization-id)
(check-team-sso team-id)))))))

View File

@ -685,9 +685,9 @@
(rx/of (dcm/change-team-role params)
(modal/hide)))))
(defn- handle-user-org-change
(defn- handle-user-organization-change
[{:keys [organization-id organization-name notification]}]
(ptk/reify ::handle-user-org-change
(ptk/reify ::handle-user-organization-change
ptk/WatchEvent
(watch [_ state _]
(when (and notification (contains? cf/flags :nitrate))
@ -698,7 +698,7 @@
:level :info
:timeout nil})
(dtm/fetch-teams)
;; When the user is currently on a team of the org
;; When the user is currently on a team of the organization
(when (= organization-id (dm/get-in team [:organization :id]))
(dcm/go-to-dashboard-recent {:team-id :default}))))))))
@ -711,22 +711,22 @@
(when (contains? cf/flags :nitrate)
(let [team-id (:current-team-id state)
current-team (dm/get-in state [:teams team-id])
current-org-id (dm/get-in current-team [:organization :id])
current-organization-id (dm/get-in current-team [:organization :id])
teams-set (set teams)
notify? (contains? teams-set team-id)
fetch? (some (:teams state) teams)
go-to-default? (or (some #{team-id} deleted-teams)
(= organization-id current-org-id))]
(= organization-id current-organization-id))]
(rx/concat
(when go-to-default? ;; If the user is currently on one of the deleted teams
(rx/of (dcm/go-to-dashboard-recent {:team-id :default})))
(when notify? ;; If the user is currently on one of the org teams
(rx/of (ntf/show {:content (tr "dashboard.org-deleted" organization-name)
(when notify? ;; If the user is currently on one of the organization teams
(rx/of (ntf/show {:content (tr "dashboard.organization-deleted" organization-name)
:type :toast
:level :info
:timeout nil})))
(when fetch? ;; If the user belonged to the org
(when fetch? ;; If the user belonged to the organization
(rx/of (dtm/fetch-teams)))))))))
(defn- process-message
@ -735,8 +735,8 @@
:notification (dcm/handle-notification msg)
:team-role-change (handle-change-team-role msg)
:team-membership-change (dcm/team-membership-change msg)
:team-org-change (dcm/handle-change-team-org msg)
:user-org-change (handle-user-org-change msg)
:team-organization-change (dcm/handle-change-team-organization msg)
:user-organization-change (handle-user-organization-change msg)
:organization-deleted (handle-organization-deleted msg)
:organization-change-sso (dcm/handle-organization-change-sso msg)
nil))

View File

@ -212,7 +212,7 @@
(defn lookup-team
"The team identified by `team-id`, looked up first in the membership
`:teams` map and falling back to the directly-opened `:current-team`.
The fallback covers org-owner access to teams the profile is not a
The fallback covers organization-owner access to teams the profile is not a
member of, which are kept out of `:teams` so they don't leak into the
teams listing."
([state]

View File

@ -161,7 +161,7 @@
(contains? #{"active" "past_due" "trialing"}
(dm/get-in profile [:subscription :status]))))
(defn leave-org
(defn leave-organization
[{:keys [id
name
default-team-id
@ -171,7 +171,7 @@
organization-member-count-before
on-error]}]
(ptk/reify ::leave-org
(ptk/reify ::leave-organization
ptk/WatchEvent
(watch [_ state _]
(let [profile-id (dm/get-in state [:profile :id])
@ -191,29 +191,29 @@
:subscription-status subscription-status})]
(rx/concat
(rx/of audit-event)
(->> (rp/cmd! ::leave-org {:id id
:name name
:default-team-id default-team-id
:teams-to-delete teams-to-delete
:teams-to-leave teams-to-leave})
(->> (rp/cmd! ::leave-organization {:id id
:name name
:default-team-id default-team-id
:teams-to-delete teams-to-delete
:teams-to-leave teams-to-leave})
(rx/mapcat
(fn [_]
(rx/of
(dt/fetch-teams)
(dcm/go-to-dashboard-recent :team-id profile-team-id)
(modal/hide)
(ntf/show {:content (tr "dasboard.leave-org.toast" name)
(ntf/show {:content (tr "dashboard.leave-organization.toast" name)
:type :toast
:level :success}))))
(rx/catch on-error)))))))
(defn show-leave-org-modal
(defn show-leave-organization-modal
[{:keys [organization profile default-team-id leave-fn teams-to-transfer on-error]}]
(ptk/reify ::show-leave-org-modal
(ptk/reify ::show-leave-organization-modal
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/cmd! ::get-leave-org-summary {:id (:id organization)
:default-team-id default-team-id})
(->> (rp/cmd! ::get-leave-organization-summary {:id (:id organization)
:default-team-id default-team-id})
(rx/mapcat
(fn [summary]
(let [num-teams-to-delete (:teams-to-delete summary)
@ -231,7 +231,7 @@
(pos? num-teams-to-transfer)
(rx/of
(modal/show
{:type :leave-and-reassign-org
{:type :leave-and-reassign-organization
:profile profile
:teams-to-transfer teams-to-transfer
:num-teams-to-delete num-teams-to-delete
@ -242,28 +242,28 @@
(pos? num-teams-to-detach))
(rx/of (modal/show
{:type :confirm
:title (tr "modals.before-leave-org.title" (:name organization))
:message (tr "modals.before-leave-org.message")
:accept-label (tr "modals.leave-org-confirm.accept")
:title (tr "modals.before-leave-organization.title" (:name organization))
:message (tr "modals.before-leave-organization.message")
:accept-label (tr "modals.leave-organization-confirm.accept")
:on-accept leave-fn
:error-msg (tr "modals.before-leave-org.warning")}))
:error-msg (tr "modals.before-leave-organization.warning")}))
:else
(rx/of (modal/show
{:type :confirm
:title (tr "modals.leave-org-confirm.title" (:name organization))
:message (tr "modals.leave-org-confirm.message")
:accept-label (tr "modals.leave-org-confirm.accept")
:title (tr "modals.leave-organization-confirm.title" (:name organization))
:message (tr "modals.leave-organization-confirm.message")
:accept-label (tr "modals.leave-organization-confirm.accept")
:on-accept leave-fn}))))))
(rx/catch on-error)))))
(defn remove-team-from-org
(defn remove-team-from-organization
[{:keys [team-id organization-id organization-name] :as params}]
(ptk/reify ::remove-team-from-org
(ptk/reify ::remove-team-from-organization
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/cmd! ::remove-team-from-org {:team-id team-id :organization-id organization-id :organization-name organization-name})
(->> (rp/cmd! ::remove-team-from-organization {:team-id team-id :organization-id organization-id :organization-name organization-name})
(rx/mapcat
(fn [_]
(rx/of (dt/fetch-teams)
@ -272,40 +272,40 @@
(fn [cause]
(let [code (-> cause ex-data :code)]
(if (= code :not-allowed)
(rx/of (modal/show :no-permission-modal {:type :no-orgs-change}))
(rx/of (modal/show :no-permission-modal {:type :no-organizations-change}))
(rx/throw cause)))))))))
(defn show-remove-team-from-org-modal
"Fetches fresh team/org data, then shows the remove-from-org confirmation
(defn show-remove-team-from-organization-modal
"Fetches fresh team/organization data, then shows the remove-from-organization confirmation
modal or the no-permission modal if the move-team permission blocks it."
[{:keys [team-id]}]
(ptk/reify ::show-remove-team-from-org-modal
(ptk/reify ::show-remove-team-from-organization-modal
ptk/WatchEvent
(watch [_ state _]
(let [profile-id (dm/get-in state [:profile :id])]
(dt/with-refreshed-team team-id
(fn [team]
(let [source-org (:organization team)
(let [source-organization (:organization team)
can-move? (nitrate-perms/allowed?
:move-team
{:org-perms {:owner-id (:owner-id source-org)
:permissions (:permissions source-org)}
{:organization-perms {:owner-id (:owner-id source-organization)
:permissions (:permissions source-organization)}
:profile-id profile-id
:team-perms (:permissions team)
:target-org-same-owner? false})]
:target-organization-same-owner? false})]
(rx/of (if can-move?
(modal/show
{:type :confirm
:title (tr "modals.remove-team-org.title")
:message (tr "modals.remove-team-org.text" (:name team) (:name source-org))
:hint (tr "modals.remove-team-org.info")
:title (tr "modals.remove-team-organization.title")
:message (tr "modals.remove-team-organization.text" (:name team) (:name source-organization))
:hint (tr "modals.remove-team-organization.info")
:hint-level :default
:accept-label (tr "modals.remove-team-org.accept")
:on-accept #(st/emit! (remove-team-from-org {:team-id team-id
:organization-id (:id source-org)
:organization-name (:name source-org)}))
:accept-label (tr "modals.remove-team-organization.accept")
:on-accept #(st/emit! (remove-team-from-organization {:team-id team-id
:organization-id (:id source-organization)
:organization-name (:name source-organization)}))
:accept-style :danger})
(modal/show :no-permission-modal {:type :no-orgs-change}))))))))))
(modal/show :no-permission-modal {:type :no-organizations-change}))))))))))
(defn add-team-to-organization
@ -359,24 +359,24 @@
(rx/empty)))))))))))
(defn- fetch-orgs-allowed
"Returns an rx observable of an `orgs-allowed` map (org-id -> boolean).
Orgs where :add-anybody-to-team is permitted are pre-approved;
the rest are verified via :all-team-members-in-orgs."
[team-id orgs]
(let [add-anybody-orgs (filterv #(nitrate-perms/allowed? :add-anybody-to-team {:org-perms %}) orgs)
orgs-to-check (filterv #(not (nitrate-perms/allowed? :add-anybody-to-team {:org-perms %})) orgs)
org-ids-to-check (mapv :id orgs-to-check)]
(if (empty? org-ids-to-check)
(rx/of (into {} (map (fn [org] [(:id org) true])) orgs))
(->> (rp/cmd! :all-team-members-in-orgs {:team-id team-id :organization-ids org-ids-to-check})
(rx/map (fn [checked-orgs]
(merge (into {} (map (fn [org] [(:id org) true])) add-anybody-orgs)
checked-orgs)))))))
(defn- fetch-organizations-allowed
"Returns an rx observable of an `organizations-allowed` map (organization-id -> boolean).
Organizations where :add-anybody-to-team is permitted are pre-approved;
the rest are verified via :all-team-members-in-organizations."
[team-id organizations]
(let [add-anybody-organizations (filterv #(nitrate-perms/allowed? :add-anybody-to-team {:organization-perms %}) organizations)
organizations-to-check (filterv #(not (nitrate-perms/allowed? :add-anybody-to-team {:organization-perms %})) organizations)
organization-ids-to-check (mapv :id organizations-to-check)]
(if (empty? organization-ids-to-check)
(rx/of (into {} (map (fn [organization] [(:id organization) true])) organizations))
(->> (rp/cmd! :all-team-members-in-organizations {:team-id team-id :organization-ids organization-ids-to-check})
(rx/map (fn [checked-organizations]
(merge (into {} (map (fn [organization] [(:id organization) true])) add-anybody-organizations)
checked-organizations)))))))
(defn show-add-team-to-organization-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
"Fetches fresh team/organization data, then shows the add-to-organization modal
restricted to organizations where the user has permission, or the no-permission
modal if none qualify."
[{:keys [team-id]}]
(ptk/reify ::show-add-team-to-organization-modal
@ -386,114 +386,114 @@
(->> (rp/cmd! :get-teams)
(rx/mapcat
(fn [teams]
(let [all-orgs (map dt/team->organization
(filter #(and (:is-default %) (:organization %)) teams))
orgs (filter (fn [org]
(let [perm (dm/get-in org [:permissions :create-teams])
is-own? (= profile-id (:owner-id org))]
(or (= perm "any") is-own?))) all-orgs)
(let [all-organizations (map dt/team->organization
(filter #(and (:is-default %) (:organization %)) teams))
organizations (filter (fn [organization]
(let [perm (dm/get-in organization [:permissions :create-teams])
is-own? (= profile-id (:owner-id organization))]
(or (= perm "any") is-own?))) all-organizations)
team (first (filter #(= (:id %) team-id) teams))
on-confirm (fn [organization-id]
(st/emit! (add-team-to-organization {:team-id team-id
:organization-id organization-id})))
show-select-modal
(fn [orgs-allowed]
(let [has-filtered? (< (count orgs) (count all-orgs))
(fn [organizations-allowed]
(let [has-filtered? (< (count organizations) (count all-organizations))
extra-props (when has-filtered?
{:info-message-key "dashboard.select-org-modal.permission-info"})]
{:info-message-key "dashboard.select-organization-modal.permission-info"})]
(modal/show :select-organization-modal
(merge {:organizations orgs
:orgs-allowed orgs-allowed
(merge {:organizations organizations
:organizations-allowed organizations-allowed
:current-organization-id (dm/get-in team [:organization :id])
:on-confirm on-confirm
:team-id team-id
: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"
:title-key "dashboard.select-organization-modal.title"
:choose-key "dashboard.select-organization-modal.choose"
:placeholder-key "dashboard.select-organization-modal.select"
:accept-key "dashboard.select-organization-modal.accept"
:cancel-key "labels.cancel"}
extra-props))))]
(if (empty? orgs)
(if (empty? organizations)
(rx/of (dt/teams-fetched teams)
(modal/show :no-permission-modal {:type :no-orgs-create}))
(->> (fetch-orgs-allowed team-id orgs)
(modal/show :no-permission-modal {:type :no-organizations-create}))
(->> (fetch-organizations-allowed team-id organizations)
(rx/mapcat
(fn [orgs-allowed]
(let [valid-orgs (filterv #(true? (get orgs-allowed (:id %))) orgs)]
(fn [organizations-allowed]
(let [valid-organizations (filterv #(true? (get organizations-allowed (:id %))) organizations)]
(rx/of
(dt/teams-fetched teams)
(if (empty? valid-orgs)
(if (empty? valid-organizations)
(modal/show
{:type :alert
:hide-actions? true
:message (tr "dashboard.team-organization.add.no-valid-orgs")
:title (tr "dashboard.select-org-modal.title")})
(show-select-modal orgs-allowed))))))))))))))))
:message (tr "dashboard.team-organization.add.no-valid-organizations")
:title (tr "dashboard.select-organization-modal.title")})
(show-select-modal organizations-allowed))))))))))))))))
(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
(defn show-change-team-organization-modal
"Fetches fresh team/organization data, then shows the change-organization modal
restricted to organizations where the user has permission, or the no-permission
modal if none qualify."
[{:keys [team-id]}]
(ptk/reify ::show-change-team-org-modal
(ptk/reify ::show-change-team-organization-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 %)) teams))
(let [all-organizations (map dt/team->organization
(filter #(and (:is-default %) (:organization %)) teams))
team (first (filter #(= (:id %) team-id) teams))
source-org (:organization team)
current-org-id (:id source-org)
move-perm (dm/get-in source-org [:permissions :move-teams])
source-owner-id (:owner-id source-org)
can-create? (fn [org]
(let [perm (dm/get-in org [:permissions :create-teams])
is-own? (= profile-id (:owner-id org))]
source-organization (:organization team)
current-organization-id (:id source-organization)
move-perm (dm/get-in source-organization [:permissions :move-teams])
source-owner-id (:owner-id source-organization)
can-create? (fn [organization]
(let [perm (dm/get-in organization [:permissions :create-teams])
is-own? (= profile-id (:owner-id organization))]
(or (= perm "any") is-own?)))
orgs-by-move (case move-perm
"never"
[]
organizations-by-move (case move-perm
"never"
[]
"myOrganizations"
(filter #(= source-owner-id (:owner-id %)) all-orgs)
"myOrganizations"
(filter #(= source-owner-id (:owner-id %)) all-organizations)
;; Default to always-allowed behavior.
all-orgs)
orgs (filter can-create? orgs-by-move)
selectable-orgs (remove #(= current-org-id (:id %)) orgs)
;; Default to always-allowed behavior.
all-organizations)
organizations (filter can-create? organizations-by-move)
selectable-organizations (remove #(= current-organization-id (:id %)) organizations)
on-confirm (fn [organization-id]
(st/emit! (add-team-to-organization {:team-id team-id
:organization-id organization-id})))]
(if (empty? selectable-orgs)
(if (empty? selectable-organizations)
(rx/of (dt/teams-fetched teams)
(modal/show :no-permission-modal {:type :no-orgs-change}))
(->> (fetch-orgs-allowed team-id selectable-orgs)
(modal/show :no-permission-modal {:type :no-organizations-change}))
(->> (fetch-organizations-allowed team-id selectable-organizations)
(rx/mapcat
(fn [orgs-allowed]
(let [valid-orgs (filterv #(true? (get orgs-allowed (:id %))) selectable-orgs)
has-filtered? (< (count orgs) (count all-orgs))
(fn [organizations-allowed]
(let [valid-organizations (filterv #(true? (get organizations-allowed (:id %))) selectable-organizations)
has-filtered? (< (count organizations) (count all-organizations))
extra-props (when has-filtered?
{:info-message-key "dashboard.select-org-modal.permission-info"})]
{:info-message-key "dashboard.select-organization-modal.permission-info"})]
(rx/of
(dt/teams-fetched teams)
(if (empty? valid-orgs)
(if (empty? valid-organizations)
(modal/show
{:type :alert
:hide-actions? true
:message (tr "dashboard.team-organization.add.no-valid-orgs")
:title (tr "dashboard.change-org-modal.title")})
:message (tr "dashboard.team-organization.add.no-valid-organizations")
:title (tr "dashboard.change-organization-modal.title")})
(modal/show :select-organization-modal
(merge {:organizations selectable-orgs
:orgs-allowed orgs-allowed
:current-organization-id current-org-id
(merge {:organizations selectable-organizations
:organizations-allowed organizations-allowed
:current-organization-id current-organization-id
:on-confirm on-confirm
:team-id team-id
: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"
:title-key "dashboard.change-organization-modal.title"
:choose-key "dashboard.change-organization-modal.choose"
:placeholder-key "dashboard.change-organization-modal.select"
:accept-key "dashboard.change-organization-modal.accept"
:cancel-key "labels.cancel"}
extra-props)))))))))))))))))

View File

@ -93,7 +93,7 @@
state))
(defn with-refreshed-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 organization
permissions, updates the app state, and calls f with the fresh team data.
Returns an observable of events."
[team-id f]
@ -106,7 +106,7 @@
(f team)))))))
(defn check-and-create-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 organization
permissions, then shows the team-form modal or a no-permission modal."
[team-id]
(ptk/reify ::check-and-create-team
@ -116,23 +116,23 @@
(with-refreshed-team team-id
(fn [team]
(let [organization (:organization team)
in-org? (and (contains? cf/flags :nitrate) organization)
can-create? (if in-org?
in-organization? (and (contains? cf/flags :nitrate) organization)
can-create? (if in-organization?
(nitrate-perms/allowed? :create-team
{:org-perms {:owner-id (:owner-id organization)
:permissions (:permissions organization)}
{:organization-perms {:owner-id (:owner-id organization)
:permissions (:permissions organization)}
:profile-id profile-id
:team-perms (:permissions team)})
true)]
(rx/of (if can-create?
(modal/show :team-form (if in-org?
(modal/show :team-form (if in-organization?
{:organization-id (:id organization)
:organization-name (:name organization)}
{}))
(modal/show :no-permission-modal {:type :create-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 organization
permissions, then shows the confirmation modal or a no-permission modal."
[{:keys [team-id delete-fn]}]
(ptk/reify ::check-and-delete-team
@ -141,17 +141,17 @@
(let [profile-id (dm/get-in state [:profile :id])]
(with-refreshed-team team-id
(fn [team]
(let [org (:organization team)
in-org? (and (contains? cf/flags :nitrate) org)
can-delete? (if in-org?
(let [organization (:organization team)
in-organization? (and (contains? cf/flags :nitrate) organization)
can-delete? (if in-organization?
(nitrate-perms/allowed? :delete-team
{:org-perms {:owner-id (:owner-id org)
:permissions (:permissions org)}
{:organization-perms {:owner-id (:owner-id organization)
:permissions (:permissions organization)}
: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" (:name org))
message (if in-organization?
(tr "modals.delete-organization-team-confirm.message" (:name organization))
(tr "modals.delete-team-confirm.message"))]
(rx/of (if can-delete?
(modal/show
@ -163,7 +163,7 @@
(modal/show :no-permission-modal {:type :delete-team}))))))))))
(defn- check-new-team-members-permission-and-show-invite-members
"Receives refreshed team data with up-to-date org
"Receives refreshed team data with up-to-date organization
permissions, then shows the invite members modal or an appropriate alert."
[{:keys [team invite-email origin]}]
(ptk/reify ::check-new-team-members-permission-and-show-invite-members
@ -175,16 +175,16 @@
:invite-email invite-email}))]
(if (and (contains? cf/flags :nitrate)
(not (nitrate-perms/allowed? :add-anybody-to-team
{:org-perms (:organization team)})))
(->> (rp/cmd! :all-org-members-in-team
{:organization-perms (:organization team)})))
(->> (rp/cmd! :all-organization-members-in-team
{:team-id (:id team)
:organization-id (get-in team [:organization :id])})
(rx/mapcat
(fn [all-org-members-in-team?]
(if all-org-members-in-team?
(fn [all-organization-members-in-team?]
(if all-organization-members-in-team?
(rx/of (modal/show
{:type :alert
:message (tr "modals.invite-restricted-members.all-org-members-in-team" (get-in team [:organization :name]))
:message (tr "modals.invite-restricted-members.all-organization-members-in-team" (get-in team [:organization :name]))
:accept-label (tr "labels.accept")
:accept-style :primary
:title (tr "modals.invite-team-member.title")}))
@ -192,7 +192,7 @@
show-invite)))))
(defn check-and-invite-members
"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 organization
permissions, then shows invite-members modal or a permission error."
[{:keys [team-id origin invite-email]
:or {origin :team}}]
@ -202,10 +202,10 @@
(let [profile-id (dm/get-in state [:profile :id])]
(with-refreshed-team team-id
(fn [team]
(let [org (:organization team)
(let [organization (:organization team)
can-invite? (nitrate-perms/can-send-invitations?
{:nitrate-enabled? (contains? cf/flags :nitrate)
:organization org
:organization organization
:profile-id profile-id
:team-permissions (:permissions team)})]
(rx/of (if can-invite?
@ -609,7 +609,7 @@
(rx/catch on-error))))))
(defn check-and-submit-invite-members
"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 organization
permissions, then submits member invitations or shows a restriction modal."
[{:keys [team-id] :as params} origin do-invite-members]
(ptk/reify ::check-and-submit-invite-members
@ -619,9 +619,9 @@
(with-refreshed-team team-id
(fn [team]
(if (not (nitrate-perms/allowed? :add-anybody-to-team
{:org-perms (:organization team)}))
(->> (rp/cmd! :check-org-members {:organization-id (get-in team [:organization :id])
:emails (vec (:emails params))})
{:organization-perms (:organization team)}))
(->> (rp/cmd! :check-organization-members {:organization-id (get-in team [:organization :id])
:emails (vec (:emails params))})
(rx/mapcat
(fn [result]
(let [blocked (into [] (comp (filter (fn [[_ v]] (not v)))
@ -830,5 +830,5 @@
(defn team->organization [team]
(when-let [org (:organization team)]
(assoc org :default-team-id (:id team))))
(when-let [organization (:organization team)]
(assoc organization :default-team-id (:id team))))

View File

@ -137,7 +137,7 @@
:notification (dc/handle-notification msg)
:team-role-change (handle-change-team-role msg)
:team-membership-change (dc/team-membership-change msg)
:team-org-change (dc/handle-change-team-org msg)
:team-organization-change (dc/handle-change-team-organization msg)
:organization-change-sso (dc/handle-organization-change-sso msg)
nil))

View File

@ -44,7 +44,7 @@
(st/emit! (da/login-from-token tdata)))
(defmethod handle-token :team-invitation
[{:keys [state team-id org-team-id organization-name invitation-token] :as tdata}]
[{:keys [state team-id organization-team-id organization-name invitation-token] :as tdata}]
(when-let [{:keys [origin props]} (:organization-invitation-audit tdata)]
(st/emit!
(ev/event
@ -54,11 +54,11 @@
(case state
:created
(if org-team-id
(if organization-team-id
(st/emit!
(du/refresh-profile)
(dcm/go-to-dashboard-recent :team-id org-team-id)
(ntf/success (tr "auth.notifications.org-invitation-accepted" organization-name)))
(dcm/go-to-dashboard-recent :team-id organization-team-id)
(ntf/success (tr "auth.notifications.organization-invitation-accepted" organization-name)))
(st/emit!
(du/refresh-profile)
(dcm/go-to-dashboard-recent :team-id team-id)
@ -99,10 +99,10 @@
(st/emit!
(rt/nav :dashboard-recent {:team-id team-id}))
(= :org-not-found code)
(= :organization-not-found code)
(st/emit!
(rt/nav :dashboard-recent {:team-id team-id})
(ntf/error (tr "errors.org-not-found")))
(ntf/error (tr "errors.organization-not-found")))
(= :canceled-invitation code)
(let [profile (:profile @st/state)

View File

@ -1,41 +0,0 @@
;; 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.main.ui.components.org-avatar
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[rumext.v2 :as mf]))
(mf/defc org-avatar*
[{:keys [org size]}]
(let [name (:name org)
custom-photo (:custom-photo org)
avatar-bg (:avatar-bg-url org)
initials (d/get-initials name)]
(if custom-photo
[:img {:src custom-photo
:class (stl/css-case :org-avatar true
:org-avatar-custom true
:org-avatar-xxxl (= size "xxxl")
:org-avatar-xxl (= size "xxl")
:org-avatar-xl (= size "xl"))
:alt name}]
[:div {:class (stl/css-case :org-avatar true
:org-avatar-xxxl (= size "xxxl")
:org-avatar-xxl (= size "xxl")
:org-avatar-xl (= size "xl"))
:aria-hidden "true"}
[:img {:src avatar-bg
:class (stl/css :org-avatar-bg)
:alt ""}]
(when (seq initials)
[:span {:class (stl/css-case :org-avatar-initials true
:size-initials-xxxl (= size "xxxl")
:size-initials-xxl (= size "xxl")
:size-initials-xxl (= size "xl"))} ;; Keep the initials as xxl to make them legible
initials])])))

View 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.main.ui.components.organization-avatar
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[rumext.v2 :as mf]))
(mf/defc organization-avatar*
[{:keys [organization size]}]
(let [name (:name organization)
custom-photo (:custom-photo organization)
avatar-bg (:avatar-bg-url organization)
initials (d/get-initials name)]
(if custom-photo
[:img {:src custom-photo
:class (stl/css-case :organization-avatar true
:organization-avatar-custom true
:organization-avatar-xxxl (= size "xxxl")
:organization-avatar-xxl (= size "xxl")
:organization-avatar-xl (= size "xl"))
:alt name}]
[:div {:class (stl/css-case :organization-avatar true
:organization-avatar-xxxl (= size "xxxl")
:organization-avatar-xxl (= size "xxl")
:organization-avatar-xl (= size "xl"))
:aria-hidden "true"}
[:img {:src avatar-bg
:class (stl/css :organization-avatar-bg)
:alt ""}]
(when (seq initials)
[:span {:class (stl/css-case :organization-avatar-initials true
:size-initials-xxxl (= size "xxxl")
:size-initials-xxl (= size "xxl")
:size-initials-xxl (= size "xl"))} ;; Keep the initials as xxl to make them legible
initials])])))

View File

@ -8,35 +8,35 @@
@use "ds/colors.scss" as *;
@use "ds/_utils.scss" as *;
.org-avatar {
.organization-avatar {
position: relative;
overflow: hidden;
flex-shrink: 0;
}
.org-avatar-custom {
.organization-avatar-custom {
object-fit: cover;
}
.org-avatar-xxxl {
.organization-avatar-xxxl {
width: px2rem(30);
height: px2rem(30);
border-radius: px2rem(6);
}
.org-avatar-xxl {
.organization-avatar-xxl {
width: var(--sp-xxl);
height: var(--sp-xxl);
border-radius: px2rem(5);
}
.org-avatar-xl {
.organization-avatar-xl {
width: px2rem(22);
height: px2rem(22);
border-radius: var(--sp-xs);
}
.org-avatar-bg {
.organization-avatar-bg {
position: absolute;
inset: 0;
width: 100%;
@ -44,7 +44,7 @@
object-fit: cover;
}
.org-avatar-initials {
.organization-avatar-initials {
display: flex;
justify-content: center;
align-items: center;

View File

@ -97,20 +97,20 @@
:form form
:default default-member-id}])]))
(defn- make-leave-org-modal-form-schema [teams]
(defn- make-leave-organization-modal-form-schema [teams]
(into
[:map {:title "LeaveOrgModalForm"}]
(for [team teams]
[(keyword (str "member-id-" (:id team))) ::sm/text])))
(mf/defc leave-and-reassign-org-modal
(mf/defc leave-and-reassign-organization-modal
{::mf/register modal/components
::mf/register-as :leave-and-reassign-org
::mf/register-as :leave-and-reassign-organization
::mf/wrap [mf/memo]}
[{:keys [profile teams-to-transfer num-teams-to-delete accept] :as props}]
(let [schema (mf/with-memo [teams-to-transfer]
(make-leave-org-modal-form-schema teams-to-transfer))
(make-leave-organization-modal-form-schema teams-to-transfer))
;; Compute initial values for each team select
team-fields (mf/with-memo [teams-to-transfer]
(for [team teams-to-transfer]
@ -146,21 +146,21 @@
team-fields)]
(accept {:teams-to-transfer teams-to-transfer})))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-org-container)}
[:div {:class (stl/css :modal-organization-container)}
[:div {:class (stl/css :modal-header)}
[:h2 {:class (stl/css :modal-org-title)} (tr "modals.before-leave-org.title")]
[:h2 {:class (stl/css :modal-organization-title)} (tr "modals.before-leave-organization.title")]
[:button {:class (stl/css :modal-close-btn)
:on-click modal/hide!} deprecated-icon/close]]
[:div {:class (stl/css :modal-content)}
(if (zero? num-teams-to-delete)
[:p {:class (stl/css :modal-org-msg)}
(tr "modals.leave-org-and-reassign.hint")]
[:p {:class (stl/css :modal-organization-msg)}
(tr "modals.leave-organization-and-reassign.hint")]
[:*
[:p {:class (stl/css :modal-org-msg)}
(tr "modals.leave-org-and-reassign.hint-delete")]
[:p {:class (stl/css :modal-org-msg)}
(tr "modals.leave-org-and-reassign.hint-promote")]])
[:p {:class (stl/css :modal-organization-msg)}
(tr "modals.leave-organization-and-reassign.hint-delete")]
[:p {:class (stl/css :modal-organization-msg)}
(tr "modals.leave-organization-and-reassign.hint-promote")]])
[:& fm/form {:form form}
[:div {:class (stl/css :teams-container)}
(for [{:keys [team field-name default-member-id]} team-fields]

View File

@ -78,20 +78,20 @@
width: 100%;
}
.modal-org-container {
.modal-organization-container {
@extend %modal-container-base;
overflow-y: auto;
max-height: $sz-512;
}
.modal-org-title {
.modal-organization-title {
@include t.use-typography("headline-large");
color: var(--modal-title-foreground-color);
}
.modal-org-msg {
.modal-organization-msg {
@include t.use-typography("body-large");
color: var(--modal-text-foreground-color);

View File

@ -25,7 +25,7 @@
[app.main.ui.components.dropdown-menu :refer [dropdown-menu*
dropdown-menu-item*]]
[app.main.ui.components.link :refer [link*]]
[app.main.ui.components.org-avatar :refer [org-avatar*]]
[app.main.ui.components.organization-avatar :refer [organization-avatar*]]
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
@ -74,11 +74,11 @@
(def ^:private menu-icon
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
(def ^:private org-menu-icon
(deprecated-icon/icon-xref :menu (stl/css :org-menu-icon)))
(def ^:private organization-menu-icon
(deprecated-icon/icon-xref :menu (stl/css :organization-menu-icon)))
(def ^:private org-menu-icon-open
(deprecated-icon/icon-xref :menu (stl/css :org-menu-icon-open)))
(def ^:private organization-menu-icon-open
(deprecated-icon/icon-xref :menu (stl/css :organization-menu-icon-open)))
(def ^:private pin-icon
(deprecated-icon/icon-xref :pin (stl/css :pin-icon)))
@ -86,8 +86,8 @@
(def ^:private exit-icon
(deprecated-icon/icon-xref :exit (stl/css :exit-icon)))
(def ^:private add-org-icon
(deprecated-icon/icon-xref :add (stl/css :add-org-icon)))
(def ^:private add-organization-icon
(deprecated-icon/icon-xref :add (stl/css :add-organization-icon)))
(def ^:private arrow-up-right-icon
(deprecated-icon/icon-xref :arrow-up-right (stl/css :arrow-up-right-icon)))
@ -312,7 +312,7 @@
account-age-days (dnt/account-age-days profile)
on-org-click
on-organization-click
(mf/use-fn
(fn [event]
(let [team-id (-> (dom/get-current-target event)
@ -320,7 +320,7 @@
(uuid/parse))]
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))
on-create-org-click
on-create-organization-click
(mf/use-fn
(mf/deps account-age-days profile subscription-type team-count)
(fn []
@ -348,15 +348,15 @@
(mf/use-fn
(mf/deps organization profile)
(fn []
;; Navigate to active org if user owns it, otherwise to last visited org
;; Navigate to active organization if user owns it, otherwise to last visited organization
(if (and (:id organization)
(= (:id profile) (:owner-id organization)))
(dnt/go-to-nitrate-ac {:organization-id (:id organization)
:organization-slug (:slug organization)})
(dnt/go-to-nitrate-ac))))
empty-org (d/seek #(nil? (:id %)) organizations)
default-team-id (or (:default-team-id empty-org)
empty-organization (d/seek #(nil? (:id %)) organizations)
default-team-id (or (:default-team-id empty-organization)
(:default-team-id profile))
organizations (filter :id organizations)
@ -365,9 +365,9 @@
[:> dropdown-menu* props
[:> dropdown-menu-item* {:on-click on-org-click
[:> dropdown-menu-item* {:on-click on-organization-click
:data-value default-team-id
:class (stl/css :org-dropdown-item)}
:class (stl/css :organization-dropdown-item)}
[:span {:class (stl/css :my-teams-icon)}
[:> raw-svg* {:id penpot-logo-icon-subtle}]]
[:span {:class (stl/css :team-text)
@ -378,28 +378,28 @@
(when (seq organizations)
[:*
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:li {:role "presentation" :class (stl/css :org-section-label)}
[:li {:role "presentation" :class (stl/css :organization-section-label)}
(tr "dashboard.section.organizations")]])
(for [org-item organizations]
[:> dropdown-menu-item* {:on-click on-org-click
:data-value (:default-team-id org-item)
:class (stl/css :org-dropdown-item)
:key (str (:default-team-id org-item))}
[:> org-avatar* {:org org-item :size "xxl"}]
(for [organization-item organizations]
[:> dropdown-menu-item* {:on-click on-organization-click
:data-value (:default-team-id organization-item)
:class (stl/css :organization-dropdown-item)
:key (str (:default-team-id organization-item))}
[:> organization-avatar* {:organization organization-item :size "xxl"}]
[:span {:class (stl/css :team-text)
:title (:name org-item)} (:name org-item)]
(when (= (:default-team-id org-item) (:default-team-id organization))
:title (:name organization-item)} (:name organization-item)]
(when (= (:default-team-id organization-item) (:default-team-id organization))
tick-icon)])
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-org-click
:class (stl/css :org-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-org-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-org")]]
[:> dropdown-menu-item* {:on-click on-create-organization-click
:class (stl/css :organization-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-organization-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-organization")]]
(when is-valid-license?
[:> dropdown-menu-item* {:on-click on-go-to-cc-click
:class (stl/css :org-dropdown-item :action)}
:class (stl/css :organization-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} arrow-up-right-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.go-to-admin-console")]])]))
@ -446,7 +446,7 @@
(seq (remove :is-default (vals teams))))
[:*
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:li {:role "presentation" :class (stl/css :org-section-label)}
[:li {:role "presentation" :class (stl/css :organization-section-label)}
(tr "dashboard.section.teams")]])
(for [team-item (remove :is-default (vals teams))]
@ -624,7 +624,7 @@
:data-testid "delete-team"}
(tr "dashboard.delete-team")])]))
(mf/defc org-options-dropdown*
(mf/defc organization-options-dropdown*
{::mf/private true}
[{:keys [organization profile teams] :rest props}]
(let [default-team-id (mf/with-memo [teams]
@ -649,8 +649,8 @@
(fn [error]
(let [code (-> error ex-data :code)
;; Map error codes to their translation keys
error-map {:not-valid-teams "errors.org-leave.no-valid-teams"
:org-owner-cannot-leave "errors.org-leave.org-owner-cannot-leave"
error-map {:not-valid-teams "errors.organization-leave.no-valid-teams"
:organization-owner-cannot-leave "errors.organization-leave.organization-owner-cannot-leave"
:only-owner-can-delete-team "errors.team-leave.only-owner-can-delete"
:no-enough-members-for-leave "errors.team-leave.insufficient-members"
:member-does-not-exist "errors.team-leave.member-does-not-exists"
@ -678,27 +678,27 @@
(map :id))]
(st/emit! (dnt/leave-org {:id (:id organization)
:name (:name organization)
:default-team-id default-team-id
:teams-to-delete teams-to-delete
:teams-to-leave teams-to-leave
:member-added-at member-added-at
:organization-member-count-before
organization-member-count-before
:on-error on-error})))))
(st/emit! (dnt/leave-organization {:id (:id organization)
:name (:name organization)
:default-team-id default-team-id
:teams-to-delete teams-to-delete
:teams-to-leave teams-to-leave
:member-added-at member-added-at
:organization-member-count-before
organization-member-count-before
:on-error on-error})))))
on-leave-clicked
(mf/use-fn
(mf/deps leave-fn profile organization default-team-id teams-to-transfer on-error owned-teams-members-loaded?)
(fn []
(when owned-teams-members-loaded?
(st/emit! (dnt/show-leave-org-modal {:organization organization
:profile profile
:default-team-id default-team-id
:leave-fn leave-fn
:teams-to-transfer teams-to-transfer
:on-error on-error})))))]
(st/emit! (dnt/show-leave-organization-modal {:organization organization
:profile profile
:default-team-id default-team-id
:leave-fn leave-fn
:teams-to-transfer teams-to-transfer
:on-error on-error})))))]
(mf/use-effect
(mf/deps owned-teams)
(fn []
@ -710,10 +710,10 @@
[:> dropdown-menu-item* {:on-click on-leave-clicked
:class (stl/css :team-options-item)}
(tr "dashboard.leave-org")]]))
(tr "dashboard.leave-organization")]]))
(mf/defc sidebar-org-switch*
(mf/defc sidebar-organization-switch*
[{:keys [team profile]}]
(let [teams (mf/deref refs/teams)
@ -723,70 +723,70 @@
account-age-days (dnt/account-age-days profile)
current-org (dtm/team->organization team)
current-organization (dtm/team->organization team)
;; Find the "your-penpot" teams, and transform them in orgs. When
;; Find the "your-penpot" teams, and transform them in organizations. When
;; the selected team is directly accessible but not listed in
;; membership teams, include only its org so the org selector can
;; membership teams, include only its organization so the organization selector can
;; show the current selection without leaking the team into the
;; teams dropdown.
orgs (mf/with-memo [teams current-org]
(cond-> (->> teams
vals
(filter :is-default)
(map dtm/team->organization)
(d/index-by :id))
(:id current-org)
(assoc (:id current-org) current-org)))
organizations (mf/with-memo [teams current-organization]
(cond-> (->> teams
vals
(filter :is-default)
(map dtm/team->organization)
(d/index-by :id))
(:id current-organization)
(assoc (:id current-organization) current-organization)))
show-dropdown? (or (dnt/is-valid-license? profile)
(> (count orgs) 1))
(> (count organizations) 1))
org-teams (mf/with-memo [teams current-org]
(->> teams
vals
(filter #(= (dm/get-in % [:organization :id]) (:id current-org)))))
organization-teams (mf/with-memo [teams current-organization]
(->> teams
vals
(filter #(= (dm/get-in % [:organization :id]) (:id current-organization)))))
default-org? (nil? (:id current-org))
default-organization? (nil? (:id current-organization))
show-options? (and (not default-org?)
(not= (:id profile) (:owner-id current-org)))
show-options? (and (not default-organization?)
(not= (:id profile) (:owner-id current-organization)))
show-orgs-menu*
show-organizations-menu*
(mf/use-state false)
show-orgs-menu?
(deref show-orgs-menu*)
show-organizations-menu?
(deref show-organizations-menu*)
orgs-rect*
organizations-rect*
(mf/use-state nil)
orgs-portal-container
organizations-portal-container
(hooks/use-portal-container :popup)
show-org-options-menu*
show-organization-options-menu*
(mf/use-state false)
show-org-options-menu?
(deref show-org-options-menu*)
show-organization-options-menu?
(deref show-organization-options-menu*)
on-show-options-click
(mf/use-fn
(fn [event]
(dom/stop-propagation event)
(swap! show-org-options-menu* not)))
(swap! show-organization-options-menu* not)))
close-org-options-menu
(mf/use-fn #(reset! show-org-options-menu* false))
close-organization-options-menu
(mf/use-fn #(reset! show-organization-options-menu* false))
on-show-orgs-click
on-show-organizations-click
(mf/use-fn
(fn [event]
(dom/stop-propagation event)
(reset! orgs-rect* (dom/get-bounding-rect (dom/get-current-target event)))
(swap! show-orgs-menu* not)))
(reset! organizations-rect* (dom/get-bounding-rect (dom/get-current-target event)))
(swap! show-organizations-menu* not)))
on-show-orgs-keydown
on-show-organizations-keydown
(mf/use-fn
(fn [event]
(when (or (kbd/space? event)
@ -795,10 +795,10 @@
(dom/stop-propagation event)
(some-> (dom/get-current-target event)
(dom/click)))))
close-orgs-menu
(mf/use-fn #(reset! show-orgs-menu* false))
close-organizations-menu
(mf/use-fn #(reset! show-organizations-menu* false))
on-create-org-click
on-create-organization-click
(mf/use-fn
(mf/deps account-age-days profile subscription-type team-count)
(fn []
@ -822,36 +822,36 @@
(= subscription-type "unlimited")
(assoc :show-contact-sales-option true)))))))]
(if show-dropdown?
[:div {:class (stl/css :sidebar-org-switch)}
[:div {:class (stl/css :org-switch-content)}
[:button {:class (stl/css-case :current-org true :current-org-no-options (not show-options?))
:on-click on-show-orgs-click
:on-key-down on-show-orgs-keydown
:aria-expanded show-orgs-menu?
[:div {:class (stl/css :sidebar-organization-switch)}
[:div {:class (stl/css :organization-switch-content)}
[:button {:class (stl/css-case :current-organization true :current-organization-no-options (not show-options?))
:on-click on-show-organizations-click
:on-key-down on-show-organizations-keydown
:aria-expanded show-organizations-menu?
:aria-haspopup "menu"}
[:div {:class (stl/css :team-name)}
(if default-org?
(if default-organization?
[:*
[:span {:class (stl/css :my-teams-icon-xxxl)}
[:> raw-svg* {:id penpot-logo-icon-subtle}]]
[:span {:class (stl/css :team-text)}
(tr "dashboard.my-teams")]]
[:*
[:> org-avatar* {:org current-org :size "xxxl"}]
[:> organization-avatar* {:organization current-organization :size "xxxl"}]
[:span {:class (stl/css :team-text)}
(:name current-org)]])]
(:name current-organization)]])]
arrow-icon]
(when show-options?
[:> button* {:variant "ghost"
:type "button"
:class (stl/css :org-options-btn)
:class (stl/css :organization-options-btn)
:on-click on-show-options-click}
(if show-org-options-menu? org-menu-icon-open org-menu-icon)])]
(if show-organization-options-menu? organization-menu-icon-open organization-menu-icon)])]
;; Orgs Dropdown
(when show-orgs-menu?
(let [rect (deref orgs-rect*)]
;; Organizations dropdown
(when show-organizations-menu?
(let [rect (deref organizations-rect*)]
(mf/portal
(mf/html
[:div {:style {:position "fixed"
@ -859,40 +859,40 @@
:left (dm/str (:left rect) "px")
:width (dm/str (:width rect) "px")}}
[:> organizations-selector-dropdown* {:show true
:on-close close-orgs-menu
:on-close close-organizations-menu
:id "organizations-list"
:class (stl/css :dropdown :teams-dropdown)
:organization current-org
:organization current-organization
:profile profile
:organizations (->> (vals orgs)
:organizations (->> (vals organizations)
(sort-by (juxt (fn [o] (str/lower (:name o "")))
:id)))}]])
orgs-portal-container)))
;; Orgs options
[:> org-options-dropdown* {:show show-org-options-menu?
:on-close close-org-options-menu
:id "team-options"
:class (stl/css :dropdown :options-dropdown)
:organization current-org
:profile profile
:teams org-teams}]]
[:div {:class (stl/css :selected-org)}
[:span {:class (stl/css :org-penpot-icon)}
organizations-portal-container)))
;; Organization options
[:> organization-options-dropdown* {:show show-organization-options-menu?
:on-close close-organization-options-menu
:id "team-options"
:class (stl/css :dropdown :options-dropdown)
:organization current-organization
:profile profile
:teams organization-teams}]]
[:div {:class (stl/css :selected-organization)}
[:span {:class (stl/css :organization-penpot-icon)}
[:> raw-svg* {:id penpot-logo-icon}]]
"Penpot"
[:> button* {:variant "ghost"
:type "button"
:class (stl/css :create-org)
:on-click on-create-org-click} (tr "dashboard.plus-create-new-org")]])))
:class (stl/css :create-organization)
:on-click on-create-organization-click} (tr "dashboard.plus-create-new-organization")]])))
(mf/defc sidebar-team-switch*
[{:keys [team profile]}]
(let [nitrate? (contains? cf/flags :nitrate)
org (:organization team)
organization-id (when nitrate? (:id org))
organization (:organization team)
organization-id (when nitrate? (:id organization))
teams (cond->> (mf/deref refs/teams)
nitrate?
(filter #(= (dm/get-in (val %) [:organization :id]) organization-id))
@ -1111,8 +1111,8 @@
[:*
[:div {:class (stl/css :sidebar-content-wrapper)}
(when nitrate?
[:div {:class (stl/css :orgs-container)}
[:> sidebar-org-switch* {:team team :profile profile}]])
[:div {:class (stl/css :organizations-container)}
[:> sidebar-organization-switch* {:team team :profile profile}]])
[:div {:ref container
:class (stl/css-case :sidebar-content true :sidebar-content-nitrate nitrate?)}
[:> sidebar-team-switch* {:team team :profile profile}]

View File

@ -196,7 +196,7 @@
grid-template-columns: 1fr auto;
}
.org-dropdown-item {
.organization-dropdown-item {
@extend %menu-item-base;
display: grid;
@ -238,7 +238,7 @@
margin: 0;
}
.org-section-label {
.organization-section-label {
@include t.use-typography("headline-small");
color: var(--color-foreground-secondary);
@ -559,7 +559,7 @@
stroke: var(--icon-foreground);
}
.add-org-icon {
.add-organization-icon {
@extend %button-icon;
width: var(--sp-l);
@ -606,7 +606,7 @@
color: var(--color-accent-tertiary);
}
.orgs-container {
.organizations-container {
align-items: center;
display: flex;
height: calc(2 * var(--sp-xxxl));
@ -615,7 +615,7 @@
padding: 0 var(--sp-l);
}
.selected-org {
.selected-organization {
@include t.use-typography("body-medium");
color: var(--color-foreground-primary);
@ -626,12 +626,12 @@
gap: var(--sp-s);
}
.create-org {
.create-organization {
margin-inline-start: auto;
text-transform: uppercase;
}
.org-penpot-icon {
.organization-penpot-icon {
display: flex;
justify-content: center;
align-items: center;
@ -647,7 +647,7 @@
}
}
.org-icon {
.organization-icon {
display: flex;
justify-content: center;
align-items: center;
@ -685,12 +685,12 @@
}
}
.sidebar-org-switch {
.sidebar-organization-switch {
position: relative;
width: 100%;
}
.current-org {
.current-organization {
@include deprecated.button-style;
text-transform: none;
@ -703,15 +703,15 @@
width: 100%;
}
.current-org-no-options {
.current-organization-no-options {
gap: 0;
}
.current-org .arrow-icon {
.current-organization .arrow-icon {
margin-inline-end: var(--sp-xs);
}
.org-options {
.organization-options {
display: flex;
justify-content: center;
align-items: center;
@ -720,7 +720,7 @@
height: 100%;
}
.org-switch-content {
.organization-switch-content {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
@ -728,7 +728,7 @@
width: 100%;
}
.org-options-btn {
.organization-options-btn {
--icon-stroke: var(--icon-foreground);
display: flex;
@ -742,13 +742,13 @@
}
}
.org-menu-icon {
.organization-menu-icon {
@extend %button-icon;
stroke: var(--icon-stroke);
}
.org-menu-icon-open {
.organization-menu-icon-open {
@extend %button-icon;
stroke: var(--color-accent-primary);

View File

@ -158,11 +158,11 @@
teams-loaded? (seq teams)
no-orgs-created? (mf/with-memo [teams]
(and (seq teams)
(->> teams
vals
(not-any? :organization))))
no-organizations-created? (mf/with-memo [teams]
(and (seq teams)
(->> teams
vals
(not-any? :organization))))
handle-click
(mf/use-fn
@ -199,17 +199,17 @@
[:*
;; TODO add translations for this texts when we have the definitive ones
(if (and nitrate? teams-loaded? no-orgs-created? (not show-subscription-warning?))
(if (and nitrate? teams-loaded? no-organizations-created? (not show-subscription-warning?))
;; Banner for users with active nitrate license but no organizations created
[:div {:class (stl/css :nitrate-banner :highlighted)}
[:div {:class (stl/css :nitrate-content)}
[:span {:class (stl/css :nitrate-title)} (tr "subscription.banner.see-enterprise")]]
[:div {:class (stl/css :nitrate-content)}
[:span {:class (stl/css :nitrate-info)} (tr "subscription.banner.create-org-info")]
[:span {:class (stl/css :nitrate-info)} (tr "subscription.banner.create-organization-info")]
[:> button* {:variant "primary"
:type "button"
:class (stl/css :nitrate-bottom-button)
:on-click handle-go-to-cc} (tr "nitrate.activation-success.create-org")]]]
:on-click handle-go-to-cc} (tr "nitrate.activation-success.create-organization")]]]
;; Banner for users without nitrate license
(when (not nitrate?)

View File

@ -24,7 +24,7 @@
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.components.forms :as fm]
[app.main.ui.components.org-avatar :refer [org-avatar*]]
[app.main.ui.components.organization-avatar :refer [organization-avatar*]]
[app.main.ui.dashboard.change-owner]
[app.main.ui.dashboard.subscription :refer [members-cta*
show-subscription-members-banner?
@ -56,8 +56,8 @@
(def ^:private menu-icon
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
(def ^:private org-menu-icon
(deprecated-icon/icon-xref :menu (stl/css :org-menu-icon)))
(def ^:private organization-menu-icon
(deprecated-icon/icon-xref :menu (stl/css :organization-menu-icon)))
(def ^:private warning-icon
(deprecated-icon/icon-xref :msg-warning (stl/css :warning-icon)))
@ -888,41 +888,41 @@
(def schema:organization-form [:map {:title "SelectOrgForm"}
[:selected-id ::sm/uuid]])
(mf/defc render-org-combobox-avatar*
(mf/defc render-organization-combobox-avatar*
[{:keys [avatar]}]
[:> org-avatar* {:org (:organization avatar)
:size (:size avatar)}])
[:> organization-avatar* {:organization (:organization avatar)
:size (:size avatar)}])
(mf/defc select-organization-modal
{::mf/register modal/components
::mf/register-as :select-organization-modal}
[{:keys [organizations orgs-allowed current-organization-id on-confirm title-key text-key choose-key placeholder-key accept-key cancel-key info-message-key team-id]}]
[{:keys [organizations organizations-allowed current-organization-id on-confirm title-key text-key choose-key placeholder-key accept-key cancel-key info-message-key team-id]}]
(let [valid-organizations (mf/with-memo [organizations]
(remove #(= (:id %) current-organization-id) organizations))
options (mf/with-memo [valid-organizations orgs-allowed]
options (mf/with-memo [valid-organizations organizations-allowed]
(mapv (fn [organization]
(let [org-id (:id organization)
;; orgs-allowed is a map of org-id and a boolean indicating if it is allowed
enabled? (or (nil? orgs-allowed)
(true? (get orgs-allowed org-id)))]
(cond-> {:id (str org-id)
(let [organization-id (:id organization)
;; organizations-allowed is a map of organization-id and a boolean indicating if it is allowed
enabled? (or (nil? organizations-allowed)
(true? (get organizations-allowed organization-id)))]
(cond-> {:id (str organization-id)
:label (:name organization)
:disabled (not enabled?)
:dimmed (not enabled?)
:avatar {:render-fn render-org-combobox-avatar*
:avatar {:render-fn render-organization-combobox-avatar*
:organization organization
:size "xl"}}
(not enabled?)
(assoc :title (tr "dashboard.team-organization.disabled-org-tooltip")))))
(assoc :title (tr "dashboard.team-organization.disabled-organization-tooltip")))))
valid-organizations))
form (fm/use-form :schema schema:organization-form :initial {})
warning-info* (mf/use-state nil)
warning-info (deref warning-info*)
selected-org (mf/with-memo [warning-info valid-organizations]
(when warning-info
(d/seek #(= (:id %) (:organization-id warning-info)) valid-organizations)))
selected-organization (mf/with-memo [warning-info valid-organizations]
(when warning-info
(d/seek #(= (:id %) (:organization-id warning-info)) valid-organizations)))
on-change
(mf/use-fn
@ -931,13 +931,13 @@
(uforms/on-input-change form :selected-id id)
;; Check for external invitations when selection changes
(when (and team-id id)
(let [org-id (d/parse-uuid id)]
(let [organization-id (d/parse-uuid id)]
(->> (rp/cmd! :check-team-external-invitations
{:team-id team-id
:organization-id org-id})
:organization-id organization-id})
(rx/subs!
(fn [result]
(reset! warning-info* (assoc result :organization-id org-id)))
(reset! warning-info* (assoc result :organization-id organization-id)))
(fn [_]
(reset! warning-info* nil))))))))
@ -947,22 +947,22 @@
(fn []
(on-confirm (dm/get-in @form [:clean-data :selected-id]))))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-select-org-container :modal-container)}
[:div {:class (stl/css :modal-select-org-header)}
[:h2 {:class (stl/css :modal-select-org-title)}
[:div {:class (stl/css :modal-select-organization-container :modal-container)}
[:div {:class (stl/css :modal-select-organization-header)}
[:h2 {:class (stl/css :modal-select-organization-title)}
(tr title-key)]
[:button {:class (stl/css :modal-close-btn)
:on-click modal/hide!} deprecated-icon/close]]
(when text-key
[:div {:class (stl/css :modal-content :modal-select-org-text)} (tr text-key)])
[:div {:class (stl/css :modal-content :modal-select-organization-text)} (tr text-key)])
[:div {:class (stl/css :modal-select-org-body)}
[:div {:class (stl/css :modal-select-organization-body)}
(when info-message-key
[:div {:class (stl/css :modal-select-org-info)}
[:div {:class (stl/css :modal-select-organization-info)}
(tr info-message-key)])
[:div {:class (stl/css :modal-select-org-content)}
[:div {:class (stl/css :modal-select-organization-content)}
(tr choose-key)]
[:> combobox* {:id "selected-id"
:class (stl/css :team-member)
@ -976,14 +976,14 @@
(when (and warning-info
(:has-external-invitations warning-info)
(not (:allows-anybody warning-info))
selected-org)
[:div {:class (stl/css :modal-select-org-warning)}
selected-organization)
[:div {:class (stl/css :modal-select-organization-warning)}
[:& context-notification
{:content (tr "dashboard.select-org-modal.external-invitations-will-be-canceled")
{:content (tr "dashboard.select-organization-modal.external-invitations-will-be-canceled")
:class (stl/css :external-invitations-warning)
:level :warning}]
[:div {:class (stl/css :modal-select-org-content)}
(tr "dashboard.select-org-modal.external-invitations-warning" (:name selected-org))]])]
[:div {:class (stl/css :modal-select-organization-content)}
(tr "dashboard.select-organization-modal.external-invitations-warning" (:name selected-organization))]])]
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons :modal-invitation-action-buttons)}
@ -1526,15 +1526,15 @@
(filter :organization)
(map dtm/team->organization)))
;; Filter to orgs where user is allowed to create/add teams
;; Filter to organizations where user is allowed to create/add teams
organizations (mf/with-memo [all-organizations profile-id]
(->> all-organizations
(filter (fn [org]
(let [perm (get-in org [:permissions :create-teams])
is-owner? (= profile-id (:owner-id org))]
(filter (fn [organization]
(let [perm (get-in organization [:permissions :create-teams])
is-owner? (= profile-id (:owner-id organization))]
(or (= perm "any") is-owner?))))))
;; Keep parity with UX requirement: hide only when user belongs to one org.
;; Keep parity with UX requirement: hide only when user belongs to one organization.
can-change-organization? (mf/with-memo [all-organizations]
(> (count all-organizations) 1))
@ -1543,20 +1543,20 @@
(pos? (count all-organizations))
(not (:is-default team))))
show-org-options-menu*
show-organization-options-menu*
(mf/use-state false)
show-org-options-menu?
(deref show-org-options-menu*)
show-organization-options-menu?
(deref show-organization-options-menu*)
on-show-options-click
(mf/use-fn
(fn [event]
(dom/stop-propagation event)
(swap! show-org-options-menu* not)))
(swap! show-organization-options-menu* not)))
close-org-options-menu
(mf/use-fn #(reset! show-org-options-menu* false))
close-organization-options-menu
(mf/use-fn #(reset! show-organization-options-menu* false))
on-image-click
(mf/use-fn #(dom/click (mf/ref-val finput)))
@ -1565,11 +1565,11 @@
(fn [file]
(st/emit! (dtm/update-team-photo file)))
on-remove-team-from-org
on-remove-team-from-organization
(mf/use-fn
(mf/deps team)
(fn []
(st/emit! (dnt/show-remove-team-from-org-modal {:team-id (:id team)}))))
(st/emit! (dnt/show-remove-team-from-organization-modal {:team-id (:id team)}))))
on-add-team-to-organization
(mf/use-fn
@ -1577,11 +1577,11 @@
(fn []
(st/emit! (dnt/show-add-team-to-organization-modal {:team-id (:id team)}))))
on-change-team-org
on-change-team-organization
(mf/use-fn
(mf/deps team)
(fn []
(st/emit! (dnt/show-change-team-org-modal {:team-id (:id team)}))))]
(st/emit! (dnt/show-change-team-organization-modal {:team-id (:id team)}))))]
(mf/with-effect [team]
(dom/set-html-title (tr "title.team-settings"
@ -1622,8 +1622,8 @@
(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"}]
[:div {:class (stl/css :organization-block-content)}
[:> organization-avatar* {:organization (dtm/team->organization team) :size "xxxl"}]
[:span {:class (stl/css :block-text)}
(:name organization)]
@ -1631,19 +1631,19 @@
[:*
[:> 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?)
:class (stl/css-case :organization-options-btn (not show-organization-options-menu?) :organization-options-btn-open show-organization-options-menu?)
:on-click on-show-options-click}
org-menu-icon
organization-menu-icon
[:& dropdown {:show show-org-options-menu? :on-close close-org-options-menu :dropdown-id "org-options"}
[:ul {:class (stl/css :org-dropdown)
[:& dropdown {:show show-organization-options-menu? :on-close close-organization-options-menu :dropdown-id "organization-options"}
[:ul {:class (stl/css :organization-dropdown)
:role "listbox"}
(when can-change-organization?
[:li {:on-click on-change-team-org
:class (stl/css :org-dropdown-item)}
[:li {:on-click on-change-team-organization
:class (stl/css :organization-dropdown-item)}
(tr "dashboard.team-organization.change")])
[:li {:on-click on-remove-team-from-org
:class (stl/css :org-dropdown-item)}
[:li {:on-click on-remove-team-from-organization
:class (stl/css :organization-dropdown-item)}
(tr "dashboard.team-organization.remove")]]]]])]]
[:*
[:div {:class (stl/css :block-content)}

View File

@ -932,47 +932,47 @@
// SELECT ORGANIZATION MODAL
.modal-select-org-container {
.modal-select-organization-container {
display: flex;
flex-direction: column;
inline-size: $sz-512;
}
.modal-select-org-header {
.modal-select-organization-header {
margin-block-end: var(--sp-xxxl);
}
.modal-select-org-body {
.modal-select-organization-body {
display: flex;
flex-direction: column;
gap: var(--sp-s);
}
.modal-select-org-warning {
.modal-select-organization-warning {
display: flex;
flex-direction: column;
gap: var(--sp-xxl);
}
.modal-select-org-content {
.modal-select-organization-content {
@include t.use-typography("body-medium");
color: var(--color-foreground-secondary);
}
.modal-select-org-info {
.modal-select-organization-info {
@include t.use-typography("body-medium");
color: var(--color-foreground-secondary);
}
.modal-select-org-title {
.modal-select-organization-title {
@include t.use-typography("headline-large");
color: var(--color-foreground-primary);
}
.modal-select-org-text {
.modal-select-organization-text {
@include t.use-typography("body-medium");
color: var(--color-foreground-secondary);
@ -984,7 +984,7 @@
// ORGANIZATIONS SETTINGS
.org-block-content {
.organization-block-content {
display: grid;
grid-template-columns: var(--sp-xxxl) 1fr var(--sp-xxxl);
align-items: center;
@ -992,7 +992,7 @@
inline-size: max-content;
}
.org-options-btn {
.organization-options-btn {
padding: 0;
justify-content: center;
@ -1003,7 +1003,7 @@
}
}
.org-options-btn-open {
.organization-options-btn-open {
padding: 0;
justify-content: center;
@ -1013,7 +1013,7 @@
position: relative;
}
.org-menu-icon {
.organization-menu-icon {
display: flex;
justify-content: center;
align-items: center;
@ -1025,7 +1025,7 @@
stroke: var(--stroke-color);
}
.org-dropdown {
.organization-dropdown {
box-shadow: var(--el-shadow-dark);
display: flex;
flex-direction: column;
@ -1043,7 +1043,7 @@
min-inline-size: $sz-160;
}
.org-dropdown-item {
.organization-dropdown-item {
@include t.use-typography("body-small");
display: flex;

View File

@ -152,10 +152,10 @@
(tr "dashboard.no-permission-create-team.message" organization-name)]
:delete-team [(tr "dashboard.delete-team")
(tr "dashboard.no-permission-delete-team.message" organization-name)]
:no-orgs-create [(tr "dashboard.select-org-modal.title")
(tr "dashboard.no-org-allows-create-team.message")]
:no-orgs-change [(tr "dashboard.change-org-modal.title")
(tr "dashboard.no-permission-move-team.message" organization-name)])]
:no-organizations-create [(tr "dashboard.select-organization-modal.title")
(tr "dashboard.no-organization-allows-create-team.message")]
:no-organizations-change [(tr "dashboard.change-organization-modal.title")
(tr "dashboard.no-permission-move-team.message" organization-name)])]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-container)}
[:div {:class (stl/css :modal-header)}

View File

@ -61,23 +61,23 @@ Avatar rendering is defined per option with `:render-fn`, so each avatar type ca
```clj
;; Example renderer for organization avatars
(mf/defc render-org-avatar*
(mf/defc render-organization-avatar*
[{:keys [avatar]}]
(when (= :organization (:type avatar))
[:> org-avatar* {:org (:organization avatar)
:size (:size avatar)}]))
[:> organization-avatar* {:organization (:organization avatar)
:size (:size avatar)}]))
[:> combobox*
{:options [{:label "Design Team"
:id "org-design"
:avatar {:render-fn render-org-avatar*
:id "organization-design"
:avatar {:render-fn render-organization-avatar*
:size "s"
:organization {:name "Design Team"
:organization-avatar-bg-url "https://example.com/avatar-bg.svg"
:organization-custom-photo nil}}}
{:label "Engineering"
:id "org-engineering"
:avatar {:render-fn render-org-avatar*
:id "organization-engineering"
:avatar {:render-fn render-organization-avatar*
:size "s"
:organization {:name "Engineering"
:organization-avatar-bg-url nil

View File

@ -32,7 +32,7 @@
date-str (when cancel-at
(ct/format-inst cancel-at "d MMMM, yyyy"))
on-create-org
on-create-organization
(mf/use-fn
(fn []
(modal/hide!)
@ -64,6 +64,6 @@
(tr "nitrate.activation-success.enjoy")]
[:> button* {:variant "primary"
:on-click on-create-org
:on-click on-create-organization
:class (stl/css :modal-button)}
(tr "nitrate.activation-success.create-org")]]]]]))
(tr "nitrate.activation-success.create-organization")]]]]]))

View File

@ -13,7 +13,7 @@
[app.main.data.profile :as du]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.ui.components.org-avatar :refer [org-avatar*]]
[app.main.ui.components.organization-avatar :refer [organization-avatar*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.notifications.context-notification :refer [context-notification]]
@ -33,9 +33,9 @@
{::mf/register modal/components
::mf/register-as :delete-account}
[]
(let [orgs* (mf/use-state nil)
orgs (deref orgs*)
has-orgs? (seq orgs)
(let [organizations* (mf/use-state nil)
organizations (deref organizations*)
has-organizations? (seq organizations)
expanded* (mf/use-state true)
expanded? (deref expanded*)
@ -51,11 +51,11 @@
(if (contains? cf/flags :nitrate)
(let [sub (->> (rp/cmd! :get-owned-organizations-summary {})
(rx/subs!
(fn [result] (reset! orgs* (or result [])))
(fn [_] (reset! orgs* []))))]
(fn [result] (reset! organizations* (or result [])))
(fn [_] (reset! organizations* []))))]
(fn []
(rx/dispose! sub)))
(reset! orgs* [])))
(reset! organizations* [])))
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-container)}
@ -69,33 +69,33 @@
[:div {:class (stl/css :warning-notice)}
[:& context-notification
{:level :warning
:content (tr (if has-orgs?
"modals.delete-account.info.with-orgs"
:content (tr (if has-organizations?
"modals.delete-account.info.with-organizations"
"modals.delete-account.info"))}]]
(when has-orgs?
[:div {:class (stl/css :orgs-section)}
[:button {:class (stl/css :orgs-section-toggle)
(when has-organizations?
[:div {:class (stl/css :organizations-section)}
[:button {:class (stl/css :organizations-section-toggle)
:type "button"
:aria-expanded expanded?
:on-click on-toggle}
[:span {:class (stl/css :orgs-section-title)}
(tr "modals.delete-account.owned-orgs.list-title")]
[:span {:class (stl/css :organizations-section-title)}
(tr "modals.delete-account.owned-organizations.list-title")]
[:> icon* {:icon-id i/arrow
:size "s"
:class (stl/css-case :orgs-section-arrow true
:class (stl/css-case :organizations-section-arrow true
:expanded expanded?)}]]
(when expanded?
[:ul {:class (stl/css :org-list)}
(for [{:keys [id name team-count member-count] :as org} orgs]
[:li {:class (stl/css :org-item) :key id}
[:> org-avatar* {:org org :size "xxl"}]
[:div {:class (stl/css :org-info)}
[:span {:class (stl/css :org-name)} name]
[:div {:class (stl/css :org-counts)}
[:span (tr "modals.delete-account.owned-orgs.teams-count"
[:ul {:class (stl/css :organization-list)}
(for [{:keys [id name team-count member-count] :as organization} organizations]
[:li {:class (stl/css :organization-item) :key id}
[:> organization-avatar* {:organization organization :size "xxl"}]
[:div {:class (stl/css :organization-info)}
[:span {:class (stl/css :organization-name)} name]
[:div {:class (stl/css :organization-counts)}
[:span (tr "modals.delete-account.owned-organizations.teams-count"
(i18n/c (or team-count 0)))]
[:span (tr "modals.delete-account.owned-orgs.members-count"
[:span (tr "modals.delete-account.owned-organizations.members-count"
(i18n/c (or member-count 0)))]]]])])])]
[:div {:class (stl/css :modal-footer)}

View File

@ -60,7 +60,7 @@
color: var(--modal-title-foreground-color);
}
.orgs-section {
.organizations-section {
@include t.use-typography("body-medium");
display: flex;
@ -70,7 +70,7 @@
gap: var(--sp-l);
}
.orgs-section-toggle {
.organizations-section-toggle {
display: flex;
flex-direction: row;
align-items: center;
@ -81,20 +81,20 @@
cursor: pointer;
}
.orgs-section-title {
.organizations-section-title {
color: var(--color-accent-tertiary);
}
.orgs-section-arrow {
.organizations-section-arrow {
color: var(--color-accent-tertiary);
transition: transform 0.15s ease;
}
.orgs-section-arrow.expanded {
.organizations-section-arrow.expanded {
transform: rotate(90deg);
}
.org-list {
.organization-list {
display: flex;
flex-direction: column;
flex: 1 1 auto;
@ -106,23 +106,23 @@
overflow-y: auto;
}
.org-item {
.organization-item {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--sp-s);
}
.org-info {
.organization-info {
display: flex;
flex-direction: column;
}
.org-name {
.organization-name {
color: var(--modal-title-foreground-color);
}
.org-counts {
.organization-counts {
display: flex;
flex-direction: row;
gap: var(--sp-s);

View File

@ -612,7 +612,7 @@
:cancel-at (when (:cancel-at nitrate-license)
(tr "nitrate.subscription.active-until" (ct/format-inst (:cancel-at nitrate-license) "d MMMM, yyyy")))
:benefits-title (tr "subscription.settings.benefits.nitrate-unlimited-benefits")
:benefits [{:label (tr "subscription.settings.enterprise.nitrate.multi-org-management")
:benefits [{:label (tr "subscription.settings.enterprise.nitrate.multi-organization-management")
:description (tr "subscription.settings.enterprise.nitrate.support-team")}
{:label (tr "subscription.settings.enterprise.nitrate.enterprise-security")
:description (tr "subscription.settings.enterprise.nitrate.native-sso")}
@ -770,7 +770,7 @@
:price-value "$25"
:price-period (tr "subscription.settings.organization-member-month")
:benefits-title (tr "subscription.settings.benefits.nitrate-unlimited-benefits")
:benefits [{:label (tr "subscription.settings.enterprise.nitrate.multi-org-management")
:benefits [{:label (tr "subscription.settings.enterprise.nitrate.multi-organization-management")
:description (tr "subscription.settings.enterprise.nitrate.support-team")}
{:label (tr "subscription.settings.enterprise.nitrate.enterprise-security")
:description (tr "subscription.settings.enterprise.nitrate.native-sso")}
@ -877,7 +877,7 @@
[:div {:class (stl/css :modal-text-medium)}
(tr "nitrate.contact-sales.downgrade-title")]
[:ul {:class (stl/css :downgrade-list)}
[:li {:class (stl/css :downgrade-item)} (tr "nitrate.contact-sales.downgrade-org-deleted")]
[:li {:class (stl/css :downgrade-item)} (tr "nitrate.contact-sales.downgrade-organization-deleted")]
[:li {:class (stl/css :downgrade-item)} (tr "nitrate.contact-sales.downgrade-teams-available")]
[:li {:class (stl/css :downgrade-item)} (tr "nitrate.contact-sales.downgrade-storage-limited")]]

View File

@ -23,7 +23,7 @@
current-url (str "https://penpot.example.com/#/dashboard/recent?team-id=" team-id)
redirect-url "https://idp.example.com/authorize"
state {:current-team-id team-id}
event (dcm/handle-change-team-org
event (dcm/handle-change-team-organization
{:team {:id team-id :organization organization}
:notification nil})]
(mock/with-mocks
@ -52,14 +52,14 @@
(t/deftest organization-sso-activation-redirects-current-team
(t/async done
(let [team-id (uuid/next)
org-id (uuid/next)
organization-id (uuid/next)
current-url (str "https://penpot.example.com/#/workspace?team-id=" team-id)
redirect-url "https://idp.example.com/authorize"
state {:current-team-id team-id
:teams {team-id {:id team-id
:organization {:id org-id}}}}
:organization {:id organization-id}}}}
event (dcm/handle-organization-change-sso
{:organization-id org-id})]
{:organization-id organization-id})]
(mock/with-mocks
{cf/flags (conj cf/flags :nitrate)
rp/cmd! (mock/stub

View File

@ -147,10 +147,10 @@
(let [public-uri (u/uri "https://example.com/penpot/")]
(t/is (= "https://example.com/penpot/admin-console/"
(dnt/build-admin-console-url public-uri "" nil)))
(t/is (= "https://example.com/penpot/admin-console/organization/my-org/organization-id/people/"
(t/is (= "https://example.com/penpot/admin-console/organization/my-organization/organization-id/people/"
(dnt/build-admin-console-url
public-uri
"organization/my-org/organization-id/people/"
"organization/my-organization/organization-id/people/"
nil)))
(t/is (= {:action "create-organization"
:origin "dashboard:organization-switcher"}

View File

@ -370,7 +370,7 @@ msgid "dashboard.copy-suffix"
msgstr "(Kopie)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Neue Organisation erstellen"
#: src/app/main/ui/dashboard/sidebar.cljs:439

View File

@ -98,7 +98,7 @@ msgid "auth.new-password"
msgstr "Type a new password"
#: src/app/main/ui/auth/verify_token.cljs:53
msgid "auth.notifications.org-invitation-accepted"
msgid "auth.notifications.organization-invitation-accepted"
msgstr "You're now part of %s"
#: src/app/main/ui/auth/recovery.cljs:36
@ -297,7 +297,7 @@ msgid "common.unpublish"
msgstr "Unpublish"
#: src/app/main/data/nitrate.cljs:165
msgid "dasboard.leave-org.toast"
msgid "dashboard.leave-organization.toast"
msgstr "You're no longer part of the %s organization."
#: src/app/main/ui/dashboard/projects.cljs:90
@ -367,25 +367,25 @@ msgid "dashboard.change-email"
msgstr "Change email"
#, unused
msgid "dashboard.change-org-modal.accept"
msgid "dashboard.change-organization-modal.accept"
msgstr "MOVE TEAM"
#, unused
msgid "dashboard.change-org-modal.choose"
msgid "dashboard.change-organization-modal.choose"
msgstr "Move to:"
#, unused
msgid "dashboard.change-org-modal.select"
msgid "dashboard.change-organization-modal.select"
msgstr "Select an organization"
#, unused
msgid "dashboard.change-org-modal.text"
msgid "dashboard.change-organization-modal.text"
msgstr ""
"Projects and files will remain available to team members. The team will get "
"the configuration from the new organization."
#: src/app/main/data/nitrate.cljs:399, src/app/main/ui/dashboard/team_form.cljs:157
msgid "dashboard.change-org-modal.title"
msgid "dashboard.change-organization-modal.title"
msgstr "Change team's organization"
#: src/app/main/ui/dashboard/deleted.cljs:316
@ -397,7 +397,7 @@ msgid "dashboard.copy-suffix"
msgstr "(copy)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Create org"
#: src/app/main/ui/dashboard/sidebar.cljs:439
@ -816,7 +816,7 @@ msgid "dashboard.invite-profile"
msgstr "Invite people"
#: src/app/main/ui/dashboard/sidebar.cljs:671
msgid "dashboard.leave-org"
msgid "dashboard.leave-organization"
msgstr "Leave org"
#: src/app/main/ui/dashboard/sidebar.cljs:570, src/app/main/ui/dashboard/sidebar.cljs:577, src/app/main/ui/dashboard/sidebar.cljs:582, src/app/main/ui/dashboard/team.cljs:433
@ -892,7 +892,7 @@ msgid "dashboard.no-matches-for"
msgstr "No matches found for “%s“"
#: src/app/main/ui/dashboard/team_form.cljs:156
msgid "dashboard.no-org-allows-create-team.message"
msgid "dashboard.no-organization-allows-create-team.message"
msgstr "You don't have permission to add teams to any of your organizations."
#: src/app/main/ui/dashboard/team_form.cljs:152
@ -962,7 +962,7 @@ msgid "dashboard.order-invitations-by-status"
msgstr "Order by status"
#: src/app/main/data/dashboard.cljs:753
msgid "dashboard.org-deleted"
msgid "dashboard.organization-deleted"
msgstr "The %s organization has been deleted."
#: src/app/main/ui/settings/password.cljs:96, src/app/main/ui/settings/password.cljs:109
@ -1002,7 +1002,7 @@ msgid "dashboard.plugins.try-plugin"
msgstr "Try plugin: "
#: src/app/main/ui/dashboard/sidebar.cljs:798
msgid "dashboard.plus-create-new-org"
msgid "dashboard.plus-create-new-organization"
msgstr "+ Create org"
#: src/app/main/data/dashboard.cljs:805
@ -1090,35 +1090,35 @@ msgid "dashboard.section.teams"
msgstr "Teams"
#, unused
msgid "dashboard.select-org-modal.accept"
msgid "dashboard.select-organization-modal.accept"
msgstr "ADD TO ORGANIZATION"
#, unused
msgid "dashboard.select-org-modal.choose"
msgid "dashboard.select-organization-modal.choose"
msgstr "Choose an organization:"
#: src/app/main/ui/dashboard/team.cljs:975
msgid "dashboard.select-org-modal.external-invitations-warning"
msgid "dashboard.select-organization-modal.external-invitations-warning"
msgstr ""
"\"%s\" only allows organization members to join its teams. Any pending "
"invitations to people outside the organization will be revoked."
#: src/app/main/ui/dashboard/team.cljs:971
msgid "dashboard.select-org-modal.external-invitations-will-be-canceled"
msgid "dashboard.select-organization-modal.external-invitations-will-be-canceled"
msgstr "Pending invitations to external users will be canceled."
#, unused
msgid "dashboard.select-org-modal.permission-info"
msgid "dashboard.select-organization-modal.permission-info"
msgstr ""
"Here you find all your organizations where you are allowed to create or add "
"teams."
#, unused
msgid "dashboard.select-org-modal.select"
msgid "dashboard.select-organization-modal.select"
msgstr "Select an organization"
#: src/app/main/data/nitrate.cljs:342, src/app/main/ui/dashboard/team_form.cljs:155
msgid "dashboard.select-org-modal.title"
msgid "dashboard.select-organization-modal.title"
msgstr "Add team to an organization"
#: src/app/main/ui/settings/options.cljs:66
@ -1242,7 +1242,7 @@ msgid "dashboard.success-move-project"
msgstr "Your project has been moved successfully"
#, unused
msgid "dashboard.team-belong-org"
msgid "dashboard.team-belong-organization"
msgstr "This team now belongs to %s"
#: src/app/main/ui/dashboard/team.cljs:1602
@ -1254,7 +1254,7 @@ msgid "dashboard.team-members"
msgstr "Team members"
#, unused
msgid "dashboard.team-no-longer-belong-org"
msgid "dashboard.team-no-longer-belong-organization"
msgstr "This team no longer belongs to the organization %s"
#: src/app/main/ui/dashboard/team.cljs:1609
@ -1266,7 +1266,7 @@ msgid "dashboard.team-organization.add"
msgstr "Add to an organization"
#: src/app/main/data/nitrate.cljs:341, src/app/main/data/nitrate.cljs:398
msgid "dashboard.team-organization.add.no-valid-orgs"
msgid "dashboard.team-organization.add.no-valid-organizations"
msgstr "You don't have permission to add this team to any of your organizations."
#: src/app/main/ui/dashboard/team.cljs:1632
@ -1274,7 +1274,7 @@ msgid "dashboard.team-organization.change"
msgstr "Change team organization"
#: src/app/main/ui/dashboard/team.cljs:905
msgid "dashboard.team-organization.disabled-org-tooltip"
msgid "dashboard.team-organization.disabled-organization-tooltip"
msgstr "Only people within your organization can be invited."
#: src/app/main/ui/dashboard/team.cljs:1639
@ -1340,7 +1340,7 @@ msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Free"
#, unused
msgid "dashboard.user-no-longer-belong-org"
msgid "dashboard.user-no-longer-belong-organization"
msgstr "You are no longer a member of the organization %s"
#: src/app/main/ui/settings/options.cljs:109
@ -1727,15 +1727,15 @@ msgid "errors.only-creator-can-unlock"
msgstr "Only the version creator can unlock it"
#, unused
msgid "errors.org-leave.no-valid-teams"
msgid "errors.organization-leave.no-valid-teams"
msgstr "There was a problem leaving the organization. Please try again."
#, unused
msgid "errors.org-leave.org-owner-cannot-leave"
msgid "errors.organization-leave.organization-owner-cannot-leave"
msgstr "The organization owner can't leave the organization."
#: src/app/main/ui/auth/verify_token.cljs:97
msgid "errors.org-not-found"
msgid "errors.organization-not-found"
msgstr "That organization doesn't exists"
#: src/app/main/ui/settings/password.cljs
@ -3763,17 +3763,17 @@ msgid "modals.add-shared-confirm.message"
msgstr "Add “%s” as Shared Library"
#: src/app/main/data/nitrate.cljs:199
msgid "modals.before-leave-org.message"
msgid "modals.before-leave-organization.message"
msgstr ""
"You are the only member of some of the teams. Those teams will be deleted "
"along with its projects and files."
#: src/app/main/data/nitrate.cljs:198, src/app/main/ui/dashboard/change_owner.cljs:151
msgid "modals.before-leave-org.title"
msgid "modals.before-leave-organization.title"
msgstr "BEFORE YOU LEAVE THE ORGANIZATION"
#: src/app/main/data/nitrate.cljs:202
msgid "modals.before-leave-org.warning"
msgid "modals.before-leave-organization.warning"
msgstr "Any team where youre the only member will be deleted."
#: src/app/main/ui/workspace/nudge.cljs:59
@ -3831,22 +3831,22 @@ msgstr "By removing your account youll lose all your current projects and arc
#: src/app/main/ui/settings/delete_account.cljs
#, unused
msgid "modals.delete-account.info.with-orgs"
msgid "modals.delete-account.info.with-organizations"
msgstr ""
"This action is irreversible. Your account, subscription, and organizations "
"will be deleted immediately. You will also lose access to your current "
"projects and files."
#: src/app/main/ui/settings/delete_account.cljs:83
msgid "modals.delete-account.owned-orgs.list-title"
msgid "modals.delete-account.owned-organizations.list-title"
msgstr "Organizations that will be deleted"
#: src/app/main/ui/settings/delete_account.cljs:98
msgid "modals.delete-account.owned-orgs.members-count"
msgid "modals.delete-account.owned-organizations.members-count"
msgstr "%s members"
#: src/app/main/ui/settings/delete_account.cljs:96
msgid "modals.delete-account.owned-orgs.teams-count"
msgid "modals.delete-account.owned-organizations.teams-count"
msgstr "%s teams"
#: src/app/main/ui/settings/delete_account.cljs:64
@ -3930,7 +3930,7 @@ msgid "modals.delete-font.title"
msgstr "Deleting font"
#: src/app/main/data/team.cljs:129
msgid "modals.delete-org-team-confirm.message"
msgid "modals.delete-organization-team-confirm.message"
msgstr "Are you sure you want to delete this team that is part of %s org?"
#: src/app/main/ui/workspace/context_menu.cljs:707, src/app/main/ui/workspace/sidebar/sitemap.cljs:109
@ -4082,7 +4082,7 @@ msgid "modals.invite-restricted-members.all-blocked-title"
msgstr "No invitations were sent"
#: src/app/main/data/team.cljs:162
msgid "modals.invite-restricted-members.all-org-members-in-team"
msgid "modals.invite-restricted-members.all-organization-members-in-team"
msgstr ""
"Only \"%s\" organization's members can be invited to this team, and they "
"are all already team members. If you need more information, contact the "
@ -4169,35 +4169,35 @@ msgid "modals.leave-confirm.title"
msgstr "Leaving team"
#: src/app/main/ui/dashboard/change_owner.cljs:158
msgid "modals.leave-org-and-reassign.hint"
msgid "modals.leave-organization-and-reassign.hint"
msgstr ""
"You are the owner of some organization's teams. Please promote another "
"member to become an owner."
#: src/app/main/ui/dashboard/change_owner.cljs:161
msgid "modals.leave-org-and-reassign.hint-delete"
msgid "modals.leave-organization-and-reassign.hint-delete"
msgstr ""
"You are the only member of some of the teams. Those teams will be deleted "
"along with its projects and files."
#: src/app/main/ui/dashboard/change_owner.cljs:163
msgid "modals.leave-org-and-reassign.hint-promote"
msgid "modals.leave-organization-and-reassign.hint-promote"
msgstr ""
"Also, you are the owner of some organization's teams. Please promote "
"another member to become an owner."
#: src/app/main/data/nitrate.cljs:200, src/app/main/data/nitrate.cljs:209
msgid "modals.leave-org-confirm.accept"
msgid "modals.leave-organization-confirm.accept"
msgstr "Leave organization"
#: src/app/main/data/nitrate.cljs:208
msgid "modals.leave-org-confirm.message"
msgid "modals.leave-organization-confirm.message"
msgstr ""
"You will permanently lose access to all teams, projects, and files within "
"it."
#: src/app/main/data/nitrate.cljs:207
msgid "modals.leave-org-confirm.title"
msgid "modals.leave-organization-confirm.title"
msgstr "Leaving %s organization?"
#: src/app/main/ui/delete_shared.cljs:54
@ -4272,21 +4272,21 @@ msgid "modals.remove-shared-confirm.message"
msgstr "Remove “%s” as Shared Library"
#: src/app/main/data/nitrate.cljs:256
msgid "modals.remove-team-org.accept"
msgid "modals.remove-team-organization.accept"
msgstr "Remove from organization"
#: src/app/main/data/nitrate.cljs:254
msgid "modals.remove-team-org.info"
msgid "modals.remove-team-organization.info"
msgstr ""
"Projects and files will remain available to team members, but the "
"organization's settings will no longer apply."
#: src/app/main/data/nitrate.cljs:253
msgid "modals.remove-team-org.text"
msgid "modals.remove-team-organization.text"
msgstr "Are you sure you want to remove the '%s' team from the '%s' organization?"
#: src/app/main/data/nitrate.cljs:252
msgid "modals.remove-team-org.title"
msgid "modals.remove-team-organization.title"
msgstr "REMOVE TEAM FROM THE ORGANIZATION"
#: src/app/main/ui/workspace/nudge.cljs:52
@ -4354,7 +4354,7 @@ msgid "nitrate.activation-success.active-until"
msgstr "Your plan is active until %s."
#: src/app/main/ui/dashboard/subscription.cljs:195, src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:68
msgid "nitrate.activation-success.create-org"
msgid "nitrate.activation-success.create-organization"
msgstr "Create organization"
#: src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:63
@ -4409,7 +4409,7 @@ msgstr ""
"To switch to this plan, please contact our sales team. We'll help you "
"update your subscription and ensure everything is set up correctly."
msgid "nitrate.contact-sales.downgrade-org-deleted"
msgid "nitrate.contact-sales.downgrade-organization-deleted"
msgstr "Your organization will be deleted."
msgid "nitrate.contact-sales.downgrade-storage-limited"
@ -5863,7 +5863,7 @@ msgid "import-shortcuts.apply"
msgstr "Apply"
#: src/app/main/ui/dashboard/subscription.cljs:191
msgid "subscription.banner.create-org-info"
msgid "subscription.banner.create-organization-info"
msgstr ""
"Create an organization and see exactly how Enterprise works. Set rules, "
"manage your teams and discover a new level of control over your design "
@ -6098,7 +6098,7 @@ msgstr "Flat monthly bill"
msgid "subscription.settings.enterprise.unlimited-storage-benefit"
msgstr "Unlimited storage"
msgid "subscription.settings.enterprise.nitrate.multi-org-management"
msgid "subscription.settings.enterprise.nitrate.multi-organization-management"
msgstr "Multi-Org Management:"
msgid "subscription.settings.enterprise.nitrate.support-team"

View File

@ -102,7 +102,7 @@ msgid "auth.new-password"
msgstr "Introduce la nueva contraseña"
#: src/app/main/ui/auth/verify_token.cljs:53
msgid "auth.notifications.org-invitation-accepted"
msgid "auth.notifications.organization-invitation-accepted"
msgstr "Te uniste a la organización %s"
#: src/app/main/ui/auth/recovery.cljs:36
@ -304,7 +304,7 @@ msgid "common.unpublish"
msgstr "Despublicar"
#: src/app/main/data/nitrate.cljs:165
msgid "dasboard.leave-org.toast"
msgid "dashboard.leave-organization.toast"
msgstr "Ya no eres miembro de la organización %s."
#: src/app/main/ui/dashboard/projects.cljs:90
@ -374,25 +374,25 @@ msgid "dashboard.change-email"
msgstr "Cambiar correo"
#, unused
msgid "dashboard.change-org-modal.accept"
msgid "dashboard.change-organization-modal.accept"
msgstr "MOVER EL EQUIPO"
#, unused
msgid "dashboard.change-org-modal.choose"
msgid "dashboard.change-organization-modal.choose"
msgstr "Mover a:"
#, unused
msgid "dashboard.change-org-modal.select"
msgid "dashboard.change-organization-modal.select"
msgstr "Elige una organización"
#, unused
msgid "dashboard.change-org-modal.text"
msgid "dashboard.change-organization-modal.text"
msgstr ""
"Los miembros del equipo seguirán teniendo acceso a los proyectos y "
"ficheros. El equipo tendrá la configuración de la nueva organización."
#: src/app/main/data/nitrate.cljs:399, src/app/main/ui/dashboard/team_form.cljs:157
msgid "dashboard.change-org-modal.title"
msgid "dashboard.change-organization-modal.title"
msgstr "Cambiar el equipo de organización"
#: src/app/main/ui/dashboard/deleted.cljs:316
@ -404,7 +404,7 @@ msgid "dashboard.copy-suffix"
msgstr "(copia)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Crear org"
#: src/app/main/ui/dashboard/sidebar.cljs:439
@ -824,7 +824,7 @@ msgid "dashboard.invite-profile"
msgstr "Invitar a la gente"
#: src/app/main/ui/dashboard/sidebar.cljs:671
msgid "dashboard.leave-org"
msgid "dashboard.leave-organization"
msgstr "Abandonar organización"
#: src/app/main/ui/dashboard/sidebar.cljs:570, src/app/main/ui/dashboard/sidebar.cljs:577, src/app/main/ui/dashboard/sidebar.cljs:582, src/app/main/ui/dashboard/team.cljs:433
@ -900,7 +900,7 @@ msgid "dashboard.no-matches-for"
msgstr "No se encuentra “%s“"
#: src/app/main/ui/dashboard/team_form.cljs:156
msgid "dashboard.no-org-allows-create-team.message"
msgid "dashboard.no-organization-allows-create-team.message"
msgstr "No tienes permiso para añadir equipos a ninguna de tus organizaciones."
#: src/app/main/ui/dashboard/team_form.cljs:152
@ -972,7 +972,7 @@ msgid "dashboard.order-invitations-by-status"
msgstr "Ordenar por estado"
#: src/app/main/data/dashboard.cljs:753
msgid "dashboard.org-deleted"
msgid "dashboard.organization-deleted"
msgstr "La organización %s se ha borrado."
#: src/app/main/ui/settings/password.cljs:96, src/app/main/ui/settings/password.cljs:109
@ -1012,7 +1012,7 @@ msgid "dashboard.plugins.try-plugin"
msgstr "Prueba la extensión: "
#: src/app/main/ui/dashboard/sidebar.cljs:798
msgid "dashboard.plus-create-new-org"
msgid "dashboard.plus-create-new-organization"
msgstr "+ Crear org"
#: src/app/main/data/dashboard.cljs:805
@ -1100,36 +1100,36 @@ msgid "dashboard.section.teams"
msgstr "Equipos"
#, unused
msgid "dashboard.select-org-modal.accept"
msgid "dashboard.select-organization-modal.accept"
msgstr "AÑADIR A UNA ORGANIZACIÓN"
#, unused
msgid "dashboard.select-org-modal.choose"
msgid "dashboard.select-organization-modal.choose"
msgstr "Elige una organización:"
#: src/app/main/ui/dashboard/team.cljs:975
msgid "dashboard.select-org-modal.external-invitations-warning"
msgid "dashboard.select-organization-modal.external-invitations-warning"
msgstr ""
"\"%s\" solo permite que los miembros de la organización se unan a sus "
"equipos. Cualquier invitación pendiente a personas fuera de la organización "
"será revocada."
#: src/app/main/ui/dashboard/team.cljs:971
msgid "dashboard.select-org-modal.external-invitations-will-be-canceled"
msgid "dashboard.select-organization-modal.external-invitations-will-be-canceled"
msgstr "Las invitaciones pendientes a usuarios externos serán canceladas."
#, unused
msgid "dashboard.select-org-modal.permission-info"
msgid "dashboard.select-organization-modal.permission-info"
msgstr ""
"Aquí encontrarás todas las organizaciones en las que tienes permiso para "
"crear o añadir equipos."
#, unused
msgid "dashboard.select-org-modal.select"
msgid "dashboard.select-organization-modal.select"
msgstr "Elige una organización"
#: src/app/main/data/nitrate.cljs:342, src/app/main/ui/dashboard/team_form.cljs:155
msgid "dashboard.select-org-modal.title"
msgid "dashboard.select-organization-modal.title"
msgstr "Añadir el equipo a una organización"
#: src/app/main/ui/settings/options.cljs:66
@ -1253,7 +1253,7 @@ msgid "dashboard.success-move-project"
msgstr "Tu proyecto ha sido movido con éxito"
#, unused
msgid "dashboard.team-belong-org"
msgid "dashboard.team-belong-organization"
msgstr "Este equipo ahora pertenece a la organización %s"
#: src/app/main/ui/dashboard/team.cljs:1602
@ -1265,7 +1265,7 @@ msgid "dashboard.team-members"
msgstr "Integrantes del equipo"
#, unused
msgid "dashboard.team-no-longer-belong-org"
msgid "dashboard.team-no-longer-belong-organization"
msgstr "Este equipo ya no pertenece a la organización %s"
#: src/app/main/ui/dashboard/team.cljs:1609
@ -1277,7 +1277,7 @@ msgid "dashboard.team-organization.add"
msgstr "Añadir a una organización"
#: src/app/main/data/nitrate.cljs:341, src/app/main/data/nitrate.cljs:398
msgid "dashboard.team-organization.add.no-valid-orgs"
msgid "dashboard.team-organization.add.no-valid-organizations"
msgstr "No tienes permiso para añadir este equipo a ninguna de tus organizaciones."
#: src/app/main/ui/dashboard/team.cljs:1632
@ -1285,7 +1285,7 @@ msgid "dashboard.team-organization.change"
msgstr "Cambiar el equipo de organización"
#: src/app/main/ui/dashboard/team.cljs:905
msgid "dashboard.team-organization.disabled-org-tooltip"
msgid "dashboard.team-organization.disabled-organization-tooltip"
msgstr "Solo se puede invitar a personas que estén dentro de tu organización."
#: src/app/main/ui/dashboard/team.cljs:1639
@ -1351,7 +1351,7 @@ msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Gratis"
#, unused
msgid "dashboard.user-no-longer-belong-org"
msgid "dashboard.user-no-longer-belong-organization"
msgstr "Ya no perteneces a la organización %s"
#: src/app/main/ui/settings/options.cljs:109
@ -1693,17 +1693,17 @@ msgid "errors.migration-in-progress"
msgstr "Migración en proceso"
#, unused
msgid "errors.org-leave.no-valid-teams"
msgid "errors.organization-leave.no-valid-teams"
msgstr ""
"Ha habido un problema abandonando la organización. Intentalo de nuevo, por "
"favor."
#, unused
msgid "errors.org-leave.org-owner-cannot-leave"
msgid "errors.organization-leave.organization-owner-cannot-leave"
msgstr "El dueño de la organización no puede abandonarla."
#: src/app/main/ui/auth/verify_token.cljs:97
msgid "errors.org-not-found"
msgid "errors.organization-not-found"
msgstr "Esa organización no existe"
#: src/app/main/ui/settings/password.cljs
@ -3655,17 +3655,17 @@ msgid "modals.add-shared-confirm.message"
msgstr "Añadir “%s” como Biblioteca Compartida"
#: src/app/main/data/nitrate.cljs:199
msgid "modals.before-leave-org.message"
msgid "modals.before-leave-organization.message"
msgstr ""
"Eres el único miembro de algunos equipos. Esos equipos se van a borrar, "
"junto con sus proyectos y ficheros."
#: src/app/main/data/nitrate.cljs:198, src/app/main/ui/dashboard/change_owner.cljs:151
msgid "modals.before-leave-org.title"
msgid "modals.before-leave-organization.title"
msgstr "ANTES DE ABANDONAR LA ORGANIZACIÓN"
#: src/app/main/data/nitrate.cljs:202
msgid "modals.before-leave-org.warning"
msgid "modals.before-leave-organization.warning"
msgstr "Se van a borrar todos los equipos en los que eres el único miembro."
#: src/app/main/ui/workspace/nudge.cljs:59
@ -3723,22 +3723,22 @@ msgstr "Si borras tu cuenta perderás todos tus proyectos y archivos."
#: src/app/main/ui/settings/delete_account.cljs
#, unused
msgid "modals.delete-account.info.with-orgs"
msgid "modals.delete-account.info.with-organizations"
msgstr ""
"Esta acción es irreversible. Tu cuenta, suscripción y organizaciones se "
"eliminarán inmediatamente. También perderás el acceso a tus proyectos y "
"archivos actuales."
#: src/app/main/ui/settings/delete_account.cljs:83
msgid "modals.delete-account.owned-orgs.list-title"
msgid "modals.delete-account.owned-organizations.list-title"
msgstr "Organizaciones que se eliminarán"
#: src/app/main/ui/settings/delete_account.cljs:98
msgid "modals.delete-account.owned-orgs.members-count"
msgid "modals.delete-account.owned-organizations.members-count"
msgstr "%s miembros"
#: src/app/main/ui/settings/delete_account.cljs:96
msgid "modals.delete-account.owned-orgs.teams-count"
msgid "modals.delete-account.owned-organizations.teams-count"
msgstr "%s equipos"
#: src/app/main/ui/settings/delete_account.cljs:64
@ -3812,7 +3812,7 @@ msgid "modals.delete-font.title"
msgstr "Eliminando fuente"
#: src/app/main/data/team.cljs:129
msgid "modals.delete-org-team-confirm.message"
msgid "modals.delete-organization-team-confirm.message"
msgstr ""
"¿Estás seguro de que deseas eliminar este equipo que forma parte de la "
"organización %s?"
@ -3957,7 +3957,7 @@ msgid "modals.invite-restricted-members.all-blocked-title"
msgstr "No se envió ninguna invitación"
#: src/app/main/data/team.cljs:162
msgid "modals.invite-restricted-members.all-org-members-in-team"
msgid "modals.invite-restricted-members.all-organization-members-in-team"
msgstr ""
"Solo se puede invitar a este equipo a miembros de la organización \"%s\", y "
"todas esas personas ya son miembros del equipo. Si necesitas más "
@ -4044,35 +4044,35 @@ msgid "modals.leave-confirm.title"
msgstr "Abandonando el equipo"
#: src/app/main/ui/dashboard/change_owner.cljs:158
msgid "modals.leave-org-and-reassign.hint"
msgid "modals.leave-organization-and-reassign.hint"
msgstr ""
"Tienes la propiedad de algunos equipos de esta organización. Por favor "
"selecciona otra persona integrante para promover al rol Propiedad."
#: src/app/main/ui/dashboard/change_owner.cljs:161
msgid "modals.leave-org-and-reassign.hint-delete"
msgid "modals.leave-organization-and-reassign.hint-delete"
msgstr ""
"Eres el único miembro de algunos equipos. Esos equipos se van a borrar, "
"junto con sus proyectos y ficheros."
#: src/app/main/ui/dashboard/change_owner.cljs:163
msgid "modals.leave-org-and-reassign.hint-promote"
msgid "modals.leave-organization-and-reassign.hint-promote"
msgstr ""
"Además, tienes la propiedad de algunos equipos de esta organización. Por "
"favor selecciona otra persona integrante para promover al rol Propiedad."
#: src/app/main/data/nitrate.cljs:200, src/app/main/data/nitrate.cljs:209
msgid "modals.leave-org-confirm.accept"
msgid "modals.leave-organization-confirm.accept"
msgstr "Abandonar organización"
#: src/app/main/data/nitrate.cljs:208
msgid "modals.leave-org-confirm.message"
msgid "modals.leave-organization-confirm.message"
msgstr ""
"Perderás permanentemente el acceso a todos los equipos, proyectos y "
"archivos en ella."
#: src/app/main/data/nitrate.cljs:207
msgid "modals.leave-org-confirm.title"
msgid "modals.leave-organization-confirm.title"
msgstr "¿Abandonando la organización %s?"
#: src/app/main/ui/delete_shared.cljs:54
@ -4147,21 +4147,21 @@ msgid "modals.remove-shared-confirm.message"
msgstr "Añadir “%s” como Biblioteca Compartida"
#: src/app/main/data/nitrate.cljs:256
msgid "modals.remove-team-org.accept"
msgid "modals.remove-team-organization.accept"
msgstr "Eliminar de la organización"
#: src/app/main/data/nitrate.cljs:254
msgid "modals.remove-team-org.info"
msgid "modals.remove-team-organization.info"
msgstr ""
"Los proyectos y archivos seguirán estando disponibles para los miembros del "
"equipo, pero la configuración de la organización dejará de aplicarse."
#: src/app/main/data/nitrate.cljs:253
msgid "modals.remove-team-org.text"
msgid "modals.remove-team-organization.text"
msgstr "¿Estás seguro de que quieres eliminar el equipo %s de la organización %s?"
#: src/app/main/data/nitrate.cljs:252
msgid "modals.remove-team-org.title"
msgid "modals.remove-team-organization.title"
msgstr "ELIMINAR EQUIPO DE LA ORGANIZACIÓN"
#: src/app/main/ui/workspace/nudge.cljs:52
@ -4229,7 +4229,7 @@ msgid "nitrate.activation-success.active-until"
msgstr "Tu plan está activo hasta el %s."
#: src/app/main/ui/dashboard/subscription.cljs:195, src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:68
msgid "nitrate.activation-success.create-org"
msgid "nitrate.activation-success.create-organization"
msgstr "Crear organización"
#: src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:63
@ -4285,7 +4285,7 @@ msgstr ""
"ayudaremos a actualizar tu suscripción y a asegurarnos de que todo esté "
"configurado correctamente."
msgid "nitrate.contact-sales.downgrade-org-deleted"
msgid "nitrate.contact-sales.downgrade-organization-deleted"
msgstr "Tu organización será eliminada."
msgid "nitrate.contact-sales.downgrade-storage-limited"
@ -5718,7 +5718,7 @@ msgid "import-shortcuts.apply"
msgstr "Aplicar"
#: src/app/main/ui/dashboard/subscription.cljs:191
msgid "subscription.banner.create-org-info"
msgid "subscription.banner.create-organization-info"
msgstr ""
"Crea una organización y descubre exactamente cómo funciona Enterprise. "
"Establece normas, gestiona tus equipos y disfruta de un nuevo nivel de "
@ -5962,7 +5962,7 @@ msgstr "Factura mensual fija"
msgid "subscription.settings.enterprise.unlimited-storage-benefit"
msgstr "Almacenamiento ilimitado"
msgid "subscription.settings.enterprise.nitrate.multi-org-management"
msgid "subscription.settings.enterprise.nitrate.multi-organization-management"
msgstr "Gestión multiorganización:"
msgid "subscription.settings.enterprise.nitrate.support-team"

View File

@ -366,7 +366,7 @@ msgid "dashboard.copy-suffix"
msgstr "(copie)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Créer une organisation"
#: src/app/main/ui/dashboard/sidebar.cljs:439

View File

@ -98,7 +98,7 @@ msgid "auth.new-password"
msgstr "Nouveau mot de passe"
#: src/app/main/ui/auth/verify_token.cljs:53
msgid "auth.notifications.org-invitation-accepted"
msgid "auth.notifications.organization-invitation-accepted"
msgstr "Tu fais maintenant partie de %s"
#: src/app/main/ui/auth/recovery.cljs:36
@ -300,7 +300,7 @@ msgid "common.unpublish"
msgstr "Dépublier"
#: src/app/main/data/nitrate.cljs:165
msgid "dasboard.leave-org.toast"
msgid "dashboard.leave-organization.toast"
msgstr "Tu ne fais plus partie de l'organisation %s."
#: src/app/main/ui/dashboard/projects.cljs:90
@ -368,25 +368,25 @@ msgid "dashboard.change-email"
msgstr "Changer l'adresse courriel"
#, unused
msgid "dashboard.change-org-modal.accept"
msgid "dashboard.change-organization-modal.accept"
msgstr "DÉPLACER L'ÉQUIPE"
#, unused
msgid "dashboard.change-org-modal.choose"
msgid "dashboard.change-organization-modal.choose"
msgstr "Déplacer vers :"
#, unused
msgid "dashboard.change-org-modal.select"
msgid "dashboard.change-organization-modal.select"
msgstr "Sélectionner une organisation"
#, unused
msgid "dashboard.change-org-modal.text"
msgid "dashboard.change-organization-modal.text"
msgstr ""
"Les membres de l'équipe maintiendront l'accès aux projets et fichiers. "
"L'équipe recevra les configurations de la nouvelle organisation."
#: src/app/main/data/nitrate.cljs:399, src/app/main/ui/dashboard/team_form.cljs:157
msgid "dashboard.change-org-modal.title"
msgid "dashboard.change-organization-modal.title"
msgstr "Changer l'organisation de l'équipe"
#: src/app/main/ui/dashboard/deleted.cljs:316
@ -398,7 +398,7 @@ msgid "dashboard.copy-suffix"
msgstr "(copier)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Crée une nouvelle organisation"
#: src/app/main/ui/dashboard/sidebar.cljs:439
@ -822,7 +822,7 @@ msgid "dashboard.invite-profile"
msgstr "Inviter des gens"
#: src/app/main/ui/dashboard/sidebar.cljs:671
msgid "dashboard.leave-org"
msgid "dashboard.leave-organization"
msgstr "Quitter l'organisation"
#: src/app/main/ui/dashboard/sidebar.cljs:570, src/app/main/ui/dashboard/sidebar.cljs:577, src/app/main/ui/dashboard/sidebar.cljs:582, src/app/main/ui/dashboard/team.cljs:433
@ -900,7 +900,7 @@ msgid "dashboard.no-matches-for"
msgstr "Aucun résultat de recherche pour « %s»"
#: src/app/main/ui/dashboard/team_form.cljs:156
msgid "dashboard.no-org-allows-create-team.message"
msgid "dashboard.no-organization-allows-create-team.message"
msgstr "Tu n'as pas la permission d'ajouter des équipes à tes organisations."
#: src/app/main/ui/dashboard/team_form.cljs:152
@ -970,7 +970,7 @@ msgid "dashboard.order-invitations-by-status"
msgstr "Ordre par statut"
#: src/app/main/data/dashboard.cljs:753
msgid "dashboard.org-deleted"
msgid "dashboard.organization-deleted"
msgstr "L'organisation %s a été supprimée."
#: src/app/main/ui/settings/password.cljs:96, src/app/main/ui/settings/password.cljs:109
@ -1010,7 +1010,7 @@ msgid "dashboard.plugins.try-plugin"
msgstr "Essayer le plugiciel : "
#: src/app/main/ui/dashboard/sidebar.cljs:798
msgid "dashboard.plus-create-new-org"
msgid "dashboard.plus-create-new-organization"
msgstr "+ Créer une org"
#: src/app/main/data/dashboard.cljs:805
@ -1095,36 +1095,36 @@ msgid "dashboard.section.organizations"
msgstr "Organisations"
#, unused
msgid "dashboard.select-org-modal.accept"
msgid "dashboard.select-organization-modal.accept"
msgstr "AJOUTER À L'ORGANISATION"
#, unused
msgid "dashboard.select-org-modal.choose"
msgid "dashboard.select-organization-modal.choose"
msgstr "Choisir une organisation :"
#: src/app/main/ui/dashboard/team.cljs:975
msgid "dashboard.select-org-modal.external-invitations-warning"
msgid "dashboard.select-organization-modal.external-invitations-warning"
msgstr ""
"« %s » ne permet qu'aux membres d'organisations de joindre ses équipes. Les "
"invitations en attente à des gens externes à l'organisation seront "
"révoquées."
#: src/app/main/ui/dashboard/team.cljs:971
msgid "dashboard.select-org-modal.external-invitations-will-be-canceled"
msgid "dashboard.select-organization-modal.external-invitations-will-be-canceled"
msgstr "Les invitations en attente à des usagers externes seront annulées."
#, unused
msgid "dashboard.select-org-modal.permission-info"
msgid "dashboard.select-organization-modal.permission-info"
msgstr ""
"Ici tu trouveras toutes les organisations auxquelles tu as le droit de "
"créer ou ajouter des équipes."
#, unused
msgid "dashboard.select-org-modal.select"
msgid "dashboard.select-organization-modal.select"
msgstr "Choisir une organisation :"
#: src/app/main/data/nitrate.cljs:342, src/app/main/ui/dashboard/team_form.cljs:155
msgid "dashboard.select-org-modal.title"
msgid "dashboard.select-organization-modal.title"
msgstr "Ajouter une équipe à l'organisation"
#: src/app/main/ui/settings/options.cljs:66
@ -1248,7 +1248,7 @@ msgid "dashboard.success-move-project"
msgstr "Le projet a été déplacé avec succès"
#, unused
msgid "dashboard.team-belong-org"
msgid "dashboard.team-belong-organization"
msgstr "Cette équipe appartient maintenant à l'organisation %s"
#: src/app/main/ui/dashboard/team.cljs:1602
@ -1260,7 +1260,7 @@ msgid "dashboard.team-members"
msgstr "Membres de l'équipe"
#, unused
msgid "dashboard.team-no-longer-belong-org"
msgid "dashboard.team-no-longer-belong-organization"
msgstr "Cette équipe n'appartient plus à l'organisation %s"
#: src/app/main/ui/dashboard/team.cljs:1609
@ -1272,7 +1272,7 @@ msgid "dashboard.team-organization.add"
msgstr "Ajouter à l'organisation"
#: src/app/main/data/nitrate.cljs:341, src/app/main/data/nitrate.cljs:398
msgid "dashboard.team-organization.add.no-valid-orgs"
msgid "dashboard.team-organization.add.no-valid-organizations"
msgstr "Tu n'as pas le droit d'ajouter cette équipe à tes organisations."
#: src/app/main/ui/dashboard/team.cljs:1632
@ -1280,7 +1280,7 @@ msgid "dashboard.team-organization.change"
msgstr "Changer l'organisation de l'équipe"
#: src/app/main/ui/dashboard/team.cljs:905
msgid "dashboard.team-organization.disabled-org-tooltip"
msgid "dashboard.team-organization.disabled-organization-tooltip"
msgstr "Seules les personnes de ton organisation peuvent être invitées."
#: src/app/main/ui/dashboard/team.cljs:1639
@ -1346,7 +1346,7 @@ msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot gratuit"
#, unused
msgid "dashboard.user-no-longer-belong-org"
msgid "dashboard.user-no-longer-belong-organization"
msgstr "Tu n'es plus membre de l'organisation %s"
#: src/app/main/ui/settings/options.cljs:109
@ -1726,15 +1726,15 @@ msgid "errors.only-creator-can-unlock"
msgstr "Seul le créateur de la version peut la déverrouiller"
#, unused
msgid "errors.org-leave.no-valid-teams"
msgid "errors.organization-leave.no-valid-teams"
msgstr "Un problème est survenu en quittant l'organisation. Merci de réessayer."
#, unused
msgid "errors.org-leave.org-owner-cannot-leave"
msgid "errors.organization-leave.organization-owner-cannot-leave"
msgstr "Le propriétaire de l'organisation ne peut pas la quitter."
#: src/app/main/ui/auth/verify_token.cljs:97
msgid "errors.org-not-found"
msgid "errors.organization-not-found"
msgstr "Cette organisation n'existe pas"
#: src/app/main/ui/settings/password.cljs
@ -3760,17 +3760,17 @@ msgid "modals.add-shared-confirm.message"
msgstr "Ajouter « %s » comme bibliothèque partagée"
#: src/app/main/data/nitrate.cljs:199
msgid "modals.before-leave-org.message"
msgid "modals.before-leave-organization.message"
msgstr ""
"Tu es le seul membre de certaines équipes. Ces équipes, ainsi que leurs "
"projets et fichiers, seront supprimées."
#: src/app/main/data/nitrate.cljs:198, src/app/main/ui/dashboard/change_owner.cljs:151
msgid "modals.before-leave-org.title"
msgid "modals.before-leave-organization.title"
msgstr "AVANT DE QUITTER L'ORGANISATION"
#: src/app/main/data/nitrate.cljs:202
msgid "modals.before-leave-org.warning"
msgid "modals.before-leave-organization.warning"
msgstr "Toute équipe dont tu es le seul membre sera supprimée."
#: src/app/main/ui/workspace/nudge.cljs:59
@ -3830,22 +3830,22 @@ msgstr "Si tu supprimes ton compte, tu vas perdre tous tes projets et fichiers."
#: src/app/main/ui/settings/delete_account.cljs
#, unused
msgid "modals.delete-account.info.with-orgs"
msgid "modals.delete-account.info.with-organizations"
msgstr ""
"Cette action est irréversible. Ton compte, abonnement et organisations "
"seront supprimés immédiatement. L'accès aux projets et fichiers courants "
"sera perdu."
#: src/app/main/ui/settings/delete_account.cljs:83
msgid "modals.delete-account.owned-orgs.list-title"
msgid "modals.delete-account.owned-organizations.list-title"
msgstr "Organisations qui seront supprimées"
#: src/app/main/ui/settings/delete_account.cljs:98
msgid "modals.delete-account.owned-orgs.members-count"
msgid "modals.delete-account.owned-organizations.members-count"
msgstr "%s membres"
#: src/app/main/ui/settings/delete_account.cljs:96
msgid "modals.delete-account.owned-orgs.teams-count"
msgid "modals.delete-account.owned-organizations.teams-count"
msgstr "%s équipes"
#: src/app/main/ui/settings/delete_account.cljs:64
@ -3929,7 +3929,7 @@ msgid "modals.delete-font.title"
msgstr "Suppression de la police"
#: src/app/main/data/team.cljs:129
msgid "modals.delete-org-team-confirm.message"
msgid "modals.delete-organization-team-confirm.message"
msgstr "Vraiment supprimer cette équipe qui fait partie de l'organisation %s?"
#: src/app/main/ui/workspace/context_menu.cljs:707, src/app/main/ui/workspace/sidebar/sitemap.cljs:109
@ -4077,7 +4077,7 @@ msgid "modals.invite-restricted-members.all-blocked-title"
msgstr "Aucune invitation envoyée"
#: src/app/main/data/team.cljs:162
msgid "modals.invite-restricted-members.all-org-members-in-team"
msgid "modals.invite-restricted-members.all-organization-members-in-team"
msgstr ""
"Seuls les membres de l'organisation « %s » peuvent être invités à cette "
"équipe et ils sont tous déjà membres. Pour plus d'informations, contacter "
@ -4164,35 +4164,35 @@ msgid "modals.leave-confirm.title"
msgstr "Quitter l'équipe"
#: src/app/main/ui/dashboard/change_owner.cljs:158
msgid "modals.leave-org-and-reassign.hint"
msgid "modals.leave-organization-and-reassign.hint"
msgstr ""
"Tu es propriétaire de certaines équipes de cette organisation. Sélectionner "
"un autre membre afin de les promouvoir au rôle de propriétaire."
#: src/app/main/ui/dashboard/change_owner.cljs:161
msgid "modals.leave-org-and-reassign.hint-delete"
msgid "modals.leave-organization-and-reassign.hint-delete"
msgstr ""
"Tu es membre unique de certaines équipes. Ces équipes ainsi que leurs "
"projets et fichiers seront supprimés."
#: src/app/main/ui/dashboard/change_owner.cljs:163
msgid "modals.leave-org-and-reassign.hint-promote"
msgid "modals.leave-organization-and-reassign.hint-promote"
msgstr ""
"De plus, tu es propriétaire de certaines équipes de cette organisation. "
"Sélectionner un autre membre afin de les promouvoir au rôle de propriétaire."
#: src/app/main/data/nitrate.cljs:200, src/app/main/data/nitrate.cljs:209
msgid "modals.leave-org-confirm.accept"
msgid "modals.leave-organization-confirm.accept"
msgstr "Quitter l'organisation"
#: src/app/main/data/nitrate.cljs:208
msgid "modals.leave-org-confirm.message"
msgid "modals.leave-organization-confirm.message"
msgstr ""
"Tu perdras accès en permanence à toutes les équipes, leurs projets et "
"fichiers."
#: src/app/main/data/nitrate.cljs:207
msgid "modals.leave-org-confirm.title"
msgid "modals.leave-organization-confirm.title"
msgstr "Quitter l'organisation %s?"
#: src/app/main/ui/delete_shared.cljs:54
@ -4267,21 +4267,21 @@ msgid "modals.remove-shared-confirm.message"
msgstr "Retirer « %s » comme bibliothèque partagée"
#: src/app/main/data/nitrate.cljs:256
msgid "modals.remove-team-org.accept"
msgid "modals.remove-team-organization.accept"
msgstr "Retirer de l'organisation"
#: src/app/main/data/nitrate.cljs:254
msgid "modals.remove-team-org.info"
msgid "modals.remove-team-organization.info"
msgstr ""
"Les projets et fichiers demeureront disponibles aux membres de l'équipe, "
"mais les paramètres de l'organisation ne s'appliqueront plus."
#: src/app/main/data/nitrate.cljs:253
msgid "modals.remove-team-org.text"
msgid "modals.remove-team-organization.text"
msgstr "Vraiment retirer l'équipe « %s » de l'organisation « %s »?"
#: src/app/main/data/nitrate.cljs:252
msgid "modals.remove-team-org.title"
msgid "modals.remove-team-organization.title"
msgstr "RETIRER L'ÉQUIPE DE L'ORGANISATION"
#: src/app/main/ui/workspace/nudge.cljs:52
@ -4349,7 +4349,7 @@ msgid "nitrate.activation-success.active-until"
msgstr "L'abonnement est actif jusqu'au %s."
#: src/app/main/ui/dashboard/subscription.cljs:195, src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:68
msgid "nitrate.activation-success.create-org"
msgid "nitrate.activation-success.create-organization"
msgstr "Créer une organisation"
#: src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:63
@ -4402,7 +4402,7 @@ msgstr ""
"soit bien configuré."
#, unused
msgid "nitrate.contact-sales.downgrade-org-deleted"
msgid "nitrate.contact-sales.downgrade-organization-deleted"
msgstr "Ton organisation sera supprimée."
#, unused
@ -5707,7 +5707,7 @@ msgid "shortcuts.zoom-selected"
msgstr "Zoomer à la sélection"
#: src/app/main/ui/dashboard/subscription.cljs:191
msgid "subscription.banner.create-org-info"
msgid "subscription.banner.create-organization-info"
msgstr ""
"Crée une organisation et vois exactement comment fonctionne Enterprise. "
"Établis les règles, gère les équipes et profite d'un nouveau niveau de "

View File

@ -352,7 +352,7 @@ msgid "dashboard.copy-suffix"
msgstr "(עותק)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "יצירת ארגון חדש"
#: src/app/main/ui/dashboard/sidebar.cljs:439

View File

@ -98,7 +98,7 @@ msgid "auth.new-password"
msgstr "Inserisci una nuova password"
#: src/app/main/ui/auth/verify_token.cljs:53
msgid "auth.notifications.org-invitation-accepted"
msgid "auth.notifications.organization-invitation-accepted"
msgstr "Ora fai parte di %s"
#: src/app/main/ui/auth/recovery.cljs:36
@ -297,7 +297,7 @@ msgid "common.unpublish"
msgstr "Rimuovi pubblicazione"
#: src/app/main/data/nitrate.cljs:165
msgid "dasboard.leave-org.toast"
msgid "dashboard.leave-organization.toast"
msgstr "Non fai più parte dell'organizzazione %s."
#: src/app/main/ui/dashboard/projects.cljs:90
@ -365,25 +365,25 @@ msgid "dashboard.change-email"
msgstr "Cambia indirizzo e-mail"
#, unused
msgid "dashboard.change-org-modal.accept"
msgid "dashboard.change-organization-modal.accept"
msgstr "SPOSTA TEAM"
#, unused
msgid "dashboard.change-org-modal.choose"
msgid "dashboard.change-organization-modal.choose"
msgstr "Sposta in:"
#, unused
msgid "dashboard.change-org-modal.select"
msgid "dashboard.change-organization-modal.select"
msgstr "Seleziona un'organizzazione"
#, unused
msgid "dashboard.change-org-modal.text"
msgid "dashboard.change-organization-modal.text"
msgstr ""
"I progetti e i file rimarranno disponibili per i membri del team. Il team "
"riceverà la configurazione dalla nuova organizzazione."
#: src/app/main/data/nitrate.cljs:399, src/app/main/ui/dashboard/team_form.cljs:157
msgid "dashboard.change-org-modal.title"
msgid "dashboard.change-organization-modal.title"
msgstr "Cambia organizzazione del team"
#: src/app/main/ui/dashboard/deleted.cljs:316
@ -395,7 +395,7 @@ msgid "dashboard.copy-suffix"
msgstr "(copia)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Crea org"
#: src/app/main/ui/dashboard/sidebar.cljs:439
@ -820,7 +820,7 @@ msgid "dashboard.invite-profile"
msgstr "Invita nel team"
#: src/app/main/ui/dashboard/sidebar.cljs:671
msgid "dashboard.leave-org"
msgid "dashboard.leave-organization"
msgstr "Lascia org"
#: src/app/main/ui/dashboard/sidebar.cljs:570, src/app/main/ui/dashboard/sidebar.cljs:577, src/app/main/ui/dashboard/sidebar.cljs:582, src/app/main/ui/dashboard/team.cljs:433
@ -898,7 +898,7 @@ msgid "dashboard.no-matches-for"
msgstr "Nessuna corrispondenza trovata per \"%s\""
#: src/app/main/ui/dashboard/team_form.cljs:156
msgid "dashboard.no-org-allows-create-team.message"
msgid "dashboard.no-organization-allows-create-team.message"
msgstr "Non hai i permessi per aggiungere team a nessuna delle tue organizzazioni."
#: src/app/main/ui/dashboard/team_form.cljs:152
@ -968,7 +968,7 @@ msgid "dashboard.order-invitations-by-status"
msgstr "Ordina per stato"
#: src/app/main/data/dashboard.cljs:753
msgid "dashboard.org-deleted"
msgid "dashboard.organization-deleted"
msgstr "L'organizzazione %s è stata eliminata."
#: src/app/main/ui/settings/password.cljs:96, src/app/main/ui/settings/password.cljs:109
@ -1008,7 +1008,7 @@ msgid "dashboard.plugins.try-plugin"
msgstr "Prova il plugin: "
#: src/app/main/ui/dashboard/sidebar.cljs:798
msgid "dashboard.plus-create-new-org"
msgid "dashboard.plus-create-new-organization"
msgstr "+ Crea org"
#: src/app/main/data/dashboard.cljs:805
@ -1095,36 +1095,36 @@ msgid "dashboard.section.organizations"
msgstr "Organizzazioni"
#, unused
msgid "dashboard.select-org-modal.accept"
msgid "dashboard.select-organization-modal.accept"
msgstr "AGGIUNGI ALL'ORGANIZZAZIONE"
#, unused
msgid "dashboard.select-org-modal.choose"
msgid "dashboard.select-organization-modal.choose"
msgstr "Scegli un'organizzazione:"
#: src/app/main/ui/dashboard/team.cljs:975
msgid "dashboard.select-org-modal.external-invitations-warning"
msgid "dashboard.select-organization-modal.external-invitations-warning"
msgstr ""
"\"%s\" autorizza solo i membri dell'organizzazione ad unirsi al suo team. "
"Qualsiasi invito in sospeso a persone al di fuori dell'organizzazione verrà "
"revocato."
#: src/app/main/ui/dashboard/team.cljs:971
msgid "dashboard.select-org-modal.external-invitations-will-be-canceled"
msgid "dashboard.select-organization-modal.external-invitations-will-be-canceled"
msgstr "Gli inviti in sospeso ad utenti esterni verranno annullati."
#, unused
msgid "dashboard.select-org-modal.permission-info"
msgid "dashboard.select-organization-modal.permission-info"
msgstr ""
"Qui trovi tutte le organizzazioni dove sei autorizzato a creare o "
"aggiungere team."
#, unused
msgid "dashboard.select-org-modal.select"
msgid "dashboard.select-organization-modal.select"
msgstr "Seleziona un'organizzazione"
#: src/app/main/data/nitrate.cljs:342, src/app/main/ui/dashboard/team_form.cljs:155
msgid "dashboard.select-org-modal.title"
msgid "dashboard.select-organization-modal.title"
msgstr "Aggiungi team ad una organizzazione"
#: src/app/main/ui/settings/options.cljs:66
@ -1248,7 +1248,7 @@ msgid "dashboard.success-move-project"
msgstr "Il tuo progetto è stato spostato con successo"
#, unused
msgid "dashboard.team-belong-org"
msgid "dashboard.team-belong-organization"
msgstr "Questo team ora appartiene a %s"
#: src/app/main/ui/dashboard/team.cljs:1602
@ -1260,7 +1260,7 @@ msgid "dashboard.team-members"
msgstr "Membri del team"
#, unused
msgid "dashboard.team-no-longer-belong-org"
msgid "dashboard.team-no-longer-belong-organization"
msgstr "Questo team non appartiene più all'organizzazione %s"
#: src/app/main/ui/dashboard/team.cljs:1609
@ -1272,7 +1272,7 @@ msgid "dashboard.team-organization.add"
msgstr "Aggiungi ad una organizzazione"
#: src/app/main/data/nitrate.cljs:341, src/app/main/data/nitrate.cljs:398
msgid "dashboard.team-organization.add.no-valid-orgs"
msgid "dashboard.team-organization.add.no-valid-organizations"
msgstr ""
"Non hai i permessi per aggiungere questo team a nessuna delle tue "
"organizzazioni."
@ -1282,7 +1282,7 @@ msgid "dashboard.team-organization.change"
msgstr "Cambia organizzazione del team"
#: src/app/main/ui/dashboard/team.cljs:905
msgid "dashboard.team-organization.disabled-org-tooltip"
msgid "dashboard.team-organization.disabled-organization-tooltip"
msgstr ""
"Solo le persone all'interno della tua organizzazione possono essere "
"invitate."
@ -1350,7 +1350,7 @@ msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Gratis"
#, unused
msgid "dashboard.user-no-longer-belong-org"
msgid "dashboard.user-no-longer-belong-organization"
msgstr "Non sei più un membro dell'organizzazione %s"
#: src/app/main/ui/settings/options.cljs:109
@ -1731,17 +1731,17 @@ msgid "errors.only-creator-can-unlock"
msgstr "Solo il creatore della versione può sbloccarla"
#, unused
msgid "errors.org-leave.no-valid-teams"
msgid "errors.organization-leave.no-valid-teams"
msgstr ""
"Si è verificato un problema durante l'abbandono dell'organizzazione. "
"Riprova."
#, unused
msgid "errors.org-leave.org-owner-cannot-leave"
msgid "errors.organization-leave.organization-owner-cannot-leave"
msgstr "Il proprietario dell'organizzazione non può lasciare l'organizzazione."
#: src/app/main/ui/auth/verify_token.cljs:97
msgid "errors.org-not-found"
msgid "errors.organization-not-found"
msgstr "L'organizzazione non esiste"
#: src/app/main/ui/settings/password.cljs

View File

@ -344,7 +344,7 @@ msgid "dashboard.copy-suffix"
msgstr "(복사)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "새 조직 생성"
#: src/app/main/ui/dashboard/sidebar.cljs:439

View File

@ -98,7 +98,7 @@ msgid "auth.new-password"
msgstr "പുതിയൊരു പാസ്‌വേഡ് ചേർക്കുക"
#: src/app/main/ui/auth/verify_token.cljs:53
msgid "auth.notifications.org-invitation-accepted"
msgid "auth.notifications.organization-invitation-accepted"
msgstr "നിങ്ങളിപ്പോൾ %s-ന്റെ ഭാഗമാണ്"
#: src/app/main/ui/auth/recovery.cljs:36

View File

@ -363,7 +363,7 @@ msgid "dashboard.copy-suffix"
msgstr "(kopie)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Nieuwe org aanmaken"
#: src/app/main/ui/dashboard/sidebar.cljs:439

View File

@ -364,7 +364,7 @@ msgid "dashboard.copy-suffix"
msgstr "(копия)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Создать новую организацию"
#: src/app/main/ui/dashboard/sidebar.cljs:439

View File

@ -96,7 +96,7 @@ msgid "auth.new-password"
msgstr "Skriv ett nytt lösenord"
#: src/app/main/ui/auth/verify_token.cljs:53
msgid "auth.notifications.org-invitation-accepted"
msgid "auth.notifications.organization-invitation-accepted"
msgstr "Du är nu med i %s"
#: src/app/main/ui/auth/recovery.cljs:36
@ -293,7 +293,7 @@ msgid "common.unpublish"
msgstr "Avpublicera"
#: src/app/main/data/nitrate.cljs:165
msgid "dasboard.leave-org.toast"
msgid "dashboard.leave-organization.toast"
msgstr "Du är inte längre med i organisationen %s."
#: src/app/main/ui/dashboard/projects.cljs:90
@ -363,25 +363,25 @@ msgid "dashboard.change-email"
msgstr "Ändra e-post"
#, unused
msgid "dashboard.change-org-modal.accept"
msgid "dashboard.change-organization-modal.accept"
msgstr "FLYTTA TEAM"
#, unused
msgid "dashboard.change-org-modal.choose"
msgid "dashboard.change-organization-modal.choose"
msgstr "Flytta till:"
#, unused
msgid "dashboard.change-org-modal.select"
msgid "dashboard.change-organization-modal.select"
msgstr "Välj en organisation"
#, unused
msgid "dashboard.change-org-modal.text"
msgid "dashboard.change-organization-modal.text"
msgstr ""
"Projekt och filer kommer att förbli tillgängliga för teammedlemmar. Teamet "
"kommer att hämta konfigurationen från den nya organisationen."
#: src/app/main/data/nitrate.cljs:399, src/app/main/ui/dashboard/team_form.cljs:157
msgid "dashboard.change-org-modal.title"
msgid "dashboard.change-organization-modal.title"
msgstr "Ändra teamets organisation"
#: src/app/main/ui/dashboard/deleted.cljs:316
@ -393,7 +393,7 @@ msgid "dashboard.copy-suffix"
msgstr "(kopia)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Skapa org"
#: src/app/main/ui/dashboard/sidebar.cljs:439
@ -812,7 +812,7 @@ msgid "dashboard.invite-profile"
msgstr "Bjud in personer"
#: src/app/main/ui/dashboard/sidebar.cljs:671
msgid "dashboard.leave-org"
msgid "dashboard.leave-organization"
msgstr "Lämna org"
#: src/app/main/ui/dashboard/sidebar.cljs:570, src/app/main/ui/dashboard/sidebar.cljs:577, src/app/main/ui/dashboard/sidebar.cljs:582, src/app/main/ui/dashboard/team.cljs:433
@ -888,7 +888,7 @@ msgid "dashboard.no-matches-for"
msgstr "Inga träffar hittades för ”%s”"
#: src/app/main/ui/dashboard/team_form.cljs:156
msgid "dashboard.no-org-allows-create-team.message"
msgid "dashboard.no-organization-allows-create-team.message"
msgstr "Du har inte behörighet att lägga till team i någon av dina organisationer."
#: src/app/main/ui/dashboard/team_form.cljs:152
@ -958,7 +958,7 @@ msgid "dashboard.order-invitations-by-status"
msgstr "Sortera efter status"
#: src/app/main/data/dashboard.cljs:753
msgid "dashboard.org-deleted"
msgid "dashboard.organization-deleted"
msgstr "Organisationen %s har tagits bort."
#: src/app/main/ui/settings/password.cljs:96, src/app/main/ui/settings/password.cljs:109
@ -998,7 +998,7 @@ msgid "dashboard.plugins.try-plugin"
msgstr "Testa plugin: "
#: src/app/main/ui/dashboard/sidebar.cljs:798
msgid "dashboard.plus-create-new-org"
msgid "dashboard.plus-create-new-organization"
msgstr "+ Skapa org"
#: src/app/main/data/dashboard.cljs:805
@ -1083,36 +1083,36 @@ msgid "dashboard.section.organizations"
msgstr "Organisationer"
#, unused
msgid "dashboard.select-org-modal.accept"
msgid "dashboard.select-organization-modal.accept"
msgstr "LÄGG TILL I ORGANISATION"
#, unused
msgid "dashboard.select-org-modal.choose"
msgid "dashboard.select-organization-modal.choose"
msgstr "Välj en organisation:"
#: src/app/main/ui/dashboard/team.cljs:975
msgid "dashboard.select-org-modal.external-invitations-warning"
msgid "dashboard.select-organization-modal.external-invitations-warning"
msgstr ""
"\"%s\" tillåter endast organisationsmedlemmar att gå med i dess team. Alla "
"väntande inbjudningar till personer utanför organisationen kommer att "
"återkallas."
#: src/app/main/ui/dashboard/team.cljs:971
msgid "dashboard.select-org-modal.external-invitations-will-be-canceled"
msgid "dashboard.select-organization-modal.external-invitations-will-be-canceled"
msgstr "Väntande inbjudningar till externa användare kommer att avbrytas."
#, unused
msgid "dashboard.select-org-modal.permission-info"
msgid "dashboard.select-organization-modal.permission-info"
msgstr ""
"Här hittar du alla dina organisationer där du har rätt att skapa eller "
"lägga till team."
#, unused
msgid "dashboard.select-org-modal.select"
msgid "dashboard.select-organization-modal.select"
msgstr "Välj en organisation"
#: src/app/main/data/nitrate.cljs:342, src/app/main/ui/dashboard/team_form.cljs:155
msgid "dashboard.select-org-modal.title"
msgid "dashboard.select-organization-modal.title"
msgstr "Lägg till team i en organisation"
#: src/app/main/ui/settings/options.cljs:66
@ -1236,7 +1236,7 @@ msgid "dashboard.success-move-project"
msgstr "Ditt projekt har flyttats"
#, unused
msgid "dashboard.team-belong-org"
msgid "dashboard.team-belong-organization"
msgstr "Detta team tillhör nu %s"
#: src/app/main/ui/dashboard/team.cljs:1602
@ -1248,7 +1248,7 @@ msgid "dashboard.team-members"
msgstr "Teammedlemmar"
#, unused
msgid "dashboard.team-no-longer-belong-org"
msgid "dashboard.team-no-longer-belong-organization"
msgstr "Detta team tillhör inte längre organisation %s"
#: src/app/main/ui/dashboard/team.cljs:1609
@ -1260,7 +1260,7 @@ msgid "dashboard.team-organization.add"
msgstr "Lägg till i en organisation"
#: src/app/main/data/nitrate.cljs:341, src/app/main/data/nitrate.cljs:398
msgid "dashboard.team-organization.add.no-valid-orgs"
msgid "dashboard.team-organization.add.no-valid-organizations"
msgstr ""
"Du har inte behörighet att lägga till det här teamet i någon av dina "
"organisationer."
@ -1270,7 +1270,7 @@ msgid "dashboard.team-organization.change"
msgstr "Ändra teamorganisation"
#: src/app/main/ui/dashboard/team.cljs:905
msgid "dashboard.team-organization.disabled-org-tooltip"
msgid "dashboard.team-organization.disabled-organization-tooltip"
msgstr "Endast personer inom din organisation kan bjudas in."
#: src/app/main/ui/dashboard/team.cljs:1639
@ -1336,7 +1336,7 @@ msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Gratis Penpot"
#, unused
msgid "dashboard.user-no-longer-belong-org"
msgid "dashboard.user-no-longer-belong-organization"
msgstr "Du är inte längre medlem i organisation %s"
#: src/app/main/ui/settings/options.cljs:109
@ -1712,15 +1712,15 @@ msgid "errors.only-creator-can-unlock"
msgstr "Endast skaparen av versionen kan låsa upp den"
#, unused
msgid "errors.org-leave.no-valid-teams"
msgid "errors.organization-leave.no-valid-teams"
msgstr "Det uppstod ett fel när du skulle lämna organisationen. Försök igen."
#, unused
msgid "errors.org-leave.org-owner-cannot-leave"
msgid "errors.organization-leave.organization-owner-cannot-leave"
msgstr "Organisationsägaren kan inte lämna organisationen."
#: src/app/main/ui/auth/verify_token.cljs:97
msgid "errors.org-not-found"
msgid "errors.organization-not-found"
msgstr "Den organisationen finns inte"
#: src/app/main/ui/settings/password.cljs
@ -3731,17 +3731,17 @@ msgid "modals.add-shared-confirm.message"
msgstr "Lägg till \"%s\" som delat bibliotek"
#: src/app/main/data/nitrate.cljs:199
msgid "modals.before-leave-org.message"
msgid "modals.before-leave-organization.message"
msgstr ""
"Du är den enda medlemmen i vissa av teamen. Dessa team kommer att raderas "
"tillsammans med deras projekt och filer."
#: src/app/main/data/nitrate.cljs:198, src/app/main/ui/dashboard/change_owner.cljs:151
msgid "modals.before-leave-org.title"
msgid "modals.before-leave-organization.title"
msgstr "INNAN DU LÄMNAR ORGANISATIONEN"
#: src/app/main/data/nitrate.cljs:202
msgid "modals.before-leave-org.warning"
msgid "modals.before-leave-organization.warning"
msgstr "Alla team där du är den enda medlemmen kommer att raderas."
#: src/app/main/ui/workspace/nudge.cljs:59
@ -3801,22 +3801,22 @@ msgstr "Genom att ta bort ditt konto förlorar du alla dina projekt och arkiv."
#: src/app/main/ui/settings/delete_account.cljs
#, unused
msgid "modals.delete-account.info.with-orgs"
msgid "modals.delete-account.info.with-organizations"
msgstr ""
"Denna åtgärd kan inte ångras. Ditt konto, din prenumeration och dina "
"organisationer kommer att raderas omedelbart. Du kommer också att förlora "
"åtkomsten till dina nuvarande projekt och filer."
#: src/app/main/ui/settings/delete_account.cljs:83
msgid "modals.delete-account.owned-orgs.list-title"
msgid "modals.delete-account.owned-organizations.list-title"
msgstr "Organisationer som kommer att raderas"
#: src/app/main/ui/settings/delete_account.cljs:98
msgid "modals.delete-account.owned-orgs.members-count"
msgid "modals.delete-account.owned-organizations.members-count"
msgstr "%s medlemmar"
#: src/app/main/ui/settings/delete_account.cljs:96
msgid "modals.delete-account.owned-orgs.teams-count"
msgid "modals.delete-account.owned-organizations.teams-count"
msgstr "%s team"
#: src/app/main/ui/settings/delete_account.cljs:64
@ -3900,7 +3900,7 @@ msgid "modals.delete-font.title"
msgstr "Tar bort typsnitt"
#: src/app/main/data/team.cljs:129
msgid "modals.delete-org-team-confirm.message"
msgid "modals.delete-organization-team-confirm.message"
msgstr ""
"Är du säker på att du vill ta bort det här teamet som är en del av "
"organisation %s?"
@ -4048,7 +4048,7 @@ msgid "modals.invite-restricted-members.all-blocked-title"
msgstr "Inga inbjudningar skickades"
#: src/app/main/data/team.cljs:162
msgid "modals.invite-restricted-members.all-org-members-in-team"
msgid "modals.invite-restricted-members.all-organization-members-in-team"
msgstr ""
"Endast medlemmar i organisationen \"%s\" kan bjudas in till det här teamet, "
"och de är alla redan teammedlemmar. Om du behöver mer information, kontakta "
@ -4135,35 +4135,35 @@ msgid "modals.leave-confirm.title"
msgstr "Lämnar teamet"
#: src/app/main/ui/dashboard/change_owner.cljs:158
msgid "modals.leave-org-and-reassign.hint"
msgid "modals.leave-organization-and-reassign.hint"
msgstr ""
"Du är ägare till teamen i vissa organisationer. Vänligen befordra en annan "
"medlem till att bli ägare."
#: src/app/main/ui/dashboard/change_owner.cljs:161
msgid "modals.leave-org-and-reassign.hint-delete"
msgid "modals.leave-organization-and-reassign.hint-delete"
msgstr ""
"Du är den enda medlemmen i vissa av teamen. Dessa team kommer att raderas "
"tillsammans med deras projekt och filer."
#: src/app/main/ui/dashboard/change_owner.cljs:163
msgid "modals.leave-org-and-reassign.hint-promote"
msgid "modals.leave-organization-and-reassign.hint-promote"
msgstr ""
"Du är också ägare till vissa organisationers team. Vänligen befordra en "
"annan medlem till att bli ägare."
#: src/app/main/data/nitrate.cljs:200, src/app/main/data/nitrate.cljs:209
msgid "modals.leave-org-confirm.accept"
msgid "modals.leave-organization-confirm.accept"
msgstr "Lämna organisation"
#: src/app/main/data/nitrate.cljs:208
msgid "modals.leave-org-confirm.message"
msgid "modals.leave-organization-confirm.message"
msgstr ""
"Du kommer permanent att förlora åtkomsten till alla team, projekt och filer "
"i den."
#: src/app/main/data/nitrate.cljs:207
msgid "modals.leave-org-confirm.title"
msgid "modals.leave-organization-confirm.title"
msgstr "Lämnar du organisation %s?"
#: src/app/main/ui/delete_shared.cljs:54
@ -4239,21 +4239,21 @@ msgid "modals.remove-shared-confirm.message"
msgstr "Ta bort \"%s\" som delat bibliotek"
#: src/app/main/data/nitrate.cljs:256
msgid "modals.remove-team-org.accept"
msgid "modals.remove-team-organization.accept"
msgstr "Ta bort från organisationen"
#: src/app/main/data/nitrate.cljs:254
msgid "modals.remove-team-org.info"
msgid "modals.remove-team-organization.info"
msgstr ""
"Projekt och filer kommer att förbli tillgängliga för teammedlemmar, men "
"organisationens inställningar kommer inte längre att gälla."
#: src/app/main/data/nitrate.cljs:253
msgid "modals.remove-team-org.text"
msgid "modals.remove-team-organization.text"
msgstr "Är du säker på att du vill ta bort teamet '%s' från organisationen '%s'?"
#: src/app/main/data/nitrate.cljs:252
msgid "modals.remove-team-org.title"
msgid "modals.remove-team-organization.title"
msgstr "TA BORT TEAMET FRÅN ORGANISATIONEN"
#: src/app/main/ui/workspace/nudge.cljs:52
@ -4321,7 +4321,7 @@ msgid "nitrate.activation-success.active-until"
msgstr "Ditt abonnemang är aktivt till %s."
#: src/app/main/ui/dashboard/subscription.cljs:195, src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:68
msgid "nitrate.activation-success.create-org"
msgid "nitrate.activation-success.create-organization"
msgstr "Skapa organisation"
#: src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:63
@ -4374,7 +4374,7 @@ msgstr ""
"korrekt konfigurerat."
#, unused
msgid "nitrate.contact-sales.downgrade-org-deleted"
msgid "nitrate.contact-sales.downgrade-organization-deleted"
msgstr "Din organisation kommer att raderas."
#, unused
@ -5671,7 +5671,7 @@ msgid "shortcuts.zoom-selected"
msgstr "Zooma in markerad"
#: src/app/main/ui/dashboard/subscription.cljs:191
msgid "subscription.banner.create-org-info"
msgid "subscription.banner.create-organization-info"
msgstr ""
"Skapa en organisation och se exakt hur Enterprise fungerar. Sätt regler, "
"hantera dina team och upptäck en ny nivå av kontroll över ditt "

View File

@ -98,7 +98,7 @@ msgid "auth.new-password"
msgstr "Yeni bir parola gir"
#: src/app/main/ui/auth/verify_token.cljs:53
msgid "auth.notifications.org-invitation-accepted"
msgid "auth.notifications.organization-invitation-accepted"
msgstr "Artık %s organizasyonunun bir parçasısınız"
#: src/app/main/ui/auth/recovery.cljs:36
@ -294,7 +294,7 @@ msgid "common.unpublish"
msgstr "Yayından kaldır"
#: src/app/main/data/nitrate.cljs:165
msgid "dasboard.leave-org.toast"
msgid "dashboard.leave-organization.toast"
msgstr "Artık %s organizasyonunun bir parçası değilsiniz."
#: src/app/main/ui/dashboard/projects.cljs:90
@ -364,25 +364,25 @@ msgid "dashboard.change-email"
msgstr "E-posta adresini değiştir"
#, unused
msgid "dashboard.change-org-modal.accept"
msgid "dashboard.change-organization-modal.accept"
msgstr "TAKIMI TAŞI"
#, unused
msgid "dashboard.change-org-modal.choose"
msgid "dashboard.change-organization-modal.choose"
msgstr "Şuraya taşı:"
#, unused
msgid "dashboard.change-org-modal.select"
msgid "dashboard.change-organization-modal.select"
msgstr "Organizasyon seç"
#, unused
msgid "dashboard.change-org-modal.text"
msgid "dashboard.change-organization-modal.text"
msgstr ""
"Projeler ve dosyalar takım üyeleri tarafından erişilebilir olmaya devam "
"edecek. Takım, yeni organizasyondan yapılandırmayı alacak."
#: src/app/main/data/nitrate.cljs:399, src/app/main/ui/dashboard/team_form.cljs:157
msgid "dashboard.change-org-modal.title"
msgid "dashboard.change-organization-modal.title"
msgstr "Takımın organizasyonunu değiştir"
#: src/app/main/ui/dashboard/deleted.cljs:316
@ -394,7 +394,7 @@ msgid "dashboard.copy-suffix"
msgstr "(kopya)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Yeni organizasyon oluştur"
#: src/app/main/ui/dashboard/sidebar.cljs:439
@ -816,7 +816,7 @@ msgid "dashboard.invite-profile"
msgstr "İnsanları davet et"
#: src/app/main/ui/dashboard/sidebar.cljs:671
msgid "dashboard.leave-org"
msgid "dashboard.leave-organization"
msgstr "Organizasyondan ayrıl"
#: src/app/main/ui/dashboard/sidebar.cljs:570, src/app/main/ui/dashboard/sidebar.cljs:577, src/app/main/ui/dashboard/sidebar.cljs:582, src/app/main/ui/dashboard/team.cljs:433
@ -894,7 +894,7 @@ msgid "dashboard.no-matches-for"
msgstr "\"%s\" için sonuç bulunamadı"
#: src/app/main/ui/dashboard/team_form.cljs:156
msgid "dashboard.no-org-allows-create-team.message"
msgid "dashboard.no-organization-allows-create-team.message"
msgstr "Hiçbir organizasyonunuza takım ekleme izniniz yok."
#: src/app/main/ui/dashboard/team_form.cljs:152
@ -964,7 +964,7 @@ msgid "dashboard.order-invitations-by-status"
msgstr "Duruma göre sırala"
#: src/app/main/data/dashboard.cljs:753
msgid "dashboard.org-deleted"
msgid "dashboard.organization-deleted"
msgstr "%s organizasyonu silindi."
#: src/app/main/ui/settings/password.cljs:96, src/app/main/ui/settings/password.cljs:109
@ -1004,7 +1004,7 @@ msgid "dashboard.plugins.try-plugin"
msgstr "Eklentiyi deneyin: "
#: src/app/main/ui/dashboard/sidebar.cljs:798
msgid "dashboard.plus-create-new-org"
msgid "dashboard.plus-create-new-organization"
msgstr "+ Organizasyon oluştur"
#: src/app/main/data/dashboard.cljs:805
@ -1091,36 +1091,36 @@ msgid "dashboard.section.organizations"
msgstr "Organizasyonlar"
#, unused
msgid "dashboard.select-org-modal.accept"
msgid "dashboard.select-organization-modal.accept"
msgstr "ORGANİZASYONA EKLE"
#, unused
msgid "dashboard.select-org-modal.choose"
msgid "dashboard.select-organization-modal.choose"
msgstr "Organizasyon seç:"
#: src/app/main/ui/dashboard/team.cljs:975
msgid "dashboard.select-org-modal.external-invitations-warning"
msgid "dashboard.select-organization-modal.external-invitations-warning"
msgstr ""
"\"%s\", yalnızca organizasyon üyelerinin takımlarına katılmasına izin "
"veriyor. Organizasyon dışındaki kişilere gönderilmiş bekleyen davetler "
"iptal edilecektir."
#: src/app/main/ui/dashboard/team.cljs:971
msgid "dashboard.select-org-modal.external-invitations-will-be-canceled"
msgid "dashboard.select-organization-modal.external-invitations-will-be-canceled"
msgstr "Dış kullanıcılara gönderilen bekleyen davetler iptal edilecektir."
#, unused
msgid "dashboard.select-org-modal.permission-info"
msgid "dashboard.select-organization-modal.permission-info"
msgstr ""
"Burada, takım oluşturmanıza veya eklemenize izin verilen tüm "
"organizasyonlarınızı bulabilirsiniz."
#, unused
msgid "dashboard.select-org-modal.select"
msgid "dashboard.select-organization-modal.select"
msgstr "Organizasyon seç"
#: src/app/main/data/nitrate.cljs:342, src/app/main/ui/dashboard/team_form.cljs:155
msgid "dashboard.select-org-modal.title"
msgid "dashboard.select-organization-modal.title"
msgstr "Takımı bir organizasyona ekle"
#: src/app/main/ui/settings/options.cljs:66
@ -1244,7 +1244,7 @@ msgid "dashboard.success-move-project"
msgstr "Projen başarıyla taşındı"
#, unused
msgid "dashboard.team-belong-org"
msgid "dashboard.team-belong-organization"
msgstr "Bu takım artık %s organizasyonuna ait"
#: src/app/main/ui/dashboard/team.cljs:1602
@ -1256,7 +1256,7 @@ msgid "dashboard.team-members"
msgstr "Takım üyeleri"
#, unused
msgid "dashboard.team-no-longer-belong-org"
msgid "dashboard.team-no-longer-belong-organization"
msgstr "Bu takım artık %s organizasyonuna ait değil"
#: src/app/main/ui/dashboard/team.cljs:1609
@ -1268,7 +1268,7 @@ msgid "dashboard.team-organization.add"
msgstr "Organizasyona ekle"
#: src/app/main/data/nitrate.cljs:341, src/app/main/data/nitrate.cljs:398
msgid "dashboard.team-organization.add.no-valid-orgs"
msgid "dashboard.team-organization.add.no-valid-organizations"
msgstr "Bu takımı hiçbir organizasyonunuza ekleme izniniz yok."
#: src/app/main/ui/dashboard/team.cljs:1632
@ -1276,7 +1276,7 @@ msgid "dashboard.team-organization.change"
msgstr "Takım organizasyonunu değiştir"
#: src/app/main/ui/dashboard/team.cljs:905
msgid "dashboard.team-organization.disabled-org-tooltip"
msgid "dashboard.team-organization.disabled-organization-tooltip"
msgstr "Yalnızca organizasyonunuzdaki kişiler davet edilebilir."
#: src/app/main/ui/dashboard/team.cljs:1639
@ -1342,7 +1342,7 @@ msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Ücretsiz"
#, unused
msgid "dashboard.user-no-longer-belong-org"
msgid "dashboard.user-no-longer-belong-organization"
msgstr "Artık %s organizasyonunun üyesi değilsiniz"
#: src/app/main/ui/settings/options.cljs:109
@ -1719,15 +1719,15 @@ msgid "errors.only-creator-can-unlock"
msgstr "Yalnızca sürüm oluşturucu onun kilidini açabilir"
#, unused
msgid "errors.org-leave.no-valid-teams"
msgid "errors.organization-leave.no-valid-teams"
msgstr "Organizasyondan ayrılma sırasında bir hata oluştu. Lütfen tekrar deneyin."
#, unused
msgid "errors.org-leave.org-owner-cannot-leave"
msgid "errors.organization-leave.organization-owner-cannot-leave"
msgstr "Organizasyon sahibi organizasyondan ayrılamaz."
#: src/app/main/ui/auth/verify_token.cljs:97
msgid "errors.org-not-found"
msgid "errors.organization-not-found"
msgstr "Bu organizasyon yok"
#: src/app/main/ui/settings/password.cljs
@ -3737,17 +3737,17 @@ msgid "modals.add-shared-confirm.message"
msgstr "Paylaşılmış Kütüphane olarak “%s” Ekle"
#: src/app/main/data/nitrate.cljs:199
msgid "modals.before-leave-org.message"
msgid "modals.before-leave-organization.message"
msgstr ""
"Bazı takımların tek üyesi sizsiniz. Bu takımlar, projeleriyle ve "
"dosyalarıyla birlikte silinecek."
#: src/app/main/data/nitrate.cljs:198, src/app/main/ui/dashboard/change_owner.cljs:151
msgid "modals.before-leave-org.title"
msgid "modals.before-leave-organization.title"
msgstr "ORGANİZASYONDAN AYRILMADAN ÖNCE"
#: src/app/main/data/nitrate.cljs:202
msgid "modals.before-leave-org.warning"
msgid "modals.before-leave-organization.warning"
msgstr "Tek üyesi olduğunuz tüm takımlar silinecektir."
#: src/app/main/ui/workspace/nudge.cljs:59
@ -3807,22 +3807,22 @@ msgstr "Hesabını silerek tüm projelerini ve arşivlerini kaybedeceksin."
#: src/app/main/ui/settings/delete_account.cljs
#, unused
msgid "modals.delete-account.info.with-orgs"
msgid "modals.delete-account.info.with-organizations"
msgstr ""
"Bu işlem geri alınamaz. Hesabınız, aboneliğiniz ve organizasyonlarınız "
"hemen silinecektir. Ayrıca var olan projelerinize ve dosyalarınıza "
"erişiminizi de kaybedeceksiniz."
#: src/app/main/ui/settings/delete_account.cljs:83
msgid "modals.delete-account.owned-orgs.list-title"
msgid "modals.delete-account.owned-organizations.list-title"
msgstr "Silinecek organizasyonlar"
#: src/app/main/ui/settings/delete_account.cljs:98
msgid "modals.delete-account.owned-orgs.members-count"
msgid "modals.delete-account.owned-organizations.members-count"
msgstr "%s üye"
#: src/app/main/ui/settings/delete_account.cljs:96
msgid "modals.delete-account.owned-orgs.teams-count"
msgid "modals.delete-account.owned-organizations.teams-count"
msgstr "%s takım"
#: src/app/main/ui/settings/delete_account.cljs:64
@ -3906,7 +3906,7 @@ msgid "modals.delete-font.title"
msgstr "Yazı tipini sil"
#: src/app/main/data/team.cljs:129
msgid "modals.delete-org-team-confirm.message"
msgid "modals.delete-organization-team-confirm.message"
msgstr ""
"%s organizasyonunun parçası olan bu takımı silmek istediğinizden emin "
"misiniz?"
@ -4054,7 +4054,7 @@ msgid "modals.invite-restricted-members.all-blocked-title"
msgstr "Davet gönderilmedi"
#: src/app/main/data/team.cljs:162
msgid "modals.invite-restricted-members.all-org-members-in-team"
msgid "modals.invite-restricted-members.all-organization-members-in-team"
msgstr ""
"Bu takıma yalnızca \"%s\" organizasyonunun üyeleri davet edilebilir ve bu "
"kişiler zaten takımın üyeleridir. Daha fazla bilgiye ihtiyacınız varsa, "
@ -4141,35 +4141,35 @@ msgid "modals.leave-confirm.title"
msgstr "Takımdan ayrıl"
#: src/app/main/ui/dashboard/change_owner.cljs:158
msgid "modals.leave-org-and-reassign.hint"
msgid "modals.leave-organization-and-reassign.hint"
msgstr ""
"Bazı organizasyon takımlarının sahibi sizsiniz. Lütfen başka bir üyeyi "
"sahibi olarak atayın."
#: src/app/main/ui/dashboard/change_owner.cljs:161
msgid "modals.leave-org-and-reassign.hint-delete"
msgid "modals.leave-organization-and-reassign.hint-delete"
msgstr ""
"Bazı takımların tek üyesi sizsiniz. Bu takımlar, projeleriyle ve "
"dosyalarıyla birlikte silinecek."
#: src/app/main/ui/dashboard/change_owner.cljs:163
msgid "modals.leave-org-and-reassign.hint-promote"
msgid "modals.leave-organization-and-reassign.hint-promote"
msgstr ""
"Ayrıca, bazı organizasyon takımlarının sahibi sizsiniz. Lütfen başka bir "
"üyeyi sahibi olarak atayın."
#: src/app/main/data/nitrate.cljs:200, src/app/main/data/nitrate.cljs:209
msgid "modals.leave-org-confirm.accept"
msgid "modals.leave-organization-confirm.accept"
msgstr "Organizasyondan ayrıl"
#: src/app/main/data/nitrate.cljs:208
msgid "modals.leave-org-confirm.message"
msgid "modals.leave-organization-confirm.message"
msgstr ""
"İçindeki tüm takımlara, projelere ve dosyalara erişiminizi kalıcı olarak "
"kaybedeceksiniz."
#: src/app/main/data/nitrate.cljs:207
msgid "modals.leave-org-confirm.title"
msgid "modals.leave-organization-confirm.title"
msgstr "%s organizasyonundan ayrılıyor musunuz?"
#: src/app/main/ui/delete_shared.cljs:54
@ -4244,21 +4244,21 @@ msgid "modals.remove-shared-confirm.message"
msgstr "“%s” Paylaşılan Kütüphanesini Kaldır"
#: src/app/main/data/nitrate.cljs:256
msgid "modals.remove-team-org.accept"
msgid "modals.remove-team-organization.accept"
msgstr "Organizasyondan kaldır"
#: src/app/main/data/nitrate.cljs:254
msgid "modals.remove-team-org.info"
msgid "modals.remove-team-organization.info"
msgstr ""
"Projeler ve dosyalar takım üyeleri tarafından kullanılabilir olmaya devam "
"edecek, ancak organizasyonun ayarları artık geçerli olmayacaktır."
#: src/app/main/data/nitrate.cljs:253
msgid "modals.remove-team-org.text"
msgid "modals.remove-team-organization.text"
msgstr "'%s' takımını '%s' organizasyonundan kaldırmak istediğinizden emin misiniz?"
#: src/app/main/data/nitrate.cljs:252
msgid "modals.remove-team-org.title"
msgid "modals.remove-team-organization.title"
msgstr "TAKIMI ORGANİZASYONDAN KALDIR"
#: src/app/main/ui/workspace/nudge.cljs:52
@ -4326,7 +4326,7 @@ msgid "nitrate.activation-success.active-until"
msgstr "Planınız %s tarihine kadar etkindir."
#: src/app/main/ui/dashboard/subscription.cljs:195, src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:68
msgid "nitrate.activation-success.create-org"
msgid "nitrate.activation-success.create-organization"
msgstr "Organizasyon oluştur"
#: src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs:63
@ -4379,7 +4379,7 @@ msgstr ""
"ayarlandığından emin olacağız."
#, unused
msgid "nitrate.contact-sales.downgrade-org-deleted"
msgid "nitrate.contact-sales.downgrade-organization-deleted"
msgstr "Organizasyonunuz silinecek."
#, unused
@ -5673,7 +5673,7 @@ msgid "shortcuts.zoom-selected"
msgstr "Seçilene yakınlaştır"
#: src/app/main/ui/dashboard/subscription.cljs:191
msgid "subscription.banner.create-org-info"
msgid "subscription.banner.create-organization-info"
msgstr ""
"Bir organizasyon oluşturun ve Kurumsal planın tam olarak nasıl çalıştığını "
"görün. Kurallar belirleyin, takımlarınızı yönetin ve tasarım iş akışınız "

View File

@ -368,7 +368,7 @@ msgid "dashboard.copy-suffix"
msgstr "(копія)"
#: src/app/main/ui/dashboard/sidebar.cljs:371
msgid "dashboard.create-new-org"
msgid "dashboard.create-new-organization"
msgstr "Створити організацію"
#: src/app/main/ui/dashboard/sidebar.cljs:439