mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
@@ -31,7 +31,8 @@
|
|||||||
- Fix consistency issues on how font variants are visualized [Taiga #11499](https://tree.taiga.io/project/penpot/us/11499)
|
- Fix consistency issues on how font variants are visualized [Taiga #11499](https://tree.taiga.io/project/penpot/us/11499)
|
||||||
- Fix parsing rx and ry SVG values for rect radius [Taiga #11861](https://tree.taiga.io/project/penpot/issue/11861)
|
- Fix parsing rx and ry SVG values for rect radius [Taiga #11861](https://tree.taiga.io/project/penpot/issue/11861)
|
||||||
|
|
||||||
## 2.9.0 (Unreleased)
|
|
||||||
|
## 2.9.0
|
||||||
|
|
||||||
### :rocket: Epics and highlights
|
### :rocket: Epics and highlights
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,15 @@
|
|||||||
opts (cond-> opts
|
opts (cond-> opts
|
||||||
(::order-by opts) (assoc :order-by (::order-by opts))
|
(::order-by opts) (assoc :order-by (::order-by opts))
|
||||||
(::columns opts) (assoc :columns (::columns opts))
|
(::columns opts) (assoc :columns (::columns opts))
|
||||||
(::for-update opts) (assoc :suffix "FOR UPDATE")
|
|
||||||
(::for-share opts) (assoc :suffix "FOR SHARE"))]
|
(or (::db/for-update opts)
|
||||||
|
(::for-update opts))
|
||||||
|
(assoc :suffix "FOR UPDATE")
|
||||||
|
|
||||||
|
(or (::db/for-share opts)
|
||||||
|
(::for-share opts))
|
||||||
|
(assoc :suffix "FOR SHARE"))]
|
||||||
|
|
||||||
(sql/for-query table where-params opts))))
|
(sql/for-query table where-params opts))))
|
||||||
|
|
||||||
(defn update
|
(defn update
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
[app.http.awsns :as-alias awsns]
|
[app.http.awsns :as-alias awsns]
|
||||||
[app.http.debug :as-alias debug]
|
[app.http.debug :as-alias debug]
|
||||||
[app.http.errors :as errors]
|
[app.http.errors :as errors]
|
||||||
|
[app.http.management :as mgmt]
|
||||||
[app.http.middleware :as mw]
|
[app.http.middleware :as mw]
|
||||||
[app.http.session :as session]
|
[app.http.session :as session]
|
||||||
[app.http.websocket :as-alias ws]
|
[app.http.websocket :as-alias ws]
|
||||||
@@ -143,6 +144,7 @@
|
|||||||
[::debug/routes schema:routes]
|
[::debug/routes schema:routes]
|
||||||
[::mtx/routes schema:routes]
|
[::mtx/routes schema:routes]
|
||||||
[::awsns/routes schema:routes]
|
[::awsns/routes schema:routes]
|
||||||
|
[::mgmt/routes schema:routes]
|
||||||
::session/manager
|
::session/manager
|
||||||
::setup/props
|
::setup/props
|
||||||
::db/pool])
|
::db/pool])
|
||||||
@@ -170,6 +172,9 @@
|
|||||||
["/webhooks"
|
["/webhooks"
|
||||||
(::awsns/routes cfg)]
|
(::awsns/routes cfg)]
|
||||||
|
|
||||||
|
["/management"
|
||||||
|
(::mgmt/routes cfg)]
|
||||||
|
|
||||||
(::ws/routes cfg)
|
(::ws/routes cfg)
|
||||||
|
|
||||||
["/api" {:middleware [[mw/cors]]}
|
["/api" {:middleware [[mw/cors]]}
|
||||||
|
|||||||
234
backend/src/app/http/management.clj
Normal file
234
backend/src/app/http/management.clj
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
;; 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.http.management
|
||||||
|
"Internal mangement HTTP API"
|
||||||
|
(:require
|
||||||
|
[app.common.logging :as l]
|
||||||
|
[app.common.schema :as sm]
|
||||||
|
[app.common.schema.generators :as sg]
|
||||||
|
[app.common.time :as ct]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.main :as-alias main]
|
||||||
|
[app.rpc.commands.profile :as cmd.profile]
|
||||||
|
[app.setup :as-alias setup]
|
||||||
|
[app.tokens :as tokens]
|
||||||
|
[app.worker :as-alias wrk]
|
||||||
|
[integrant.core :as ig]
|
||||||
|
[yetti.response :as-alias yres]))
|
||||||
|
|
||||||
|
;; ---- ROUTES
|
||||||
|
|
||||||
|
(declare ^:private authenticate)
|
||||||
|
(declare ^:private get-customer)
|
||||||
|
(declare ^:private update-customer)
|
||||||
|
|
||||||
|
(defmethod ig/assert-key ::routes
|
||||||
|
[_ params]
|
||||||
|
(assert (db/pool? (::db/pool params)) "expect valid database pool"))
|
||||||
|
|
||||||
|
(def ^:private default-system
|
||||||
|
{:name ::default-system
|
||||||
|
:compile
|
||||||
|
(fn [_ _]
|
||||||
|
(fn [handler cfg]
|
||||||
|
(fn [request]
|
||||||
|
(handler cfg request))))})
|
||||||
|
|
||||||
|
(def ^:private transaction
|
||||||
|
{:name ::transaction
|
||||||
|
:compile
|
||||||
|
(fn [data _]
|
||||||
|
(when (:transaction data)
|
||||||
|
(fn [handler]
|
||||||
|
(fn [cfg request]
|
||||||
|
(db/tx-run! cfg handler request)))))})
|
||||||
|
|
||||||
|
(defmethod ig/init-key ::routes
|
||||||
|
[_ cfg]
|
||||||
|
["" {:middleware [[default-system cfg]
|
||||||
|
[transaction]]}
|
||||||
|
["/authenticate"
|
||||||
|
{:handler authenticate
|
||||||
|
:allowed-methods #{:post}}]
|
||||||
|
|
||||||
|
["/get-customer"
|
||||||
|
{:handler get-customer
|
||||||
|
:transaction true
|
||||||
|
:allowed-methods #{:post}}]
|
||||||
|
|
||||||
|
["/update-customer"
|
||||||
|
{:handler update-customer
|
||||||
|
:allowed-methods #{:post}
|
||||||
|
:transaction true}]])
|
||||||
|
|
||||||
|
;; ---- HELPERS
|
||||||
|
|
||||||
|
(defn- coercer
|
||||||
|
[schema & {:as opts}]
|
||||||
|
(let [decode-fn (sm/decoder schema sm/json-transformer)
|
||||||
|
check-fn (sm/check-fn schema opts)]
|
||||||
|
(fn [data]
|
||||||
|
(-> data decode-fn check-fn))))
|
||||||
|
|
||||||
|
;; ---- API: AUTHENTICATE
|
||||||
|
|
||||||
|
(defn- authenticate
|
||||||
|
[cfg request]
|
||||||
|
(let [token (-> request :params :token)
|
||||||
|
props (get cfg ::setup/props)
|
||||||
|
result (tokens/verify props {:token token :iss "authentication"})]
|
||||||
|
{::yres/status 200
|
||||||
|
::yres/body result}))
|
||||||
|
|
||||||
|
;; ---- API: GET-CUSTOMER
|
||||||
|
|
||||||
|
(def ^:private schema:get-customer
|
||||||
|
[:map [:id ::sm/uuid]])
|
||||||
|
|
||||||
|
(def ^:private coerce-get-customer-params
|
||||||
|
(coercer schema:get-customer
|
||||||
|
:type :validation
|
||||||
|
:hint "invalid data provided for `get-customer` rpc call"))
|
||||||
|
|
||||||
|
(def ^:private sql:get-customer-slots
|
||||||
|
"WITH teams AS (
|
||||||
|
SELECT tpr.team_id AS id,
|
||||||
|
tpr.profile_id AS profile_id
|
||||||
|
FROM team_profile_rel AS tpr
|
||||||
|
WHERE tpr.is_owner IS true
|
||||||
|
AND tpr.profile_id = ?
|
||||||
|
), teams_with_slots AS (
|
||||||
|
SELECT tpr.team_id AS id,
|
||||||
|
count(*) AS total
|
||||||
|
FROM team_profile_rel AS tpr
|
||||||
|
WHERE tpr.team_id IN (SELECT id FROM teams)
|
||||||
|
AND tpr.can_edit IS true
|
||||||
|
GROUP BY 1
|
||||||
|
ORDER BY 2
|
||||||
|
)
|
||||||
|
SELECT max(total) AS total FROM teams_with_slots;")
|
||||||
|
|
||||||
|
(defn- get-customer-slots
|
||||||
|
[cfg profile-id]
|
||||||
|
(let [result (db/exec-one! cfg [sql:get-customer-slots profile-id])]
|
||||||
|
(:total result)))
|
||||||
|
|
||||||
|
(defn- get-customer
|
||||||
|
[cfg request]
|
||||||
|
(let [profile-id (-> request :params coerce-get-customer-params :id)
|
||||||
|
profile (cmd.profile/get-profile cfg profile-id)
|
||||||
|
result {:id (get profile :id)
|
||||||
|
:name (get profile :fullname)
|
||||||
|
:email (get profile :email)
|
||||||
|
:num-editors (get-customer-slots cfg profile-id)
|
||||||
|
:subscription (-> profile :props :subscription)}]
|
||||||
|
{::yres/status 200
|
||||||
|
::yres/body result}))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---- API: UPDATE-CUSTOMER
|
||||||
|
|
||||||
|
(def ^:private schema:timestamp
|
||||||
|
(sm/type-schema
|
||||||
|
{:type ::timestamp
|
||||||
|
:pred ct/inst?
|
||||||
|
:type-properties
|
||||||
|
{:title "inst"
|
||||||
|
:description "The same as :app.common.time/inst but encodes to epoch"
|
||||||
|
:error/message "should be an instant"
|
||||||
|
:gen/gen (->> (sg/small-int)
|
||||||
|
(sg/fmap (fn [v] (ct/inst v))))
|
||||||
|
:decode/string ct/inst
|
||||||
|
:encode/string inst-ms
|
||||||
|
:decode/json ct/inst
|
||||||
|
:encode/json inst-ms}}))
|
||||||
|
|
||||||
|
(def ^:private schema:subscription
|
||||||
|
[:map {:title "Subscription"}
|
||||||
|
[:id ::sm/text]
|
||||||
|
[:customer-id ::sm/text]
|
||||||
|
[:type [:enum
|
||||||
|
"unlimited"
|
||||||
|
"professional"
|
||||||
|
"enterprise"]]
|
||||||
|
[:status [:enum
|
||||||
|
"active"
|
||||||
|
"canceled"
|
||||||
|
"incomplete"
|
||||||
|
"incomplete_expired"
|
||||||
|
"past_due"
|
||||||
|
"paused"
|
||||||
|
"trialing"
|
||||||
|
"unpaid"]]
|
||||||
|
|
||||||
|
[:billing-period [:enum
|
||||||
|
"month"
|
||||||
|
"day"
|
||||||
|
"week"
|
||||||
|
"year"]]
|
||||||
|
[:quantity :int]
|
||||||
|
[:description [:maybe ::sm/text]]
|
||||||
|
[:created-at schema:timestamp]
|
||||||
|
[:start-date [:maybe schema:timestamp]]
|
||||||
|
[:ended-at [:maybe schema:timestamp]]
|
||||||
|
[:trial-end [:maybe schema:timestamp]]
|
||||||
|
[:trial-start [:maybe schema:timestamp]]
|
||||||
|
[:cancel-at [:maybe schema:timestamp]]
|
||||||
|
[:canceled-at [:maybe schema:timestamp]]
|
||||||
|
[:current-period-end [:maybe schema:timestamp]]
|
||||||
|
[:current-period-start [:maybe schema:timestamp]]
|
||||||
|
[:cancel-at-period-end :boolean]
|
||||||
|
|
||||||
|
[:cancellation-details
|
||||||
|
[:map {:title "CancellationDetails"}
|
||||||
|
[:comment [:maybe ::sm/text]]
|
||||||
|
[:reason [:maybe ::sm/text]]
|
||||||
|
[:feedback [:maybe
|
||||||
|
[:enum
|
||||||
|
"customer_service"
|
||||||
|
"low_quality"
|
||||||
|
"missing_feature"
|
||||||
|
"other"
|
||||||
|
"switched_service"
|
||||||
|
"too_complex"
|
||||||
|
"too_expensive"
|
||||||
|
"unused"]]]]]])
|
||||||
|
|
||||||
|
(def ^:private schema:update-customer
|
||||||
|
[:map
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:subscription [:maybe schema:subscription]]])
|
||||||
|
|
||||||
|
(def ^:private coerce-update-customer-params
|
||||||
|
(coercer schema:update-customer
|
||||||
|
:type :validation
|
||||||
|
:hint "invalid data provided for `update-customer` rpc call"))
|
||||||
|
|
||||||
|
(defn- update-customer
|
||||||
|
[cfg request]
|
||||||
|
(let [{:keys [id subscription]}
|
||||||
|
(-> request :params coerce-update-customer-params)
|
||||||
|
|
||||||
|
{:keys [props] :as profile}
|
||||||
|
(cmd.profile/get-profile cfg id ::db/for-update true)
|
||||||
|
|
||||||
|
props
|
||||||
|
(assoc props :subscription subscription)]
|
||||||
|
|
||||||
|
(l/dbg :hint "update customer"
|
||||||
|
:profile-id (str id)
|
||||||
|
:subscription-type (get subscription :type)
|
||||||
|
:subscription-status (get subscription :status)
|
||||||
|
:subscription-quantity (get subscription :quantity))
|
||||||
|
|
||||||
|
(db/update! cfg :profile
|
||||||
|
{:props (db/tjson props)}
|
||||||
|
{:id id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
|
||||||
|
{::yres/status 201
|
||||||
|
::yres/body nil}))
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
[app.http.awsns :as http.awsns]
|
[app.http.awsns :as http.awsns]
|
||||||
[app.http.client :as-alias http.client]
|
[app.http.client :as-alias http.client]
|
||||||
[app.http.debug :as-alias http.debug]
|
[app.http.debug :as-alias http.debug]
|
||||||
|
[app.http.management :as mgmt]
|
||||||
[app.http.session :as-alias session]
|
[app.http.session :as-alias session]
|
||||||
[app.http.session.tasks :as-alias session.tasks]
|
[app.http.session.tasks :as-alias session.tasks]
|
||||||
[app.http.websocket :as http.ws]
|
[app.http.websocket :as http.ws]
|
||||||
@@ -273,6 +274,10 @@
|
|||||||
::email/blacklist (ig/ref ::email/blacklist)
|
::email/blacklist (ig/ref ::email/blacklist)
|
||||||
::email/whitelist (ig/ref ::email/whitelist)}
|
::email/whitelist (ig/ref ::email/whitelist)}
|
||||||
|
|
||||||
|
::mgmt/routes
|
||||||
|
{::db/pool (ig/ref ::db/pool)
|
||||||
|
::setup/props (ig/ref ::setup/props)}
|
||||||
|
|
||||||
:app.http/router
|
:app.http/router
|
||||||
{::session/manager (ig/ref ::session/manager)
|
{::session/manager (ig/ref ::session/manager)
|
||||||
::db/pool (ig/ref ::db/pool)
|
::db/pool (ig/ref ::db/pool)
|
||||||
@@ -281,6 +286,7 @@
|
|||||||
::setup/props (ig/ref ::setup/props)
|
::setup/props (ig/ref ::setup/props)
|
||||||
::mtx/routes (ig/ref ::mtx/routes)
|
::mtx/routes (ig/ref ::mtx/routes)
|
||||||
::oidc/routes (ig/ref ::oidc/routes)
|
::oidc/routes (ig/ref ::oidc/routes)
|
||||||
|
::mgmt/routes (ig/ref ::mgmt/routes)
|
||||||
::http.debug/routes (ig/ref ::http.debug/routes)
|
::http.debug/routes (ig/ref ::http.debug/routes)
|
||||||
::http.assets/routes (ig/ref ::http.assets/routes)
|
::http.assets/routes (ig/ref ::http.assets/routes)
|
||||||
::http.ws/routes (ig/ref ::http.ws/routes)
|
::http.ws/routes (ig/ref ::http.ws/routes)
|
||||||
|
|||||||
@@ -131,9 +131,7 @@
|
|||||||
;; NOTE: we need to retrieve the profile independently if we use
|
;; NOTE: we need to retrieve the profile independently if we use
|
||||||
;; it or not for explicit locking and avoid concurrent updates of
|
;; it or not for explicit locking and avoid concurrent updates of
|
||||||
;; the same row/object.
|
;; the same row/object.
|
||||||
(let [profile (-> (db/get-by-id conn :profile profile-id ::sql/for-update true)
|
(let [profile (get-profile conn profile-id ::db/for-update true)
|
||||||
(decode-row))
|
|
||||||
|
|
||||||
;; Update the profile map with direct params
|
;; Update the profile map with direct params
|
||||||
profile (-> profile
|
profile (-> profile
|
||||||
(assoc :fullname fullname)
|
(assoc :fullname fullname)
|
||||||
@@ -143,9 +141,9 @@
|
|||||||
(db/update! conn :profile
|
(db/update! conn :profile
|
||||||
{:fullname fullname
|
{:fullname fullname
|
||||||
:lang lang
|
:lang lang
|
||||||
:theme theme
|
:theme theme}
|
||||||
:props (db/tjson (:props profile))}
|
{:id profile-id}
|
||||||
{:id profile-id})
|
{::db/return-keys false})
|
||||||
|
|
||||||
(-> profile
|
(-> profile
|
||||||
(strip-private-attrs)
|
(strip-private-attrs)
|
||||||
@@ -228,21 +226,22 @@
|
|||||||
|
|
||||||
(defn- update-notifications!
|
(defn- update-notifications!
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id dashboard-comments email-comments email-invites]}]
|
[{:keys [::db/conn] :as cfg} {:keys [profile-id dashboard-comments email-comments email-invites]}]
|
||||||
(let [profile (get-profile conn profile-id)
|
(let [profile
|
||||||
|
(get-profile conn profile-id ::db/for-update true)
|
||||||
|
|
||||||
notifications
|
notifications
|
||||||
{:dashboard-comments dashboard-comments
|
{:dashboard-comments dashboard-comments
|
||||||
:email-comments email-comments
|
:email-comments email-comments
|
||||||
:email-invites email-invites}]
|
:email-invites email-invites}
|
||||||
|
|
||||||
(db/update!
|
props
|
||||||
conn :profile
|
(-> (get profile :props)
|
||||||
{:props
|
(assoc :notifications notifications))]
|
||||||
(-> (:props profile)
|
|
||||||
(assoc :notifications notifications)
|
|
||||||
(db/tjson))}
|
|
||||||
{:id (:id profile)})
|
|
||||||
|
|
||||||
|
(db/update! conn :profile
|
||||||
|
{:props (db/tjson props)}
|
||||||
|
{:id profile-id}
|
||||||
|
{::db/return-keys false})
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
;; --- MUTATION: Update Photo
|
;; --- MUTATION: Update Photo
|
||||||
@@ -411,7 +410,7 @@
|
|||||||
|
|
||||||
(defn update-profile-props
|
(defn update-profile-props
|
||||||
[{:keys [::db/conn] :as cfg} profile-id props]
|
[{:keys [::db/conn] :as cfg} profile-id props]
|
||||||
(let [profile (get-profile conn profile-id ::sql/for-update true)
|
(let [profile (get-profile conn profile-id ::db/for-update true)
|
||||||
props (reduce-kv (fn [props k v]
|
props (reduce-kv (fn [props k v]
|
||||||
;; We don't accept namespaced keys
|
;; We don't accept namespaced keys
|
||||||
(if (simple-ident? k)
|
(if (simple-ident? k)
|
||||||
@@ -424,16 +423,17 @@
|
|||||||
|
|
||||||
(db/update! conn :profile
|
(db/update! conn :profile
|
||||||
{:props (db/tjson props)}
|
{:props (db/tjson props)}
|
||||||
{:id profile-id})
|
{:id profile-id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
|
||||||
(filter-props props)))
|
(filter-props props)))
|
||||||
|
|
||||||
(sv/defmethod ::update-profile-props
|
(sv/defmethod ::update-profile-props
|
||||||
{::doc/added "1.0"
|
{::doc/added "1.0"
|
||||||
::sm/params schema:update-profile-props}
|
::sm/params schema:update-profile-props
|
||||||
|
::db/transaction true}
|
||||||
[cfg {:keys [::rpc/profile-id props]}]
|
[cfg {:keys [::rpc/profile-id props]}]
|
||||||
(db/tx-run! cfg (fn [cfg]
|
(update-profile-props cfg profile-id props))
|
||||||
(update-profile-props cfg profile-id props))))
|
|
||||||
|
|
||||||
;; --- MUTATION: Delete Profile
|
;; --- MUTATION: Delete Profile
|
||||||
|
|
||||||
@@ -471,6 +471,26 @@
|
|||||||
(-> (rph/wrap nil)
|
(-> (rph/wrap nil)
|
||||||
(rph/with-transform (session/delete-fn cfg)))))
|
(rph/with-transform (session/delete-fn cfg)))))
|
||||||
|
|
||||||
|
(def sql:get-subscription-editors
|
||||||
|
"SELECT DISTINCT
|
||||||
|
p.id,
|
||||||
|
p.fullname AS name,
|
||||||
|
p.email AS email
|
||||||
|
FROM team_profile_rel AS tpr1
|
||||||
|
JOIN team_profile_rel AS tpr2
|
||||||
|
ON (tpr1.team_id = tpr2.team_id)
|
||||||
|
JOIN profile AS p
|
||||||
|
ON (tpr2.profile_id = p.id)
|
||||||
|
WHERE tpr1.profile_id = ?
|
||||||
|
AND tpr1.is_owner IS true
|
||||||
|
AND tpr2.can_edit IS true")
|
||||||
|
|
||||||
|
(sv/defmethod ::get-subscription-usage
|
||||||
|
{::doc/added "2.9"}
|
||||||
|
[cfg {:keys [::rpc/profile-id]}]
|
||||||
|
(let [editors (db/exec! cfg [sql:get-subscription-editors profile-id])]
|
||||||
|
{:editors editors}))
|
||||||
|
|
||||||
;; --- HELPERS
|
;; --- HELPERS
|
||||||
|
|
||||||
(def sql:owned-teams
|
(def sql:owned-teams
|
||||||
|
|||||||
96
backend/test/backend_tests/http_management_test.clj
Normal file
96
backend/test/backend_tests/http_management_test.clj
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
;; 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 backend-tests.http-management-test
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.time :as ct]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.http.access-token]
|
||||||
|
[app.http.management :as mgmt]
|
||||||
|
[app.http.session :as sess]
|
||||||
|
[app.main :as-alias main]
|
||||||
|
[app.rpc :as-alias rpc]
|
||||||
|
[backend-tests.helpers :as th]
|
||||||
|
[clojure.test :as t]
|
||||||
|
[mockery.core :refer [with-mocks]]
|
||||||
|
[yetti.response :as-alias yres]))
|
||||||
|
|
||||||
|
(t/use-fixtures :once th/state-init)
|
||||||
|
(t/use-fixtures :each th/database-reset)
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest authenticate-method
|
||||||
|
(let [profile (th/create-profile* 1)
|
||||||
|
props (get th/*system* :app.setup/props)
|
||||||
|
token (#'sess/gen-token props {:profile-id (:id profile)})
|
||||||
|
request {:params {:token token}}
|
||||||
|
response (#'mgmt/authenticate th/*system* request)]
|
||||||
|
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (= "authentication" (-> response ::yres/body :iss)))
|
||||||
|
(t/is (= (:id profile) (-> response ::yres/body :uid)))))
|
||||||
|
|
||||||
|
(t/deftest get-customer-method
|
||||||
|
(let [profile (th/create-profile* 1)
|
||||||
|
request {:params {:id (:id profile)}}
|
||||||
|
response (#'mgmt/get-customer th/*system* request)]
|
||||||
|
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (= (:id profile) (-> response ::yres/body :id)))
|
||||||
|
(t/is (= (:fullname profile) (-> response ::yres/body :name)))
|
||||||
|
(t/is (= (:email profile) (-> response ::yres/body :email)))
|
||||||
|
(t/is (= 1 (-> response ::yres/body :num-editors)))
|
||||||
|
(t/is (nil? (-> response ::yres/body :subscription)))))
|
||||||
|
|
||||||
|
(t/deftest update-customer-method
|
||||||
|
(let [profile (th/create-profile* 1)
|
||||||
|
|
||||||
|
subs {:type "unlimited"
|
||||||
|
:description nil
|
||||||
|
:id "foobar"
|
||||||
|
:customer-id (str (:id profile))
|
||||||
|
:status "past_due"
|
||||||
|
:billing-period "week"
|
||||||
|
:quantity 1
|
||||||
|
:created-at (ct/truncate (ct/now) :day)
|
||||||
|
:cancel-at-period-end true
|
||||||
|
:start-date nil
|
||||||
|
:ended-at nil
|
||||||
|
:trial-end nil
|
||||||
|
:trial-start nil
|
||||||
|
:cancel-at nil
|
||||||
|
:canceled-at nil
|
||||||
|
:current-period-end nil
|
||||||
|
:current-period-start nil
|
||||||
|
|
||||||
|
:cancellation-details
|
||||||
|
{:comment "other"
|
||||||
|
:reason "other"
|
||||||
|
:feedback "other"}}
|
||||||
|
|
||||||
|
request {:params {:id (:id profile)
|
||||||
|
:subscription subs}}
|
||||||
|
response (#'mgmt/update-customer th/*system* request)]
|
||||||
|
|
||||||
|
(t/is (= 201 (::yres/status response)))
|
||||||
|
(t/is (nil? (::yres/body response)))
|
||||||
|
|
||||||
|
(let [request {:params {:id (:id profile)}}
|
||||||
|
response (#'mgmt/get-customer th/*system* request)]
|
||||||
|
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (= (:id profile) (-> response ::yres/body :id)))
|
||||||
|
(t/is (= (:fullname profile) (-> response ::yres/body :name)))
|
||||||
|
(t/is (= (:email profile) (-> response ::yres/body :email)))
|
||||||
|
(t/is (= 1 (-> response ::yres/body :num-editors)))
|
||||||
|
|
||||||
|
(let [subs' (-> response ::yres/body :subscription)]
|
||||||
|
(t/is (= subs' subs))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"~:id": "~uf88e52d7-2b77-81fd-8006-23413fafe56c",
|
|
||||||
"~:name": "The Alpaca team",
|
|
||||||
"~:total-editors": 3,
|
|
||||||
"~:total-members": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"~:id": "~u81be1d05-a07b-81d5-8006-3e728bea76fb",
|
|
||||||
"~:name": "The Quokka team",
|
|
||||||
"~:total-editors": 1,
|
|
||||||
"~:total-members": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"~:editors": [
|
||||||
|
{
|
||||||
|
"~:id": "~u4f535993-36f9-8135-8006-9b18345c55cd",
|
||||||
|
"~:name": "Luke Skywalker",
|
||||||
|
"~:email": "luke@rebels.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"~:editors": [
|
||||||
|
{
|
||||||
|
"~:id": "~u4f535993-36f9-8135-8006-9b18345c55cd",
|
||||||
|
"~:name": "Luke Skywalker",
|
||||||
|
"~:email": "luke@rebels.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~u70a3b232-3722-8008-8006-86646ed3b6af",
|
||||||
|
"~:name": "Leia Organa",
|
||||||
|
"~:email": "leia@rebels.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~u81be1d05-a07b-81d5-8006-39095ea4121c",
|
||||||
|
"~:name": "Han Solo",
|
||||||
|
"~:email": "han@falcon.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~u96ce2641-e3fd-803a-8006-5e516d034d57",
|
||||||
|
"~:name": "Darth Vader",
|
||||||
|
"~:email": "vader@empire.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~uc9aa6cb0-9fb5-80a2-8006-9c3a0783ddc7",
|
||||||
|
"~:name": "Obi-Wan Kenobi",
|
||||||
|
"~:email": "obiwan@jedi.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~uf88e52d7-2b77-81fd-8006-234039f9e8db",
|
||||||
|
"~:name": "Yoda",
|
||||||
|
"~:email": "yoda@jedi.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~uf88e52d7-2b77-81fd-8006-2341585b061d",
|
||||||
|
"~:name": "Chewbacca",
|
||||||
|
"~:email": "chewie@falcon.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:id": "~ufa35a73f-fa4f-81f9-8006-a558c4d406b1",
|
||||||
|
"~:name": "R2-D2",
|
||||||
|
"~:email": "r2d2@astromech.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "luke@rebels.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "Luke Skywalker",
|
||||||
|
"~:fullname": "Luke Skywalker",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~u4f535993-36f9-8135-8006-9b18345c55cd",
|
||||||
|
"~:profile-id": "~u4f535993-36f9-8135-8006-9b18345c55cd",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "leia@rebels.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "Leia Organa",
|
||||||
|
"~:fullname": "Leia Organa",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~u70a3b232-3722-8008-8006-86646ed3b6af",
|
||||||
|
"~:profile-id": "~u70a3b232-3722-8008-8006-86646ed3b6af",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "han@falcon.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "Han Solo",
|
||||||
|
"~:fullname": "Han Solo",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~u81be1d05-a07b-81d5-8006-39095ea4121c",
|
||||||
|
"~:profile-id": "~u81be1d05-a07b-81d5-8006-39095ea4121c",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "vader@empire.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "Darth Vader",
|
||||||
|
"~:fullname": "Darth Vader",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~u96ce2641-e3fd-803a-8006-5e516d034d57",
|
||||||
|
"~:profile-id": "~u96ce2641-e3fd-803a-8006-5e516d034d57",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "obiwan@jedi.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "Obi-Wan Kenobi",
|
||||||
|
"~:fullname": "Obi-Wan Kenobi",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~uc9aa6cb0-9fb5-80a2-8006-9c3a0783ddc7",
|
||||||
|
"~:profile-id": "~uc9aa6cb0-9fb5-80a2-8006-9c3a0783ddc7",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "yoda@jedi.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "Yoda",
|
||||||
|
"~:fullname": "Yoda",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~uf88e52d7-2b77-81fd-8006-234039f9e8db",
|
||||||
|
"~:profile-id": "~uf88e52d7-2b77-81fd-8006-234039f9e8db",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "chewie@falcon.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "Chewbacca",
|
||||||
|
"~:fullname": "Chewbacca",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~uf88e52d7-2b77-81fd-8006-2341585b061d",
|
||||||
|
"~:profile-id": "~uf88e52d7-2b77-81fd-8006-2341585b061d",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:is-admin": false,
|
||||||
|
"~:email": "r2d2@astromech.com",
|
||||||
|
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||||
|
"~:name": "R2-D2",
|
||||||
|
"~:fullname": "R2-D2",
|
||||||
|
"~:is-owner": false,
|
||||||
|
"~:modified-at": "~m1713533116365",
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:is-active": true,
|
||||||
|
"~:id": "~ufa35a73f-fa4f-81f9-8006-a558c4d406b1",
|
||||||
|
"~:profile-id": "~ufa35a73f-fa4f-81f9-8006-a558c4d406b1",
|
||||||
|
"~:created-at": "~m1713533116365"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -39,10 +39,11 @@
|
|||||||
"~:is-admin": true,
|
"~:is-admin": true,
|
||||||
"~:can-edit": true
|
"~:can-edit": true
|
||||||
},
|
},
|
||||||
|
"~:unique-editors": 1,
|
||||||
"~:subscription": {
|
"~:subscription": {
|
||||||
"~:type": "unlimited",
|
"~:type": "unlimited",
|
||||||
"~:status": "trialing",
|
"~:status": "trialing",
|
||||||
"~:seats": 2
|
"~:seats": 5
|
||||||
},
|
},
|
||||||
"~:name": "Second team",
|
"~:name": "Second team",
|
||||||
"~:modified-at": "~m1701164272671",
|
"~:modified-at": "~m1701164272671",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"fdata/shape-data-type"
|
"fdata/shape-data-type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"~:unique-editors": 1,
|
||||||
"~:permissions": {
|
"~:permissions": {
|
||||||
"~:type": "~:owner",
|
"~:type": "~:owner",
|
||||||
"~:is-owner": true,
|
"~:is-owner": true,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ export class SubscriptionProfilePage extends DashboardPage {
|
|||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-owned-teams",
|
"get-subscription-usage",
|
||||||
"subscription/get-owned-teams.json",
|
"subscription/get-subscription-usage.json",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ test.describe("Subscriptions: dashboard", () => {
|
|||||||
"subscription/get-profile-unlimited-subscription.json",
|
"subscription/get-profile-unlimited-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -55,6 +61,12 @@ test.describe("Subscriptions: dashboard", () => {
|
|||||||
"subscription/get-profile-unlimited-subscription.json",
|
"subscription/get-profile-unlimited-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage-one-editor.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -89,6 +101,12 @@ test.describe("Subscriptions: dashboard", () => {
|
|||||||
"subscription/get-profile-unlimited-unpaid-subscription.json",
|
"subscription/get-profile-unlimited-unpaid-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -123,6 +141,12 @@ test.describe("Subscriptions: dashboard", () => {
|
|||||||
"subscription/get-profile-enterprise-canceled-subscription.json",
|
"subscription/get-profile-enterprise-canceled-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -159,6 +183,12 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
"logged-in-user/get-profile-logged-in.json",
|
"logged-in-user/get-profile-logged-in.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -185,6 +215,12 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
"subscription/get-team-members-subscription-member.json",
|
"subscription/get-team-members-subscription-member.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-team-stats?team-id=*",
|
||||||
|
"dashboard/get-team-stats.json",
|
||||||
|
);
|
||||||
|
|
||||||
await dashboardPage.mockRPC(
|
await dashboardPage.mockRPC(
|
||||||
"push-audit-events",
|
"push-audit-events",
|
||||||
"workspace/audit-event-empty.json",
|
"workspace/audit-event-empty.json",
|
||||||
@@ -206,6 +242,12 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
"subscription/get-profile-unlimited-subscription.json",
|
"subscription/get-profile-unlimited-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -251,7 +293,7 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Members tab has warning message when team has more members than subscriptions. Subscribe link is shown for owners.", async ({
|
test("Members tab has warning message when user has more seats than editors.", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
@@ -260,6 +302,12 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
"subscription/get-profile-unlimited-subscription.json",
|
"subscription/get-profile-unlimited-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -283,7 +331,7 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-members?team-id=*",
|
"get-team-members?team-id=*",
|
||||||
"subscription/get-team-members-subscription-owner.json",
|
"subscription/get-team-members-subscription-eight-member.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
await dashboardPage.mockRPC(
|
await dashboardPage.mockRPC(
|
||||||
@@ -292,105 +340,15 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await dashboardPage.goToSecondTeamMembersSection();
|
await dashboardPage.goToSecondTeamMembersSection();
|
||||||
await expect(page.getByTestId("cta")).toBeVisible();
|
|
||||||
await expect(page.getByText("Subscribe now.")).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Members tab has warning message when team has more members than subscriptions. Contact to owner is shown for members.", async ({
|
const ctas = page.getByTestId("cta");
|
||||||
page,
|
await expect(ctas).toHaveCount(2);
|
||||||
}) => {
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-profile",
|
|
||||||
"logged-in-user/get-profile-logged-in.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-team-info",
|
|
||||||
"subscription/get-team-info-subscriptions.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const dashboardPage = new DashboardPage(page);
|
|
||||||
await dashboardPage.setupDashboardFull();
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-teams",
|
|
||||||
"subscription/get-teams-unlimited-subscription-member.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-projects?team-id=*",
|
|
||||||
"dashboard/get-projects-second-team.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-team-members?team-id=*",
|
|
||||||
"subscription/get-team-members-subscription-member.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await dashboardPage.mockRPC(
|
|
||||||
"push-audit-events",
|
|
||||||
"workspace/audit-event-empty.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await dashboardPage.goToSecondTeamMembersSection();
|
|
||||||
await expect(page.getByTestId("cta")).toBeVisible();
|
|
||||||
await expect(page.getByText("Contact with the team owner")).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Members tab has warning message when has professional subscription and more than 8 members.", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-profile",
|
|
||||||
"logged-in-user/get-profile-logged-in.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-team-info",
|
|
||||||
"subscription/get-team-info-subscriptions.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const dashboardPage = new DashboardPage(page);
|
|
||||||
await dashboardPage.setupDashboardFull();
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-teams",
|
|
||||||
"subscription/get-teams-professional-subscription-owner.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-projects?team-id=*",
|
|
||||||
"dashboard/get-projects-second-team.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-team-members?team-id=*",
|
|
||||||
"subscription/get-team-members-more-than-8.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await dashboardPage.mockRPC(
|
|
||||||
"push-audit-events",
|
|
||||||
"workspace/audit-event-empty.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await dashboardPage.goToSecondTeamMembersSection();
|
|
||||||
await expect(page.getByTestId("cta")).toBeVisible();
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(
|
page.getByText("Inviting people while on the unlimited plan"),
|
||||||
"The Professional plan is designed for teams of up to 8 editors (owner, admin, and editor).",
|
|
||||||
),
|
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Invitations tab has warning message when team has more members than subscriptions", async ({
|
test("Invitations tab has warning message when user has more seats than editors.", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
@@ -399,6 +357,12 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
"subscription/get-profile-unlimited-subscription.json",
|
"subscription/get-profile-unlimited-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await DashboardPage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-info",
|
"get-team-info",
|
||||||
@@ -422,13 +386,13 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-members?team-id=*",
|
"get-team-members?team-id=*",
|
||||||
"subscription/get-team-members-subscription-owner.json",
|
"subscription/get-team-members-subscription-eight-member.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
await DashboardPage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-team-invitations?team-id=*",
|
"get-team-invitations?team-id=*",
|
||||||
"dashboard/get-team-invitations-empty.json",
|
"subscription/get-team-invitations.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
await dashboardPage.mockRPC(
|
await dashboardPage.mockRPC(
|
||||||
@@ -437,66 +401,11 @@ test.describe("Subscriptions: team members and invitations", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await dashboardPage.goToSecondTeamInvitationsSection();
|
await dashboardPage.goToSecondTeamInvitationsSection();
|
||||||
await expect(page.getByTestId("cta")).toBeVisible();
|
|
||||||
|
const ctas = page.getByTestId("cta");
|
||||||
|
await expect(ctas).toHaveCount(2);
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(
|
page.getByText("Inviting people while on the unlimited plan"),
|
||||||
"Looks like your team has grown! Your plan includes 2 seats, but you're now using 3",
|
|
||||||
),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Invitations tab has warning message when has professional subscription and more than 8 members.", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-profile",
|
|
||||||
"subscription/get-profile-unlimited-subscription.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-team-info",
|
|
||||||
"subscription/get-team-info-subscriptions.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const dashboardPage = new DashboardPage(page);
|
|
||||||
await dashboardPage.setupDashboardFull();
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-teams",
|
|
||||||
"subscription/get-teams-unlimited-subscription-owner.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-projects?team-id=*",
|
|
||||||
"dashboard/get-projects-second-team.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-team-members?team-id=*",
|
|
||||||
"subscription/get-team-members-more-than-8.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await DashboardPage.mockRPC(
|
|
||||||
page,
|
|
||||||
"get-team-invitations?team-id=*",
|
|
||||||
"dashboard/get-team-invitations-empty.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await dashboardPage.mockRPC(
|
|
||||||
"push-audit-events",
|
|
||||||
"workspace/audit-event-empty.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
await dashboardPage.goToSecondTeamInvitationsSection();
|
|
||||||
await expect(page.getByTestId("cta")).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByText(
|
|
||||||
"Looks like your team has grown! Your plan includes 2 seats, but you're now using 9",
|
|
||||||
),
|
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ test.describe("Subscriptions: workspace", () => {
|
|||||||
"subscription/get-profile-unlimited-subscription.json",
|
"subscription/get-profile-unlimited-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await WorkspacePage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await workspacePage.mockRPC(
|
await workspacePage.mockRPC(
|
||||||
"push-audit-events",
|
"push-audit-events",
|
||||||
"workspace/audit-event-empty.json",
|
"workspace/audit-event-empty.json",
|
||||||
@@ -44,6 +50,12 @@ test.describe("Subscriptions: workspace", () => {
|
|||||||
"subscription/get-profile-enterprise-subscription.json",
|
"subscription/get-profile-enterprise-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await WorkspacePage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await workspacePage.mockRPC(
|
await workspacePage.mockRPC(
|
||||||
"push-audit-events",
|
"push-audit-events",
|
||||||
"workspace/audit-event-empty.json",
|
"workspace/audit-event-empty.json",
|
||||||
@@ -60,6 +72,18 @@ test.describe("Subscriptions: workspace", () => {
|
|||||||
const workspacePage = new WorkspacePage(page);
|
const workspacePage = new WorkspacePage(page);
|
||||||
await workspacePage.setupEmptyFile();
|
await workspacePage.setupEmptyFile();
|
||||||
|
|
||||||
|
await WorkspacePage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-profile",
|
||||||
|
"subscription/get-profile-enterprise-subscription.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
await WorkspacePage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await workspacePage.mockRPC(
|
await workspacePage.mockRPC(
|
||||||
"push-audit-events",
|
"push-audit-events",
|
||||||
"workspace/audit-event-empty.json",
|
"workspace/audit-event-empty.json",
|
||||||
@@ -90,6 +114,12 @@ test.describe("Subscriptions: workspace", () => {
|
|||||||
"subscription/get-profile-unlimited-subscription.json",
|
"subscription/get-profile-unlimited-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await WorkspacePage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await WorkspacePage.mockRPC(
|
await WorkspacePage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-teams",
|
"get-teams",
|
||||||
@@ -126,6 +156,12 @@ test.describe("Subscriptions: workspace", () => {
|
|||||||
"subscription/get-profile-enterprise-subscription.json",
|
"subscription/get-profile-enterprise-subscription.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await WorkspacePage.mockRPC(
|
||||||
|
page,
|
||||||
|
"get-subscription-usage",
|
||||||
|
"subscription/get-subscription-usage.json",
|
||||||
|
);
|
||||||
|
|
||||||
await WorkspacePage.mockRPC(
|
await WorkspacePage.mockRPC(
|
||||||
page,
|
page,
|
||||||
"get-teams",
|
"get-teams",
|
||||||
|
|||||||
@@ -82,6 +82,16 @@
|
|||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (rp/cmd! :get-profile)
|
(->> (rp/cmd! :get-profile)
|
||||||
|
(rx/mapcat (fn [profile]
|
||||||
|
(if (and (contains? cf/flags :subscriptions)
|
||||||
|
(is-authenticated? profile))
|
||||||
|
(->> (rp/cmd! :get-subscription-usage {})
|
||||||
|
(rx/map (fn [{:keys [editors]}]
|
||||||
|
(update-in profile [:props :subscription] assoc :editors editors)))
|
||||||
|
(rx/catch (fn [cause]
|
||||||
|
(js/console.error "unexpected error on obtaining subscription usage" cause)
|
||||||
|
(rx/of profile))))
|
||||||
|
(rx/of profile))))
|
||||||
(rx/map (partial ptk/data-event ::profile-fetched))
|
(rx/map (partial ptk/data-event ::profile-fetched))
|
||||||
(rx/catch on-fetch-profile-exception)))))
|
(rx/catch on-fetch-profile-exception)))))
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.media :as di]
|
[app.main.data.media :as di]
|
||||||
|
[app.main.data.profile :as dp]
|
||||||
[app.main.features :as features]
|
[app.main.features :as features]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
@@ -142,8 +143,9 @@
|
|||||||
|
|
||||||
(defn update-member-role
|
(defn update-member-role
|
||||||
[{:keys [role member-id] :as params}]
|
[{:keys [role member-id] :as params}]
|
||||||
(dm/assert! (uuid? member-id))
|
|
||||||
(dm/assert! (contains? ctt/valid-roles role))
|
(assert (uuid? member-id))
|
||||||
|
(assert (contains? ctt/valid-roles role))
|
||||||
|
|
||||||
(ptk/reify ::update-member-role
|
(ptk/reify ::update-member-role
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
@@ -152,13 +154,13 @@
|
|||||||
params (assoc params :team-id team-id)]
|
params (assoc params :team-id team-id)]
|
||||||
(->> (rp/cmd! :update-team-member-role params)
|
(->> (rp/cmd! :update-team-member-role params)
|
||||||
(rx/mapcat (fn [_]
|
(rx/mapcat (fn [_]
|
||||||
(rx/of (fetch-members team-id)
|
(rx/of (dp/refresh-profile)
|
||||||
|
(fetch-members team-id)
|
||||||
(fetch-teams)
|
(fetch-teams)
|
||||||
(ptk/data-event ::ev/event
|
(ev/event {::ev/name "update-team-member-role"
|
||||||
{::ev/name "update-team-member-role"
|
:team-id team-id
|
||||||
:team-id team-id
|
:role role
|
||||||
:role role
|
:member-id member-id})))))))))
|
||||||
:member-id member-id})))))))))
|
|
||||||
|
|
||||||
(defn delete-member
|
(defn delete-member
|
||||||
[{:keys [member-id] :as params}]
|
[{:keys [member-id] :as params}]
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
[:> team-members-page* {:team team :profile profile}]
|
[:> team-members-page* {:team team :profile profile}]
|
||||||
|
|
||||||
:dashboard-invitations
|
:dashboard-invitations
|
||||||
[:> team-invitations-page* {:team team}]
|
[:> team-invitations-page* {:team team :profile profile}]
|
||||||
|
|
||||||
:dashboard-webhooks
|
:dashboard-webhooks
|
||||||
[:> webhooks-page* {:team team}]
|
[:> webhooks-page* {:team team}]
|
||||||
|
|||||||
@@ -27,7 +27,11 @@
|
|||||||
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
|
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
|
||||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||||
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
||||||
[app.main.ui.dashboard.subscription :refer [subscription-sidebar* menu-team-icon* get-subscription-type]]
|
[app.main.ui.dashboard.subscription :refer [subscription-sidebar*
|
||||||
|
menu-team-icon*
|
||||||
|
dashboard-cta*
|
||||||
|
show-subscription-dashboard-banner?
|
||||||
|
get-subscription-type]]
|
||||||
[app.main.ui.dashboard.team-form]
|
[app.main.ui.dashboard.team-form]
|
||||||
[app.main.ui.icons :as i :refer [icon-xref]]
|
[app.main.ui.icons :as i :refer [icon-xref]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
@@ -838,7 +842,9 @@
|
|||||||
|
|
||||||
[:*
|
[:*
|
||||||
(when (contains? cf/flags :subscriptions)
|
(when (contains? cf/flags :subscriptions)
|
||||||
[:> subscription-sidebar* {:profile profile}])
|
(if (show-subscription-dashboard-banner? profile)
|
||||||
|
[:> dashboard-cta* {:profile profile}]
|
||||||
|
[:> subscription-sidebar* {:profile profile}]))
|
||||||
|
|
||||||
;; TODO remove this block when subscriptions is full implemented
|
;; TODO remove this block when subscriptions is full implemented
|
||||||
(when (contains? cf/flags :subscriptions-old)
|
(when (contains? cf/flags :subscriptions-old)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
(defn get-subscription-type
|
(defn get-subscription-type
|
||||||
[{:keys [type status] :as subscription}]
|
[{:keys [type status] :as subscription}]
|
||||||
(if (and subscription (not (contains? #{"unpaid" "canceled"} status)))
|
(if (and subscription (:type subscription) (not (contains? #{"unpaid" "canceled"} status)))
|
||||||
type
|
type
|
||||||
"professional"))
|
"professional"))
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
[:> cta-power-up*
|
[:> cta-power-up*
|
||||||
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
|
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
|
||||||
:top-description (tr "subscription.dashboard.power-up.professional.top-title")
|
:top-description (tr "subscription.dashboard.power-up.professional.top-title")
|
||||||
:bottom-description (tr "subscription.dashboard.power-up.professional.bottom", subscription-href)
|
:bottom-description (tr "subscription.dashboard.power-up.professional.bottom-text" subscription-href)
|
||||||
:has-dropdown true}]
|
:has-dropdown true}]
|
||||||
|
|
||||||
"unlimited"
|
"unlimited"
|
||||||
@@ -69,13 +69,13 @@
|
|||||||
[:> cta-power-up*
|
[:> cta-power-up*
|
||||||
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
|
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
|
||||||
:top-description (tr "subscription.dashboard.power-up.trial.top-title")
|
:top-description (tr "subscription.dashboard.power-up.trial.top-title")
|
||||||
:bottom-description (tr "subscription.dashboard.power-up.trial.bottom-description", subscription-href)
|
:bottom-description (tr "subscription.dashboard.power-up.trial.bottom-description" subscription-href)
|
||||||
:has-dropdown true}]
|
:has-dropdown true}]
|
||||||
|
|
||||||
[:> cta-power-up*
|
[:> cta-power-up*
|
||||||
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
|
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
|
||||||
:top-description (tr "subscription.dashboard.power-up.unlimited-plan")
|
:top-description (tr "subscription.dashboard.power-up.unlimited-plan")
|
||||||
:bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom-text", subscription-href)
|
:bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom-text" subscription-href)
|
||||||
:has-dropdown true}])
|
:has-dropdown true}])
|
||||||
|
|
||||||
"enterprise"
|
"enterprise"
|
||||||
@@ -147,71 +147,74 @@
|
|||||||
[:span {:class (stl/css :item-name)} (tr "subscription.workspace.header.menu.option.power-up")]]))
|
[:span {:class (stl/css :item-name)} (tr "subscription.workspace.header.menu.option.power-up")]]))
|
||||||
|
|
||||||
(mf/defc members-cta*
|
(mf/defc members-cta*
|
||||||
[{:keys [banner-is-expanded team]}]
|
[]
|
||||||
(let [subscription (:subscription team)
|
[:> cta* {:class (stl/css :members-cta)
|
||||||
|
:title (tr "subscription.dashboard.unlimited-members-extra-editors-cta-title")}
|
||||||
|
[:> i18n/tr-html*
|
||||||
|
{:tag-name "span"
|
||||||
|
:class (stl/css :cta-message)
|
||||||
|
:content (tr "subscription.dashboard.unlimited-members-extra-editors-cta-text")}]])
|
||||||
|
|
||||||
|
(mf/defc dashboard-cta*
|
||||||
|
[{:keys [profile]}]
|
||||||
|
(let [subscription (-> profile :props :subscription)
|
||||||
subscription-type (get-subscription-type subscription)
|
subscription-type (get-subscription-type subscription)
|
||||||
is-owner (-> team :permissions :is-owner)
|
|
||||||
|
|
||||||
email-owner (:email (some #(when (:is-owner %) %) (:members team)))
|
|
||||||
mail-to-owner (str "<a href=\"" "mailto:" email-owner "\">" email-owner "</a>")
|
|
||||||
go-to-subscription (dm/str (u/join cf/public-uri "#/settings/subscriptions"))
|
go-to-subscription (dm/str (u/join cf/public-uri "#/settings/subscriptions"))
|
||||||
seats (or (:seats subscription) 0)
|
seats (:quantity subscription)
|
||||||
editors (count (filterv :can-edit (:members team)))
|
editors (count (:editors subscription))
|
||||||
|
|
||||||
link
|
|
||||||
(if is-owner
|
|
||||||
go-to-subscription
|
|
||||||
mail-to-owner)
|
|
||||||
|
|
||||||
cta-title
|
cta-title
|
||||||
(cond
|
(cond
|
||||||
(and (= "professional" subscription-type) (>= editors 8))
|
(= "professional" subscription-type)
|
||||||
(tr "subscription.dashboard.cta.professional-plan-designed")
|
(tr "subscription.dashboard.professional-dashboard-cta-title" editors)
|
||||||
|
|
||||||
(and (= "unlimited" subscription-type) (< seats editors))
|
(= "unlimited" subscription-type)
|
||||||
(tr "subscription.dashboard.cta.unlimited-many-editors" seats editors))
|
(tr "subscription.dashboard.unlimited-dashboard-cta-title" seats editors))
|
||||||
|
|
||||||
cta-message
|
cta-message
|
||||||
(cond
|
(cond
|
||||||
(and (= "professional" subscription-type) (>= editors 8) is-owner)
|
(= "professional" subscription-type)
|
||||||
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" link)
|
(tr "subscription.dashboard.professional-dashboard-cta-upgrade-owner" go-to-subscription)
|
||||||
|
|
||||||
(and (= "professional" subscription-type) (>= editors 8) (not is-owner))
|
(= "unlimited" subscription-type)
|
||||||
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-member" link)
|
(tr "subscription.dashboard.unlimited-dashboard-cta-upgrade-owner" go-to-subscription))]
|
||||||
|
|
||||||
(and (= "unlimited" subscription-type) (< seats editors) is-owner)
|
[:> cta* {:class (stl/css :dashboard-cta) :title cta-title}
|
||||||
(tr "subscription.dashboard.cta.upgrade-more-seats-owner" link)
|
|
||||||
|
|
||||||
(and (= "unlimited" subscription-type) (< seats editors) (not is-owner))
|
|
||||||
(tr "subscription.dashboard.cta.upgrade-more-seats-member" link))]
|
|
||||||
|
|
||||||
[:> cta* {:class (stl/css-case ::members-cta-full-width banner-is-expanded :members-cta (not banner-is-expanded)) :title cta-title}
|
|
||||||
[:> i18n/tr-html*
|
[:> i18n/tr-html*
|
||||||
{:tag-name "span"
|
{:tag-name "span"
|
||||||
:class (stl/css :cta-message)
|
:class (stl/css :cta-message)
|
||||||
:content cta-message}]]))
|
:content cta-message}]]))
|
||||||
|
|
||||||
(defn show-subscription-members-main-banner?
|
(defn show-subscription-dashboard-banner?
|
||||||
[team]
|
[profile]
|
||||||
(let [subscription-type (get-subscription-type (:subscription team))
|
(let [subscription (-> profile :props :subscription)
|
||||||
seats (-> team :subscription :seats)
|
subscription-type (get-subscription-type subscription)
|
||||||
editors (count (filter :can-edit (:members team)))]
|
seats (:quantity subscription)
|
||||||
|
editors (count (:editors subscription))]
|
||||||
|
|
||||||
(or
|
(or
|
||||||
(and (= subscription-type "professional")
|
(and (= subscription-type "professional")
|
||||||
(> editors 8))
|
(> editors 8))
|
||||||
(and
|
(and
|
||||||
(= subscription-type "unlimited")
|
(= subscription-type "unlimited")
|
||||||
(>= editors 8)
|
(or
|
||||||
(< seats editors)))))
|
;; common: seats < 25 and diff >= 4
|
||||||
|
(and (< seats 25)
|
||||||
|
(>= (- editors seats) 4))
|
||||||
|
;; special: reached 25+ editors, seats < 25 and there is overuse
|
||||||
|
(and (< seats 25)
|
||||||
|
(>= editors 25)
|
||||||
|
(> editors seats)))))))
|
||||||
|
|
||||||
(defn show-subscription-members-small-banner?
|
(defn show-subscription-members-banner?
|
||||||
[team]
|
[team profile]
|
||||||
(let [subscription-type (get-subscription-type (:subscription team))
|
(let [subscription (:subscription team)
|
||||||
seats (-> team :subscription :seats)
|
subscription-type (get-subscription-type subscription)
|
||||||
editors (count (filterv :can-edit (:members team)))]
|
seats (:seats subscription)
|
||||||
(or
|
editors (count (-> profile :props :subscription :editors))
|
||||||
(and (= subscription-type "professional")
|
is-owner (-> team :permissions :is-owner)]
|
||||||
(= editors 8))
|
(and
|
||||||
(and (= subscription-type "unlimited")
|
is-owner
|
||||||
(< editors 8)
|
(= subscription-type "unlimited")
|
||||||
(< seats editors)))))
|
;; common: seats < 25 and diff >= 4 between editors/seats and there is overuse
|
||||||
|
(and (< seats 25)
|
||||||
|
(>= (- editors seats) 4)))))
|
||||||
|
|||||||
@@ -143,16 +143,21 @@
|
|||||||
.members-cta {
|
.members-cta {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
margin-block-start: var(--sp-s);
|
margin-block-start: var(--sp-s);
|
||||||
margin-inline-start: $s-68;
|
margin-inline-start: $s-52;
|
||||||
max-width: $s-200;
|
max-width: $s-220;
|
||||||
|
|
||||||
.cta-title {
|
.cta-title {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.members-cta-full-width {
|
.dashboard-cta {
|
||||||
max-width: $s-1000;
|
height: fit-content;
|
||||||
|
margin: var(--sp-l);
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-message {
|
.cta-message {
|
||||||
|
|||||||
@@ -24,8 +24,7 @@
|
|||||||
[app.main.ui.dashboard.change-owner]
|
[app.main.ui.dashboard.change-owner]
|
||||||
[app.main.ui.dashboard.subscription :refer [team*
|
[app.main.ui.dashboard.subscription :refer [team*
|
||||||
members-cta*
|
members-cta*
|
||||||
show-subscription-members-main-banner?
|
show-subscription-members-banner?]]
|
||||||
show-subscription-members-small-banner?]]
|
|
||||||
[app.main.ui.dashboard.team-form]
|
[app.main.ui.dashboard.team-form]
|
||||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
@@ -267,8 +266,7 @@
|
|||||||
[:span {:class (stl/css :you)} (tr "labels.you")])]
|
[:span {:class (stl/css :you)} (tr "labels.you")])]
|
||||||
[:div {:class (stl/css :member-email)} (:email member)]]]))
|
[:div {:class (stl/css :member-email)} (:email member)]]]))
|
||||||
|
|
||||||
(mf/defc rol-info
|
(mf/defc rol-info*
|
||||||
{::mf/props :obj}
|
|
||||||
[{:keys [member team on-set-admin on-set-editor on-set-owner on-set-viewer profile]}]
|
[{:keys [member team on-set-admin on-set-editor on-set-owner on-set-viewer profile]}]
|
||||||
(let [member-is-owner (:is-owner member)
|
(let [member-is-owner (:is-owner member)
|
||||||
member-is-admin (and (:is-admin member) (not member-is-owner))
|
member-is-admin (and (:is-admin member) (not member-is-owner))
|
||||||
@@ -283,13 +281,15 @@
|
|||||||
is-you (= (:id profile) (:id member))
|
is-you (= (:id profile) (:id member))
|
||||||
|
|
||||||
can-change-rol (or is-owner is-admin)
|
can-change-rol (or is-owner is-admin)
|
||||||
not-superior (or (and (not member-is-owner) is-admin) (and can-change-rol (or member-is-admin member-is-editor member-is-viewer)))
|
not-superior (or (and (not member-is-owner) is-admin)
|
||||||
|
(and can-change-rol (or member-is-admin member-is-editor member-is-viewer)))
|
||||||
|
|
||||||
role (cond
|
role (cond
|
||||||
member-is-owner "labels.owner"
|
member-is-owner "labels.owner"
|
||||||
member-is-admin "labels.admin"
|
member-is-admin "labels.admin"
|
||||||
member-is-editor "labels.editor"
|
member-is-editor "labels.editor"
|
||||||
:else "labels.viewer")
|
:else "labels.viewer")
|
||||||
|
|
||||||
on-show (mf/use-fn #(reset! show? true))
|
on-show (mf/use-fn #(reset! show? true))
|
||||||
on-hide (mf/use-fn #(reset! show? false))]
|
on-hide (mf/use-fn #(reset! show? false))]
|
||||||
[:*
|
[:*
|
||||||
@@ -321,8 +321,7 @@
|
|||||||
:class (stl/css :rol-dropdown-item)}
|
:class (stl/css :rol-dropdown-item)}
|
||||||
(tr "labels.owner")])]]]))
|
(tr "labels.owner")])]]]))
|
||||||
|
|
||||||
(mf/defc member-actions
|
(mf/defc member-actions*
|
||||||
{::mf/props :obj}
|
|
||||||
[{:keys [member team on-delete on-leave profile]}]
|
[{:keys [member team on-delete on-leave profile]}]
|
||||||
(let [is-owner? (:is-owner member)
|
(let [is-owner? (:is-owner member)
|
||||||
owner? (dm/get-in team [:permissions :is-owner])
|
owner? (dm/get-in team [:permissions :is-owner])
|
||||||
@@ -471,20 +470,20 @@
|
|||||||
[:& member-info {:member member :profile profile}]]
|
[:& member-info {:member member :profile profile}]]
|
||||||
|
|
||||||
[:div {:class (stl/css :table-field :field-roles)}
|
[:div {:class (stl/css :table-field :field-roles)}
|
||||||
[:& rol-info {:member member
|
[:> rol-info* {:member member
|
||||||
:team team
|
:team team
|
||||||
:on-set-admin on-set-admin
|
:on-set-admin on-set-admin
|
||||||
:on-set-editor on-set-editor
|
:on-set-editor on-set-editor
|
||||||
:on-set-viewer on-set-viewer
|
:on-set-viewer on-set-viewer
|
||||||
:on-set-owner on-set-owner
|
:on-set-owner on-set-owner
|
||||||
:profile profile}]]
|
:profile profile}]]
|
||||||
|
|
||||||
[:div {:class (stl/css :table-field :field-actions)}
|
[:div {:class (stl/css :table-field :field-actions)}
|
||||||
[:& member-actions {:member member
|
[:> member-actions* {:member member
|
||||||
:profile profile
|
:profile profile
|
||||||
:team team
|
:team team
|
||||||
:on-delete on-delete
|
:on-delete on-delete
|
||||||
:on-leave on-leave'}]]]))
|
:on-leave on-leave'}]]]))
|
||||||
|
|
||||||
(mf/defc team-members*
|
(mf/defc team-members*
|
||||||
{::mf/props :obj
|
{::mf/props :obj
|
||||||
@@ -541,21 +540,15 @@
|
|||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:& header {:section :dashboard-team-members :team team}]
|
[:& header {:section :dashboard-team-members :team team}]
|
||||||
[:section {:class (stl/css-case
|
[:section {:class (stl/css :dashboard-container :dashboard-team-members)}
|
||||||
:dashboard-container true
|
|
||||||
:dashboard-team-members true
|
|
||||||
:dashboard-top-cta (show-subscription-members-main-banner? team))}
|
|
||||||
(when (and (contains? cfg/flags :subscriptions)
|
|
||||||
(show-subscription-members-main-banner? team))
|
|
||||||
[:> members-cta* {:banner-is-expanded true :team team}])
|
|
||||||
[:> team-members*
|
[:> team-members*
|
||||||
{:profile profile
|
{:profile profile
|
||||||
:team team}]
|
:team team}]
|
||||||
|
|
||||||
(when (and
|
(when (and (contains? cfg/flags :subscriptions)
|
||||||
(contains? cfg/flags :subscriptions)
|
(show-subscription-members-banner? team profile))
|
||||||
(show-subscription-members-small-banner? team))
|
[:> members-cta* {:team team}])]])
|
||||||
[:> members-cta* {:banner-is-expanded false :team team}])]])
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; INVITATIONS SECTION
|
;; INVITATIONS SECTION
|
||||||
@@ -803,7 +796,7 @@
|
|||||||
|
|
||||||
(mf/defc team-invitations-page*
|
(mf/defc team-invitations-page*
|
||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
[{:keys [team]}]
|
[{:keys [team profile]}]
|
||||||
|
|
||||||
(mf/with-effect [team]
|
(mf/with-effect [team]
|
||||||
(dom/set-html-title
|
(dom/set-html-title
|
||||||
@@ -818,16 +811,13 @@
|
|||||||
[:*
|
[:*
|
||||||
[:& header {:section :dashboard-team-invitations
|
[:& header {:section :dashboard-team-invitations
|
||||||
:team team}]
|
:team team}]
|
||||||
[:section {:class (stl/css-case
|
[:section {:class (stl/css :dashboard-team-invitations)}
|
||||||
:dashboard-team-invitations true
|
|
||||||
:dashboard-top-cta (show-subscription-members-main-banner? team))}
|
|
||||||
(when (and (contains? cfg/flags :subscriptions)
|
|
||||||
(show-subscription-members-main-banner? team))
|
|
||||||
[:> members-cta* {:banner-is-expanded true :team team}])
|
|
||||||
[:> invitation-section* {:team team}]
|
[:> invitation-section* {:team team}]
|
||||||
|
|
||||||
(when (and (contains? cfg/flags :subscriptions)
|
(when (and (contains? cfg/flags :subscriptions)
|
||||||
(show-subscription-members-small-banner? team))
|
(show-subscription-members-banner? team profile))
|
||||||
[:> members-cta* {:banner-is-expanded false :team team}])]])
|
[:> members-cta* {:team team}])]])
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; WEBHOOKS SECTION
|
;; WEBHOOKS SECTION
|
||||||
|
|||||||
@@ -300,11 +300,6 @@
|
|||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-team-invitations.dashboard-top-cta {
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invitations {
|
.invitations {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
|
|||||||
@@ -8,15 +8,13 @@
|
|||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.repo :as rp]
|
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.forms :as fm]
|
[app.main.ui.components.forms :as fm]
|
||||||
[app.main.ui.dashboard.subscription :refer [get-subscription-type]]
|
[app.main.ui.dashboard.subscription :refer [get-subscription-type]]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr c]]
|
||||||
[beicon.v2.core :as rx]
|
|
||||||
[potok.v2.core :as ptk]
|
[potok.v2.core :as ptk]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
@@ -54,14 +52,15 @@
|
|||||||
:on-click cta-link} cta-text])
|
:on-click cta-link} cta-text])
|
||||||
(when (and cta-link-trial cta-text-trial) [:button {:class (stl/css :cta-button :bottom-link)
|
(when (and cta-link-trial cta-text-trial) [:button {:class (stl/css :cta-button :bottom-link)
|
||||||
:on-click cta-link-trial} cta-text-trial])])
|
:on-click cta-link-trial} cta-text-trial])])
|
||||||
(def ^:private schema:seats-form
|
(defn schema:seats-form [min-editors]
|
||||||
[:map {:title "SeatsForm"}
|
[:map {:title "SeatsForm"}
|
||||||
[:min-members [::sm/number {:min 1 :max 9999}]]])
|
[:min-members [::sm/number {:min min-editors
|
||||||
|
:max 9999}]]])
|
||||||
|
|
||||||
(mf/defc subscribe-management-dialog
|
(mf/defc subscribe-management-dialog
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
::mf/register-as :management-dialog}
|
::mf/register-as :management-dialog}
|
||||||
[{:keys [subscription-type current-subscription teams subscribe-to-trial]}]
|
[{:keys [subscription-type current-subscription editors subscribe-to-trial]}]
|
||||||
|
|
||||||
(let [subscription-name (if subscribe-to-trial
|
(let [subscription-name (if subscribe-to-trial
|
||||||
(if (= subscription-type "unlimited")
|
(if (= subscription-type "unlimited")
|
||||||
@@ -71,19 +70,24 @@
|
|||||||
"professional" (tr "subscription.settings.professional")
|
"professional" (tr "subscription.settings.professional")
|
||||||
"unlimited" (tr "subscription.settings.unlimited")
|
"unlimited" (tr "subscription.settings.unlimited")
|
||||||
"enterprise" (tr "subscription.settings.enterprise")))
|
"enterprise" (tr "subscription.settings.enterprise")))
|
||||||
initial (mf/with-memo []
|
min-editors (or (count editors) 1)
|
||||||
{:min-members (or (some->> teams (map :total-editors) (apply max)) 1)})
|
initial (mf/with-memo [min-editors]
|
||||||
form (fm/use-form :schema schema:seats-form
|
{:min-members min-editors})
|
||||||
|
form (fm/use-form :schema (schema:seats-form min-editors)
|
||||||
:initial initial)
|
:initial initial)
|
||||||
|
submit-in-progress* (mf/use-state false)
|
||||||
subscribe-to-unlimited (mf/use-fn
|
subscribe-to-unlimited (mf/use-fn
|
||||||
(fn [form]
|
(fn [form]
|
||||||
(let [data (:clean-data @form)
|
(when (not @submit-in-progress*)
|
||||||
return-url (-> (rt/get-current-href) (rt/encode-url))
|
(reset! submit-in-progress* true)
|
||||||
href (dm/str "payments/subscriptions/create?type=unlimited&quantity=" (:min-members data) "&returnUrl=" return-url)]
|
(let [data (:clean-data @form)
|
||||||
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
|
return-url (-> (rt/get-current-href) (rt/encode-url))
|
||||||
:type "unlimited"
|
href (dm/str "payments/subscriptions/create?type=unlimited&quantity=" (:min-members data) "&returnUrl=" return-url)]
|
||||||
:quantity (:min-members data)})
|
(reset! form nil)
|
||||||
(rt/nav-raw :href href)))))
|
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
|
||||||
|
:type "unlimited"
|
||||||
|
:quantity (:min-members data)})
|
||||||
|
(rt/nav-raw :href href))))))
|
||||||
|
|
||||||
subscribe-to-enterprise (mf/use-fn
|
subscribe-to-enterprise (mf/use-fn
|
||||||
(fn []
|
(fn []
|
||||||
@@ -106,7 +110,14 @@
|
|||||||
handle-close-dialog (mf/use-fn
|
handle-close-dialog (mf/use-fn
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
|
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
|
||||||
(modal/hide!)))]
|
(modal/hide!)))
|
||||||
|
|
||||||
|
show-editors-list* (mf/use-state false)
|
||||||
|
show-editors-list (deref show-editors-list*)
|
||||||
|
handle-click (mf/use-fn
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(swap! show-editors-list* not)))]
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-overlay)}
|
[:div {:class (stl/css :modal-overlay)}
|
||||||
[:div {:class (stl/css :modal-dialog)}
|
[:div {:class (stl/css :modal-dialog)}
|
||||||
@@ -115,15 +126,19 @@
|
|||||||
(tr "subscription.settings.management.dialog.title" subscription-name)]
|
(tr "subscription.settings.management.dialog.title" subscription-name)]
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-content)}
|
[:div {:class (stl/css :modal-content)}
|
||||||
(if (seq teams)
|
(when (seq editors)
|
||||||
[:* [:div {:class (stl/css :modal-text)}
|
[:* [:p {:class (stl/css :editors-text)}
|
||||||
(tr "subscription.settings.management.dialog.choose-this-plan")]
|
(tr "subscription.settings.management.dialog.currently-editors-title" (c (count editors)))]
|
||||||
[:ul {:class (stl/css :teams-list)}
|
[:button {:class (stl/css :cta-button :show-editors-button) :on-click handle-click}
|
||||||
(for [team teams]
|
(tr "subscription.settings.management.dialog.editors")
|
||||||
[:li {:key (dm/str (:id team)) :class (stl/css :team-name)}
|
[:span {:class (stl/css :icon-dropdown)} i/arrow]]
|
||||||
(:name team) (tr "subscription.settings.management.dialog.members" (:total-editors team))])]]
|
(when show-editors-list
|
||||||
[:div {:class (stl/css :modal-text)}
|
[:*
|
||||||
(tr "subscription.settings.management.dialog.no-teams")])
|
[:p {:class (stl/css :editors-text :editors-list-warning)}
|
||||||
|
(tr "subscription.settings.management.dialog.editors-explanation")]
|
||||||
|
[:ul {:class (stl/css :editors-list)}
|
||||||
|
(for [editor editors]
|
||||||
|
[:li {:key (dm/str (:id editor)) :class (stl/css :team-name)} "- " (:name editor)])]])])
|
||||||
|
|
||||||
(when (and
|
(when (and
|
||||||
(or (and (= subscription-type "professional") (contains? #{"unlimited" "enterprise"} (:type current-subscription)))
|
(or (and (= subscription-type "professional") (contains? #{"unlimited" "enterprise"} (:type current-subscription)))
|
||||||
@@ -138,8 +153,6 @@
|
|||||||
[:& fm/form {:on-submit subscribe-to-unlimited
|
[:& fm/form {:on-submit subscribe-to-unlimited
|
||||||
:class (stl/css :seats-form)
|
:class (stl/css :seats-form)
|
||||||
:form form}
|
:form form}
|
||||||
[:label {:for "editors-subscription" :class (stl/css :modal-text :editors-label)}
|
|
||||||
(tr "subscription.settings.management.dialog.select-editors")]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :editors-wrapper)}
|
[:div {:class (stl/css :editors-wrapper)}
|
||||||
[:div {:class (stl/css :fields-row)}
|
[:div {:class (stl/css :fields-row)}
|
||||||
@@ -149,7 +162,7 @@
|
|||||||
:label ""
|
:label ""
|
||||||
:class (stl/css :input-field)}]]
|
:class (stl/css :input-field)}]]
|
||||||
[:div {:class (stl/css :editors-cost)}
|
[:div {:class (stl/css :editors-cost)}
|
||||||
[:span {:class (stl/css :modal-text-small)}
|
[:span {:class (stl/css :modal-text-medium)}
|
||||||
(when (> (get-in @form [:clean-data :min-members]) 25)
|
(when (> (get-in @form [:clean-data :min-members]) 25)
|
||||||
[:> i18n/tr-html*
|
[:> i18n/tr-html*
|
||||||
{:class (stl/css :modal-text-cap)
|
{:class (stl/css :modal-text-cap)
|
||||||
@@ -160,9 +173,16 @@
|
|||||||
:tag-name "span"
|
:tag-name "span"
|
||||||
:content (tr "subscription.settings.management.dialog.price-month"
|
:content (tr "subscription.settings.management.dialog.price-month"
|
||||||
(* 7 (or (get-in @form [:clean-data :min-members]) 0)))}]]
|
(* 7 (or (get-in @form [:clean-data :min-members]) 0)))}]]
|
||||||
[:span {:class (stl/css :modal-text-small)}
|
[:span {:class (stl/css :modal-text-medium)}
|
||||||
(tr "subscription.settings.management.dialog.payment-explanation")]]]
|
(tr "subscription.settings.management.dialog.payment-explanation")]]]
|
||||||
|
|
||||||
|
(when (get-in @form [:errors :min-members])
|
||||||
|
[:div {:class (stl/css :error-message)}
|
||||||
|
(tr "subscription.settings.management.dialog.input-error")])
|
||||||
|
|
||||||
|
[:div {:class (stl/css :unlimited-capped-warning)}
|
||||||
|
(tr "subscription.settings.management.dialog.unlimited-capped-warning")]
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-footer)}
|
[:div {:class (stl/css :modal-footer)}
|
||||||
[:div {:class (stl/css :action-buttons)}
|
[:div {:class (stl/css :action-buttons)}
|
||||||
[:input
|
[:input
|
||||||
@@ -214,6 +234,8 @@
|
|||||||
|
|
||||||
[:div {:class (stl/css :modal-end)}
|
[:div {:class (stl/css :modal-end)}
|
||||||
[:div {:class (stl/css :modal-title)} (tr "subscription.settings.sucess.dialog.title" subscription-name)]
|
[:div {:class (stl/css :modal-title)} (tr "subscription.settings.sucess.dialog.title" subscription-name)]
|
||||||
|
(when (not= subscription-name "professional")
|
||||||
|
[:p {:class (stl/css :modal-text-large)} (tr "subscription.settings.success.dialog.thanks" subscription-name)])
|
||||||
[:p {:class (stl/css :modal-text-large)} (tr "subscription.settings.success.dialog.description")]
|
[:p {:class (stl/css :modal-text-large)} (tr "subscription.settings.success.dialog.description")]
|
||||||
[:p {:class (stl/css :modal-text-large)} (tr "subscription.settings.sucess.dialog.footer")]
|
[:p {:class (stl/css :modal-text-large)} (tr "subscription.settings.sucess.dialog.footer")]
|
||||||
|
|
||||||
@@ -229,9 +251,6 @@
|
|||||||
(let [route (mf/deref refs/route)
|
(let [route (mf/deref refs/route)
|
||||||
authenticated? (da/is-authenticated? profile)
|
authenticated? (da/is-authenticated? profile)
|
||||||
|
|
||||||
teams* (mf/use-state nil)
|
|
||||||
teams (deref teams*)
|
|
||||||
|
|
||||||
params-subscription
|
params-subscription
|
||||||
(-> route :params :query :subscription)
|
(-> route :params :query :subscription)
|
||||||
|
|
||||||
@@ -246,6 +265,9 @@
|
|||||||
success-modal-is-trial?
|
success-modal-is-trial?
|
||||||
(-> route :params :query :trial)
|
(-> route :params :query :trial)
|
||||||
|
|
||||||
|
subscription-editors
|
||||||
|
(-> profile :props :subscription :editors)
|
||||||
|
|
||||||
subscription
|
subscription
|
||||||
(-> profile :props :subscription)
|
(-> profile :props :subscription)
|
||||||
|
|
||||||
@@ -282,7 +304,7 @@
|
|||||||
|
|
||||||
open-subscription-modal
|
open-subscription-modal
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps teams)
|
(mf/deps subscription-editors)
|
||||||
(fn [subscription-type current-subscription]
|
(fn [subscription-type current-subscription]
|
||||||
(st/emit! (ev/event {::ev/name "open-subscription-modal"
|
(st/emit! (ev/event {::ev/name "open-subscription-modal"
|
||||||
::ev/origin "settings:in-app"}))
|
::ev/origin "settings:in-app"}))
|
||||||
@@ -290,12 +312,7 @@
|
|||||||
(modal/show :management-dialog
|
(modal/show :management-dialog
|
||||||
{:subscription-type subscription-type
|
{:subscription-type subscription-type
|
||||||
:current-subscription current-subscription
|
:current-subscription current-subscription
|
||||||
:teams teams :subscribe-to-trial (not subscription)}))))]
|
:editors subscription-editors :subscribe-to-trial (not (:type subscription))}))))]
|
||||||
|
|
||||||
(mf/with-effect []
|
|
||||||
(->> (rp/cmd! :get-owned-teams)
|
|
||||||
(rx/subs! (fn [teams]
|
|
||||||
(reset! teams* teams)))))
|
|
||||||
|
|
||||||
(mf/with-effect []
|
(mf/with-effect []
|
||||||
(dom/set-html-title (tr "subscription.labels")))
|
(dom/set-html-title (tr "subscription.labels")))
|
||||||
@@ -313,8 +330,8 @@
|
|||||||
"unlimited"
|
"unlimited"
|
||||||
"enterprise")
|
"enterprise")
|
||||||
:current-subscription subscription
|
:current-subscription subscription
|
||||||
:teams teams
|
:editors subscription-editors
|
||||||
:subscribe-to-trial (not subscription)})
|
:subscribe-to-trial (not (:type subscription))})
|
||||||
(rt/nav :settings-subscription {} {::rt/replace true}))
|
(rt/nav :settings-subscription {} {::rt/replace true}))
|
||||||
|
|
||||||
^boolean show-subscription-success-modal?
|
^boolean show-subscription-success-modal?
|
||||||
@@ -339,18 +356,18 @@
|
|||||||
(case subscription-type
|
(case subscription-type
|
||||||
"professional"
|
"professional"
|
||||||
[:> plan-card* {:card-title (tr "subscription.settings.professional")
|
[:> plan-card* {:card-title (tr "subscription.settings.professional")
|
||||||
:benefits [(tr "subscription.settings.professional.projects-files"),
|
:benefits [(tr "subscription.settings.professional.storage-benefit"),
|
||||||
(tr "subscription.settings.professional.teams-editors"),
|
(tr "subscription.settings.professional.autosave-benefit"),
|
||||||
(tr "subscription.settings.professional.storage-autosave")]}]
|
(tr "subscription.settings.professional.teams-editors-benefit")]}]
|
||||||
|
|
||||||
"unlimited"
|
"unlimited"
|
||||||
(if subscription-is-trial?
|
(if subscription-is-trial?
|
||||||
[:> plan-card* {:card-title (tr "subscription.settings.unlimited-trial")
|
[:> plan-card* {:card-title (tr "subscription.settings.unlimited-trial")
|
||||||
:card-title-icon i/character-u
|
:card-title-icon i/character-u
|
||||||
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
|
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits"),
|
||||||
:benefits [(tr "subscription.settings.unlimited.teams"),
|
:benefits [(tr "subscription.settings.unlimited.storage-benefit")
|
||||||
(tr "subscription.settings.unlimited.bill"),
|
(tr "subscription.settings.unlimited.autosave-benefit"),
|
||||||
(tr "subscription.settings.unlimited.storage-autosave")]
|
(tr "subscription.settings.unlimited.bill")]
|
||||||
:cta-text (tr "subscription.settings.manage-your-subscription")
|
:cta-text (tr "subscription.settings.manage-your-subscription")
|
||||||
:cta-link go-to-payments
|
:cta-link go-to-payments
|
||||||
:cta-text-trial (tr "subscription.settings.add-payment-to-continue")
|
:cta-text-trial (tr "subscription.settings.add-payment-to-continue")
|
||||||
@@ -360,9 +377,9 @@
|
|||||||
[:> plan-card* {:card-title (tr "subscription.settings.unlimited")
|
[:> plan-card* {:card-title (tr "subscription.settings.unlimited")
|
||||||
:card-title-icon i/character-u
|
:card-title-icon i/character-u
|
||||||
:benefits-title (tr "subscription.settings.benefits.all-unlimited-benefits")
|
:benefits-title (tr "subscription.settings.benefits.all-unlimited-benefits")
|
||||||
:benefits [(tr "subscription.settings.unlimited.teams"),
|
:benefits [(tr "subscription.settings.unlimited.storage-benefit"),
|
||||||
(tr "subscription.settings.unlimited.bill"),
|
(tr "subscription.settings.unlimited.autosave-benefit"),
|
||||||
(tr "subscription.settings.unlimited.storage-autosave")]
|
(tr "subscription.settings.unlimited.bill")]
|
||||||
:cta-text (tr "subscription.settings.manage-your-subscription")
|
:cta-text (tr "subscription.settings.manage-your-subscription")
|
||||||
:cta-link go-to-payments
|
:cta-link go-to-payments
|
||||||
:editors (-> profile :props :subscription :quantity)}])
|
:editors (-> profile :props :subscription :quantity)}])
|
||||||
@@ -371,18 +388,20 @@
|
|||||||
(if subscription-is-trial?
|
(if subscription-is-trial?
|
||||||
[:> plan-card* {:card-title (tr "subscription.settings.enterprise-trial")
|
[:> plan-card* {:card-title (tr "subscription.settings.enterprise-trial")
|
||||||
:card-title-icon i/character-e
|
:card-title-icon i/character-e
|
||||||
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
|
:benefits-title (tr "subscription.settings.benefits.all-unlimited-benefits"),
|
||||||
:benefits [(tr "subscription.settings.enterprise.unlimited-storage"),
|
:benefits [(tr "subscription.settings.enterprise.unlimited-storage-benefit"),
|
||||||
(tr "subscription.settings.enterprise.capped-bill"),
|
(tr "subscription.settings.enterprise.autosave"),
|
||||||
(tr "subscription.settings.enterprise.autosave")]
|
(tr "subscription.settings.enterprise.capped-bill")]
|
||||||
:cta-text (tr "subscription.settings.manage-your-subscription")
|
:cta-text (tr "subscription.settings.manage-your-subscription")
|
||||||
:cta-link go-to-payments}]
|
:cta-link go-to-payments
|
||||||
|
:cta-text-trial (tr "subscription.settings.add-payment-to-continue")
|
||||||
|
:cta-link-trial go-to-payments}]
|
||||||
[:> plan-card* {:card-title (tr "subscription.settings.enterprise")
|
[:> plan-card* {:card-title (tr "subscription.settings.enterprise")
|
||||||
:card-title-icon i/character-e
|
:card-title-icon i/character-e
|
||||||
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
|
:benefits-title (tr "subscription.settings.benefits.all-unlimited-benefits"),
|
||||||
:benefits [(tr "subscription.settings.enterprise.unlimited-storage"),
|
:benefits [(tr "subscription.settings.enterprise.unlimited-storage-benefit"),
|
||||||
(tr "subscription.settings.enterprise.capped-bill"),
|
(tr "subscription.settings.enterprise.autosave"),
|
||||||
(tr "subscription.settings.enterprise.autosave")]
|
(tr "subscription.settings.enterprise.capped-bill")]
|
||||||
:cta-text (tr "subscription.settings.manage-your-subscription")
|
:cta-text (tr "subscription.settings.manage-your-subscription")
|
||||||
:cta-link go-to-payments}]))
|
:cta-link go-to-payments}]))
|
||||||
|
|
||||||
@@ -404,9 +423,9 @@
|
|||||||
[:> plan-card* {:card-title (tr "subscription.settings.professional")
|
[:> plan-card* {:card-title (tr "subscription.settings.professional")
|
||||||
:price-value "$0"
|
:price-value "$0"
|
||||||
:price-period (tr "subscription.settings.price-editor-month")
|
:price-period (tr "subscription.settings.price-editor-month")
|
||||||
:benefits [(tr "subscription.settings.professional.projects-files"),
|
:benefits [(tr "subscription.settings.professional.storage-benefit"),
|
||||||
(tr "subscription.settings.professional.teams-editors"),
|
(tr "subscription.settings.professional.autosave-benefit"),
|
||||||
(tr "subscription.settings.professional.storage-autosave")]
|
(tr "subscription.settings.professional.teams-editors-benefit")]
|
||||||
:cta-text (tr "subscription.settings.subscribe")
|
:cta-text (tr "subscription.settings.subscribe")
|
||||||
:cta-link #(open-subscription-modal "professional")
|
:cta-link #(open-subscription-modal "professional")
|
||||||
:cta-text-with-icon (tr "subscription.settings.more-information")
|
:cta-text-with-icon (tr "subscription.settings.more-information")
|
||||||
@@ -418,10 +437,10 @@
|
|||||||
:price-value "$7"
|
:price-value "$7"
|
||||||
:price-period (tr "subscription.settings.price-editor-month")
|
:price-period (tr "subscription.settings.price-editor-month")
|
||||||
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
|
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
|
||||||
:benefits [(tr "subscription.settings.unlimited.teams"),
|
:benefits [(tr "subscription.settings.unlimited.storage-benefit"),
|
||||||
(tr "subscription.settings.unlimited.bill"),
|
(tr "subscription.settings.unlimited.autosave-benefit"),
|
||||||
(tr "subscription.settings.unlimited.storage-autosave")]
|
(tr "subscription.settings.unlimited.bill")]
|
||||||
:cta-text (if subscription (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
|
:cta-text (if (:type subscription) (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
|
||||||
:cta-link #(open-subscription-modal "unlimited" subscription)
|
:cta-link #(open-subscription-modal "unlimited" subscription)
|
||||||
:cta-text-with-icon (tr "subscription.settings.more-information")
|
:cta-text-with-icon (tr "subscription.settings.more-information")
|
||||||
:cta-link-with-icon go-to-pricing-page}])
|
:cta-link-with-icon go-to-pricing-page}])
|
||||||
@@ -432,10 +451,10 @@
|
|||||||
:price-value "$950"
|
:price-value "$950"
|
||||||
:price-period (tr "subscription.settings.price-organization-month")
|
:price-period (tr "subscription.settings.price-organization-month")
|
||||||
:benefits-title (tr "subscription.settings.benefits.all-unlimited-benefits")
|
:benefits-title (tr "subscription.settings.benefits.all-unlimited-benefits")
|
||||||
:benefits [(tr "subscription.settings.enterprise.unlimited-storage"),
|
:benefits [(tr "subscription.settings.enterprise.unlimited-storage-benefit"),
|
||||||
(tr "subscription.settings.enterprise.capped-bill"),
|
(tr "subscription.settings.enterprise.autosave"),
|
||||||
(tr "subscription.settings.enterprise.autosave")]
|
(tr "subscription.settings.enterprise.capped-bill")]
|
||||||
:cta-text (if subscription (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
|
:cta-text (if (:type subscription) (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
|
||||||
:cta-link #(open-subscription-modal "enterprise" subscription)
|
:cta-link #(open-subscription-modal "enterprise" subscription)
|
||||||
:cta-text-with-icon (tr "subscription.settings.more-information")
|
:cta-text-with-icon (tr "subscription.settings.more-information")
|
||||||
:cta-link-with-icon go-to-pricing-page}])]]]))
|
:cta-link-with-icon go-to-pricing-page}])]]]))
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
.title-section {
|
.title-section {
|
||||||
@include t.use-typography("title-large");
|
@include t.use-typography("title-large");
|
||||||
color: var(--color-foreground-primary);
|
color: var(--color-foreground-primary);
|
||||||
margin-block-end: var(--sp-l);
|
margin-block-end: var(--sp-xxl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-section-title {
|
.plan-section-title {
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
.plan-card-header {
|
.plan-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-block-end: var(--sp-s);
|
margin-block-end: var(--sp-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-card-title-container {
|
.plan-card-title-container {
|
||||||
@@ -135,12 +135,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.other-subscriptions {
|
.other-subscriptions {
|
||||||
margin-block-start: $s-36;
|
margin-block-start: $s-52;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button {
|
.cta-button {
|
||||||
@include t.use-typography("body-medium");
|
@include t.use-typography("body-medium");
|
||||||
@include buttonStyle;
|
@include buttonStyle;
|
||||||
|
align-items: center;
|
||||||
color: var(--color-accent-tertiary);
|
color: var(--color-accent-tertiary);
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-block-start: var(--sp-m);
|
margin-block-start: var(--sp-m);
|
||||||
@@ -155,6 +156,10 @@
|
|||||||
margin-inline-start: var(--sp-xs);
|
margin-inline-start: var(--sp-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-dropdown svg {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-link {
|
.bottom-link {
|
||||||
margin-block-start: var(--sp-xs);
|
margin-block-start: var(--sp-xs);
|
||||||
}
|
}
|
||||||
@@ -168,11 +173,11 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
max-height: initial;
|
max-height: initial;
|
||||||
min-width: $s-520;
|
min-width: $s-548;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog.subscription-success {
|
.modal-dialog.subscription-success {
|
||||||
min-width: $s-612;
|
min-width: $s-648;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
@@ -191,12 +196,8 @@
|
|||||||
margin-block-end: var(--sp-l);
|
margin-block-end: var(--sp-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-text-lage {
|
.modal-text-medium {
|
||||||
@include t.use-typography("body-large");
|
@include t.use-typography("body-medium");
|
||||||
}
|
|
||||||
|
|
||||||
.modal-text-small {
|
|
||||||
@include t.use-typography("body-small");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-text-cap {
|
.modal-text-cap {
|
||||||
@@ -207,7 +208,7 @@
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-text-small strong,
|
.modal-text-medium strong,
|
||||||
.text-strikethrough strong,
|
.text-strikethrough strong,
|
||||||
.modal-text-cap strong {
|
.modal-text-cap strong {
|
||||||
font-weight: $fw700;
|
font-weight: $fw700;
|
||||||
@@ -260,11 +261,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.teams-list {
|
.editors-text {
|
||||||
list-style-position: inside;
|
@include t.use-typography("body-medium");
|
||||||
list-style-type: disc;
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editors-list-warning {
|
||||||
|
margin-inline-start: var(--sp-xl);
|
||||||
|
margin-block: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editors-list {
|
||||||
|
@include t.use-typography("body-medium");
|
||||||
|
list-style-position: inside;
|
||||||
|
list-style-type: none;
|
||||||
margin-inline-start: var(--sp-xl);
|
margin-inline-start: var(--sp-xl);
|
||||||
margin-block: var(--sp-xxl);
|
|
||||||
max-height: $s-216;
|
max-height: $s-216;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -274,11 +285,14 @@
|
|||||||
width: $s-80;
|
width: $s-80;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editors-label {
|
.error-message {
|
||||||
margin-block-start: var(--sp-xxl);
|
@include t.use-typography("body-small");
|
||||||
|
color: var(--color-foreground-error);
|
||||||
|
margin-block-start: $s-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editors-wrapper {
|
.editors-wrapper {
|
||||||
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--sp-xl);
|
gap: var(--sp-xl);
|
||||||
margin-block-start: var(--sp-l);
|
margin-block-start: var(--sp-l);
|
||||||
@@ -288,3 +302,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unlimited-capped-warning {
|
||||||
|
@include t.use-typography("body-small");
|
||||||
|
background-color: var(--color-background-tertiary);
|
||||||
|
border-radius: var(--sp-s);
|
||||||
|
margin-block-start: $s-40;
|
||||||
|
padding-block: var(--sp-s);
|
||||||
|
padding-inline: var(--sp-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-editors-button {
|
||||||
|
padding-inline: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -501,16 +501,16 @@
|
|||||||
profile (mf/deref refs/profile)
|
profile (mf/deref refs/profile)
|
||||||
|
|
||||||
auth-error? (= type :authentication)
|
auth-error? (= type :authentication)
|
||||||
|
not-found? (= type :not-found)
|
||||||
|
|
||||||
authenticated?
|
authenticated?
|
||||||
(is-authenticated? profile)
|
(is-authenticated? profile)
|
||||||
|
|
||||||
request-access?
|
request-access?
|
||||||
(and
|
(and
|
||||||
(or (= type :not-found) auth-error?)
|
|
||||||
(or workspace? dashboard? view?)
|
(or workspace? dashboard? view?)
|
||||||
(or (:file-id info)
|
(or (some? (:file-id info))
|
||||||
(:team-id info)))]
|
(some? (:team-id info))))]
|
||||||
|
|
||||||
(mf/with-effect [params info]
|
(mf/with-effect [params info]
|
||||||
(when-not (:loaded info)
|
(when-not (:loaded info)
|
||||||
@@ -518,25 +518,26 @@
|
|||||||
(rx/subs! (partial reset! info*)
|
(rx/subs! (partial reset! info*)
|
||||||
(partial reset! info* {:loaded true})))))
|
(partial reset! info* {:loaded true})))))
|
||||||
|
|
||||||
(if (and auth-error? (not authenticated?))
|
|
||||||
[:> context-wrapper*
|
|
||||||
{:is-workspace workspace?
|
|
||||||
:is-dashboard dashboard?
|
|
||||||
:is-viewer view?
|
|
||||||
:profile profile}
|
|
||||||
[:> login-dialog* {}]]
|
|
||||||
|
|
||||||
(when (get info :loaded false)
|
(if (or auth-error? not-found?)
|
||||||
(if request-access?
|
(if (not authenticated?)
|
||||||
[:> context-wrapper* {:is-workspace workspace?
|
[:> context-wrapper*
|
||||||
:is-dashboard dashboard?
|
{:is-workspace workspace?
|
||||||
:is-viewer view?
|
:is-dashboard dashboard?
|
||||||
:profile profile}
|
:is-viewer view?
|
||||||
[:> request-access* {:file-id (:file-id info)
|
:profile profile}
|
||||||
:team-id (:team-id info)
|
[:> login-dialog* {}]]
|
||||||
:is-default (:team-default info)
|
(when (get info :loaded false)
|
||||||
:profile profile
|
(if request-access?
|
||||||
:is-workspace workspace?}]]
|
[:> context-wrapper* {:is-workspace workspace?
|
||||||
|
:is-dashboard dashboard?
|
||||||
[:> exception-section* props])))))
|
:is-viewer view?
|
||||||
|
:profile profile}
|
||||||
|
[:> request-access* {:file-id (:file-id info)
|
||||||
|
:team-id (:team-id info)
|
||||||
|
:is-default (:team-default info)
|
||||||
|
:profile profile
|
||||||
|
:is-workspace workspace?}]]
|
||||||
|
[:> exception-section* props])))
|
||||||
|
|
||||||
|
[:> exception-section* props])))
|
||||||
|
|||||||
@@ -4302,50 +4302,14 @@ msgstr "Zoom lense increase"
|
|||||||
msgid "shortcuts.zoom-selected"
|
msgid "shortcuts.zoom-selected"
|
||||||
msgstr "Zoom to selected"
|
msgstr "Zoom to selected"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:154
|
|
||||||
msgid "subscription.dashboard.cta.professional-plan-designed"
|
|
||||||
msgstr ""
|
|
||||||
"The Professional plan is designed for teams of up to 8 editors (owner, "
|
|
||||||
"admin, and editor)."
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:160
|
|
||||||
msgid "subscription.dashboard.cta.unlimited-many-editors"
|
|
||||||
msgstr ""
|
|
||||||
"Looks like your team has grown! Your plan includes %s seats, but you're now using %s"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:168
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-member"
|
|
||||||
msgstr ""
|
|
||||||
"Get more editors, more storage, and more autosaved versions with the "
|
|
||||||
"Unlimited or Enterprise plan. Contact with the team owner: %s"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:165
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner"
|
|
||||||
msgstr ""
|
|
||||||
"Get more editors, more storage, and more autosaved versions with the "
|
|
||||||
"Unlimited or Enterprise plan. [Subscribe now.|target:self](%s)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:176
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-more-seats-owner"
|
|
||||||
msgstr "Please upgrade to match your usage. [Subscribe now.|target:self](%s)"
|
|
||||||
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-more-seats-member"
|
|
||||||
msgstr "Please upgrade to match your usage. Contact with the team owner: %s"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:80
|
#: src/app/main/ui/dashboard/subscription.cljs:80
|
||||||
msgid "subscription.dashboard.power-up.enterprise-plan"
|
msgid "subscription.dashboard.power-up.enterprise-plan"
|
||||||
msgstr "Enterprise plan"
|
msgstr "Enterprise plan"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:60
|
#: src/app/main/ui/dashboard/subscription.cljs:60
|
||||||
#, markdown
|
#, markdown
|
||||||
msgid "subscription.dashboard.power-up.professional.bottom"
|
msgid "subscription.dashboard.power-up.professional.bottom-text"
|
||||||
msgstr ""
|
msgstr "Get extra storage, file recovery and more for your teams with the Unlimited plan. [Power up!|target:self](%s)"
|
||||||
"Get extra editors and storage, file recovery and more with the Unlimited "
|
|
||||||
"plan. [Power up|target:self](%s)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:59
|
#: src/app/main/ui/dashboard/subscription.cljs:59
|
||||||
msgid "subscription.dashboard.power-up.professional.top-title"
|
msgid "subscription.dashboard.power-up.professional.top-title"
|
||||||
@@ -4361,11 +4325,6 @@ msgstr "Subscribe"
|
|||||||
msgid "subscription.dashboard.power-up.trial.bottom-description"
|
msgid "subscription.dashboard.power-up.trial.bottom-description"
|
||||||
msgstr "Enjoying your trial? Unlock full access forever.[Subscribe|target:self](%s)"
|
msgstr "Enjoying your trial? Unlock full access forever.[Subscribe|target:self](%s)"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:62
|
|
||||||
#, unused
|
|
||||||
msgid "subscription.dashboard.power-up.trial.top-description"
|
|
||||||
msgstr "Extra editors, storage and autosaved version, file backup and more."
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:67
|
#: src/app/main/ui/dashboard/subscription.cljs:67
|
||||||
msgid "subscription.dashboard.power-up.trial.top-title"
|
msgid "subscription.dashboard.power-up.trial.top-title"
|
||||||
msgstr "Unlimited plan (trial)"
|
msgstr "Unlimited plan (trial)"
|
||||||
@@ -4380,9 +4339,7 @@ msgstr "Enterprise plan (trial)"
|
|||||||
#: src/app/main/ui/dashboard/subscription.cljs:74
|
#: src/app/main/ui/dashboard/subscription.cljs:74
|
||||||
#, markdown
|
#, markdown
|
||||||
msgid "subscription.dashboard.power-up.unlimited.bottom-text"
|
msgid "subscription.dashboard.power-up.unlimited.bottom-text"
|
||||||
msgstr ""
|
msgstr "Get unlimited storage, extended file recovery and unlimited editors for all your teams at a fixed price. [Take a look at the Enterprise plan.|target:self](%s)"
|
||||||
"Get extra editors, more backup, unlimited storage and more. "
|
|
||||||
"[Take a look to the Enterprise plan.|target:self](%s)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:70
|
#: src/app/main/ui/dashboard/subscription.cljs:70
|
||||||
#, unused
|
#, unused
|
||||||
@@ -4406,6 +4363,26 @@ msgstr "Team plan"
|
|||||||
msgid "subscription.dashboard.upgrade-plan.power-up"
|
msgid "subscription.dashboard.upgrade-plan.power-up"
|
||||||
msgstr "Power up"
|
msgstr "Power up"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.professional-dashboard-cta-title"
|
||||||
|
msgstr "You have %s editors across your owned teams, while your professional plan covers up to 8."
|
||||||
|
|
||||||
|
#, markdown
|
||||||
|
msgid "subscription.dashboard.professional-dashboard-cta-upgrade-owner"
|
||||||
|
msgstr "Please upgrade now to Unlimited or Enterprise to unlock more editors, storage and file recovery. [Subscribe now.|target:self](%s)"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.unlimited-dashboard-cta-title"
|
||||||
|
msgstr "Your team keeps growing! Your Unlimited plan covers up to %s editors, but you now have %s."
|
||||||
|
|
||||||
|
#, markdown
|
||||||
|
msgid "subscription.dashboard.unlimited-dashboard-cta-upgrade-owner"
|
||||||
|
msgstr "Please upgrade now to match your current editor count. [Subscribe now.|target:self](%s)"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.unlimited-members-extra-editors-cta-title"
|
||||||
|
msgstr "Inviting people while on the Unlimited plan"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.unlimited-members-extra-editors-cta-text"
|
||||||
|
msgstr "Only new editors across your owned teams count towards future billing. A flat $175/month still applies for 25+ editors."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/sidebar.cljs:116, src/app/main/ui/settings/subscription.cljs:222, src/app/main/ui/settings/subscription.cljs:231
|
#: src/app/main/ui/settings/sidebar.cljs:116, src/app/main/ui/settings/subscription.cljs:222, src/app/main/ui/settings/subscription.cljs:231
|
||||||
msgid "subscription.labels"
|
msgid "subscription.labels"
|
||||||
msgstr "Subscription"
|
msgstr "Subscription"
|
||||||
@@ -4431,23 +4408,31 @@ msgid "subscription.settings.enterprise-trial"
|
|||||||
msgstr "Enterprise (trial)"
|
msgstr "Enterprise (trial)"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320
|
#: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320
|
||||||
msgid "subscription.settings.enterprise.unlimited-storage"
|
msgid "subscription.settings.enterprise.unlimited-storage-benefit"
|
||||||
msgstr "Unlimited storage and 90-day autosave versions and file recovery"
|
msgstr "Unlimited storage"
|
||||||
|
|
||||||
msgid "subscription.settings.enterprise.autosave"
|
msgid "subscription.settings.enterprise.autosave"
|
||||||
msgstr "90-day autosave versions and file recovery"
|
msgstr "90-day autosave versions and file recovery"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:269, src/app/main/ui/settings/subscription.cljs:318
|
#: src/app/main/ui/settings/subscription.cljs:269, src/app/main/ui/settings/subscription.cljs:318
|
||||||
msgid "subscription.settings.enterprise.capped-bill"
|
msgid "subscription.settings.enterprise.capped-bill"
|
||||||
msgstr "Capped monthly bill"
|
msgstr "Flat monthly bill"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:114, src/app/main/ui/settings/subscription.cljs:251, src/app/main/ui/settings/subscription.cljs:262, src/app/main/ui/settings/subscription.cljs:272
|
#: src/app/main/ui/dashboard/subscription.cljs:114, src/app/main/ui/settings/subscription.cljs:251, src/app/main/ui/settings/subscription.cljs:262, src/app/main/ui/settings/subscription.cljs:272
|
||||||
msgid "subscription.settings.manage-your-subscription"
|
msgid "subscription.settings.manage-your-subscription"
|
||||||
msgstr "Manage your subscription"
|
msgstr "Manage your subscription"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:102
|
#: src/app/main/ui/settings/subscription.cljs:102
|
||||||
msgid "subscription.settings.management.dialog.choose-this-plan"
|
msgid "subscription.settings.management.dialog.currently-editors-title"
|
||||||
msgstr "You are choosing this plan for:"
|
msgid_plural "subscription.settings.management.dialog.currently-editors-title"
|
||||||
|
msgstr[0] "Currently, your have %s person across your teams who can edit."
|
||||||
|
msgstr[1] "Currently, your have %s people across your teams who can edit."
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.editors"
|
||||||
|
msgstr "Editors"
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.editors-explanation"
|
||||||
|
msgstr "(Owners, Admin and Editors. Viewers doesn't count as Editors)"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:132
|
#: src/app/main/ui/settings/subscription.cljs:132
|
||||||
msgid "subscription.settings.management.dialog.downgrade"
|
msgid "subscription.settings.management.dialog.downgrade"
|
||||||
@@ -4455,27 +4440,21 @@ msgstr ""
|
|||||||
"Heads up: switching to a lower plan means less storage and shorter backups "
|
"Heads up: switching to a lower plan means less storage and shorter backups "
|
||||||
"and version history."
|
"and version history."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:106
|
|
||||||
msgid "subscription.settings.management.dialog.members"
|
|
||||||
msgstr " (%s editors)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:108
|
|
||||||
msgid "subscription.settings.management.dialog.no-teams"
|
|
||||||
msgstr "This plan will apply to all future teams you create or own."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:126
|
#: src/app/main/ui/settings/subscription.cljs:126
|
||||||
msgid "subscription.settings.management.dialog.payment-explanation"
|
msgid "subscription.settings.management.dialog.payment-explanation"
|
||||||
msgstr "(No payment will be made now)"
|
msgstr "Charged after trial. No credit card required right now."
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.input-error"
|
||||||
|
msgstr "You can't set fewer editors than you have now. Change the role (editor/admin to viewer) for people who don't actually edit files in the team settings."
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.unlimited-capped-warning"
|
||||||
|
msgstr "Tip: You can increase your seat count now to stay ahead of invites. At 25+ editors across teams, you’ll enjoy a flat $175/month."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:124
|
#: src/app/main/ui/settings/subscription.cljs:124
|
||||||
#, markdown
|
#, markdown
|
||||||
msgid "subscription.settings.management.dialog.price-month"
|
msgid "subscription.settings.management.dialog.price-month"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"**$%s** per month"
|
"**$%s**/month"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:112
|
|
||||||
msgid "subscription.settings.management.dialog.select-editors"
|
|
||||||
msgstr "Select number of editors (seats) you need:"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:97
|
#: src/app/main/ui/settings/subscription.cljs:97
|
||||||
msgid "subscription.settings.management.dialog.title"
|
msgid "subscription.settings.management.dialog.title"
|
||||||
@@ -4506,16 +4485,16 @@ msgid "subscription.settings.professional"
|
|||||||
msgstr "Professional"
|
msgstr "Professional"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:239, src/app/main/ui/settings/subscription.cljs:290
|
#: src/app/main/ui/settings/subscription.cljs:239, src/app/main/ui/settings/subscription.cljs:290
|
||||||
msgid "subscription.settings.professional.projects-files"
|
msgid "subscription.settings.professional.autosave-benefit"
|
||||||
msgstr "Unlimited projects, files and drafts"
|
msgstr "7-day autosave versions and file recovery"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:241, src/app/main/ui/settings/subscription.cljs:292
|
#: src/app/main/ui/settings/subscription.cljs:241, src/app/main/ui/settings/subscription.cljs:292
|
||||||
msgid "subscription.settings.professional.storage-autosave"
|
msgid "subscription.settings.professional.storage-benefit"
|
||||||
msgstr "10GB of storage and 7-day autosave versions and file recovery"
|
msgstr "10GB of storage"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:240, src/app/main/ui/settings/subscription.cljs:291
|
#: src/app/main/ui/settings/subscription.cljs:240, src/app/main/ui/settings/subscription.cljs:291
|
||||||
msgid "subscription.settings.professional.teams-editors"
|
msgid "subscription.settings.professional.teams-editors-benefit"
|
||||||
msgstr "Unlimited teams of up to 8 editors"
|
msgstr "Unlimited teams. Up to 8 editors across your owned teams."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:235
|
#: src/app/main/ui/settings/subscription.cljs:235
|
||||||
msgid "subscription.settings.section-plan"
|
msgid "subscription.settings.section-plan"
|
||||||
@@ -4532,6 +4511,9 @@ msgstr "Start free trial"
|
|||||||
msgid "subscription.settings.subscribe"
|
msgid "subscription.settings.subscribe"
|
||||||
msgstr "Subscribe"
|
msgstr "Subscribe"
|
||||||
|
|
||||||
|
msgid "subscription.settings.success.dialog.thanks"
|
||||||
|
msgstr "Thank your for chosing the Penpot %s plan!"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:167
|
#: src/app/main/ui/settings/subscription.cljs:167
|
||||||
msgid "subscription.settings.success.dialog.description"
|
msgid "subscription.settings.success.dialog.description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4565,15 +4547,15 @@ msgstr "Unlimited (trial)"
|
|||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305
|
#: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305
|
||||||
msgid "subscription.settings.unlimited.bill"
|
msgid "subscription.settings.unlimited.bill"
|
||||||
msgstr "Capped monthly bill"
|
msgstr "Capped monthly bill at $175"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:250, src/app/main/ui/settings/subscription.cljs:261, src/app/main/ui/settings/subscription.cljs:306
|
#: src/app/main/ui/settings/subscription.cljs:250, src/app/main/ui/settings/subscription.cljs:261, src/app/main/ui/settings/subscription.cljs:306
|
||||||
msgid "subscription.settings.unlimited.storage-autosave"
|
msgid "subscription.settings.unlimited.storage-benefit"
|
||||||
msgstr "25GB of storage and 30-day autosave versions and file recovery"
|
msgstr "25GB of storage"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:248, src/app/main/ui/settings/subscription.cljs:259, src/app/main/ui/settings/subscription.cljs:304
|
#: src/app/main/ui/settings/subscription.cljs:248, src/app/main/ui/settings/subscription.cljs:259, src/app/main/ui/settings/subscription.cljs:304
|
||||||
msgid "subscription.settings.unlimited.teams"
|
msgid "subscription.settings.unlimited.autosave-benefit"
|
||||||
msgstr "Unlimited teams, no matter your team size"
|
msgstr "30-day autosave versions and file recovery"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:133, src/app/main/ui/workspace/main_menu.cljs:928
|
#: src/app/main/ui/dashboard/subscription.cljs:133, src/app/main/ui/workspace/main_menu.cljs:928
|
||||||
msgid "subscription.workspace.header.menu.option.power-up"
|
msgid "subscription.workspace.header.menu.option.power-up"
|
||||||
|
|||||||
@@ -4305,52 +4305,14 @@ msgstr "Incrementar zoom a objetivo"
|
|||||||
msgid "shortcuts.zoom-selected"
|
msgid "shortcuts.zoom-selected"
|
||||||
msgstr "Zoom a selección"
|
msgstr "Zoom a selección"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:154
|
|
||||||
msgid "subscription.dashboard.cta.professional-plan-designed"
|
|
||||||
msgstr ""
|
|
||||||
"El plan Professional está diseñado para equipos de hasta 8 editores "
|
|
||||||
"(propietario, administrador y editor)."
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:160
|
|
||||||
msgid "subscription.dashboard.cta.unlimited-many-editors"
|
|
||||||
msgstr ""
|
|
||||||
"¡Parece que tu equipo ha crecido! Tu plan incluye %s asientos, pero ahora estás usando %s"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:168
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-member"
|
|
||||||
msgstr ""
|
|
||||||
"Consigue más editores, más almacenamiento y más versiones guardadas "
|
|
||||||
"automáticamente con el plan Unlimited o Enterprise. Contacta con el "
|
|
||||||
"propietario del equipo: %s"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:165
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner"
|
|
||||||
msgstr ""
|
|
||||||
"Consigue más editores, más almacenamiento y más versiones guardadas "
|
|
||||||
"automáticamente con el plan Unlimited o Enterprise. [Suscríbete ahora.|target:self](%s)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:176
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-more-seats-owner"
|
|
||||||
msgstr "Por favor, mejóralo para adaptarlo a tu uso. [Suscríbete ahora.|target:self](%s)"
|
|
||||||
|
|
||||||
#, markdown
|
|
||||||
msgid "subscription.dashboard.cta.upgrade-more-seats-member"
|
|
||||||
msgstr "Por favor, mejóralo para adaptarlo a tu uso. Contacta con el "
|
|
||||||
"propietario del equipo: %s"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:80
|
#: src/app/main/ui/dashboard/subscription.cljs:80
|
||||||
msgid "subscription.dashboard.power-up.enterprise-plan"
|
msgid "subscription.dashboard.power-up.enterprise-plan"
|
||||||
msgstr "Plan Enterprise"
|
msgstr "Plan Enterprise"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:60
|
#: src/app/main/ui/dashboard/subscription.cljs:60
|
||||||
#, markdown
|
#, markdown
|
||||||
msgid "subscription.dashboard.power-up.professional.bottom"
|
msgid "subscription.dashboard.power-up.professional.bottom-text"
|
||||||
msgstr ""
|
msgstr "Consigue almacenamiento adicional, recuperación de archivos y mucho más para tus equipos con el Plan Unlimited. [Mejóralo!|target:self](%s)"
|
||||||
"Consigue editores y almacenamiento adicionales, recuperación de "
|
|
||||||
"archivos y mucho más con el Plan Unlimited[Mejóralo|target:self](%s)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:59
|
#: src/app/main/ui/dashboard/subscription.cljs:59
|
||||||
msgid "subscription.dashboard.power-up.professional.top-title"
|
msgid "subscription.dashboard.power-up.professional.top-title"
|
||||||
@@ -4368,13 +4330,6 @@ msgstr ""
|
|||||||
"¿Disfrutas de la prueba? Desbloquea el acceso completo para "
|
"¿Disfrutas de la prueba? Desbloquea el acceso completo para "
|
||||||
"siempre.[Suscríbete|target:self](%s)"
|
"siempre.[Suscríbete|target:self](%s)"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:62
|
|
||||||
#, unused
|
|
||||||
msgid "subscription.dashboard.power-up.trial.top-description"
|
|
||||||
msgstr ""
|
|
||||||
"Editores adicionales, almacenamiento y versión autoguardada, copia de "
|
|
||||||
"seguridad de archivos y mucho más."
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:67
|
#: src/app/main/ui/dashboard/subscription.cljs:67
|
||||||
msgid "subscription.dashboard.power-up.trial.top-title"
|
msgid "subscription.dashboard.power-up.trial.top-title"
|
||||||
msgstr "Plan Unlimited (Prueba)"
|
msgstr "Plan Unlimited (Prueba)"
|
||||||
@@ -4389,9 +4344,7 @@ msgstr "Plan Unlimited"
|
|||||||
#: src/app/main/ui/dashboard/subscription.cljs:74
|
#: src/app/main/ui/dashboard/subscription.cljs:74
|
||||||
#, markdown
|
#, markdown
|
||||||
msgid "subscription.dashboard.power-up.unlimited.bottom-text"
|
msgid "subscription.dashboard.power-up.unlimited.bottom-text"
|
||||||
msgstr ""
|
msgstr "Consigue almacenamiento ilimitado, recuperación extendida de archivos y editores ilimitados para todos tus equipos a un precio fijo. [Echa un ojo al Plan Enterprise|target:self](%s)"
|
||||||
"Consigue editores adicionales, copias de seguridad, almacenamiento ilimitado y mucho más. "
|
|
||||||
"[Echa un ojo al Plan Enterprise|target:self](%s)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:70
|
#: src/app/main/ui/dashboard/subscription.cljs:70
|
||||||
#, unused
|
#, unused
|
||||||
@@ -4417,6 +4370,26 @@ msgstr "Plan de equipo"
|
|||||||
msgid "subscription.dashboard.upgrade-plan.power-up"
|
msgid "subscription.dashboard.upgrade-plan.power-up"
|
||||||
msgstr "Mejora"
|
msgstr "Mejora"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.professional-dashboard-cta-title"
|
||||||
|
msgstr "Tienes %s editores en todos tus equipos pero que tu plan profesional cubre hasta 8."
|
||||||
|
|
||||||
|
#, markdown
|
||||||
|
msgid "subscription.dashboard.professional-dashboard-cta-upgrade-owner"
|
||||||
|
msgstr "Mejora ahora tu plan a Unlimited o Enterprise para desbloquear más editores, almacenamiento y recuperación de archivos. [Suscríbete ahora.|target:self](%s)"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.unlimited-dashboard-cta-title"
|
||||||
|
msgstr "¡Tu equipo sigue creciendo! Tu plan Unlimited cubre hasta %s editores pero ya tienes %s."
|
||||||
|
|
||||||
|
#, markdown
|
||||||
|
msgid "subscription.dashboard.unlimited-dashboard-cta-upgrade-owner"
|
||||||
|
msgstr "Por favor, actualiza ahora para ajustar el número actual de editores. [Suscríbete ahora.|target:self](%s)"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.unlimited-members-extra-editors-cta-title"
|
||||||
|
msgstr "Invita a personas mientras estás en el plan Unlimited"
|
||||||
|
|
||||||
|
msgid "subscription.dashboard.unlimited-members-extra-editors-cta-text"
|
||||||
|
msgstr "Solo los nuevos editores de tus equipos se tendrán en cuenta para la facturación futura. Se seguirá aplicando una tarifa plana de 175$/mes para 25+ editores."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/sidebar.cljs:116, src/app/main/ui/settings/subscription.cljs:222, src/app/main/ui/settings/subscription.cljs:231
|
#: src/app/main/ui/settings/sidebar.cljs:116, src/app/main/ui/settings/subscription.cljs:222, src/app/main/ui/settings/subscription.cljs:231
|
||||||
msgid "subscription.labels"
|
msgid "subscription.labels"
|
||||||
msgstr "Suscripción"
|
msgstr "Suscripción"
|
||||||
@@ -4442,23 +4415,31 @@ msgid "subscription.settings.enterprise-trial"
|
|||||||
msgstr "Enterprise (prueba)"
|
msgstr "Enterprise (prueba)"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320
|
#: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320
|
||||||
msgid "subscription.settings.enterprise.unlimited-storage"
|
msgid "subscription.settings.enterprise.unlimited-storage-benefit"
|
||||||
msgstr "Almacenamiento ilimitado y versiones de autoguardado de 90 días y recuperación de archivos"
|
msgstr "Almacenamiento ilimitado"
|
||||||
|
|
||||||
msgid "subscription.settings.enterprise.autosave"
|
msgid "subscription.settings.enterprise.autosave"
|
||||||
msgstr "Versiones guardadas automáticamente cada 90 días y recuperación de archivos"
|
msgstr "Versiones guardadas automáticamente cada 90 días y recuperación de archivos"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:269, src/app/main/ui/settings/subscription.cljs:318
|
#: src/app/main/ui/settings/subscription.cljs:269, src/app/main/ui/settings/subscription.cljs:318
|
||||||
msgid "subscription.settings.enterprise.capped-bill"
|
msgid "subscription.settings.enterprise.capped-bill"
|
||||||
msgstr "Factura mensual limitada"
|
msgstr "Factura mensual fija"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:114, src/app/main/ui/settings/subscription.cljs:251, src/app/main/ui/settings/subscription.cljs:262, src/app/main/ui/settings/subscription.cljs:272
|
#: src/app/main/ui/dashboard/subscription.cljs:114, src/app/main/ui/settings/subscription.cljs:251, src/app/main/ui/settings/subscription.cljs:262, src/app/main/ui/settings/subscription.cljs:272
|
||||||
msgid "subscription.settings.manage-your-subscription"
|
msgid "subscription.settings.manage-your-subscription"
|
||||||
msgstr "Gestionar tu suscripción"
|
msgstr "Gestionar tu suscripción"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:102
|
#: src/app/main/ui/settings/subscription.cljs:102
|
||||||
msgid "subscription.settings.management.dialog.choose-this-plan"
|
msgid "subscription.settings.management.dialog.currently-editors-title"
|
||||||
msgstr "Estás eligiendo este plan para:"
|
msgid_plural "subscription.settings.management.dialog.currently-editors-title"
|
||||||
|
msgstr[0] "Actualmente hay %s persona en tus equipos que pueden editar."
|
||||||
|
msgstr[1] "Actualmente hay %s personas en tus equipos que pueden editar."
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.editors"
|
||||||
|
msgstr "Editores"
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.editors-explanation"
|
||||||
|
msgstr "(Propietarios, administradores y editores. Los lectores no cuentan como editores)."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:132
|
#: src/app/main/ui/settings/subscription.cljs:132
|
||||||
msgid "subscription.settings.management.dialog.downgrade"
|
msgid "subscription.settings.management.dialog.downgrade"
|
||||||
@@ -4466,27 +4447,21 @@ msgstr ""
|
|||||||
"Ten en cuenta: cambiar a un plan inferior significa menos almacenamiento y "
|
"Ten en cuenta: cambiar a un plan inferior significa menos almacenamiento y "
|
||||||
"copias de seguridad e historial de versiones más cortos."
|
"copias de seguridad e historial de versiones más cortos."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:106
|
|
||||||
msgid "subscription.settings.management.dialog.members"
|
|
||||||
msgstr " (%s editores)"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:108
|
|
||||||
msgid "subscription.settings.management.dialog.no-teams"
|
|
||||||
msgstr "Este plan se aplicará a todos los futuros equipos que crees o poseas."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:126
|
#: src/app/main/ui/settings/subscription.cljs:126
|
||||||
msgid "subscription.settings.management.dialog.payment-explanation"
|
msgid "subscription.settings.management.dialog.payment-explanation"
|
||||||
msgstr "(Ahora no se efectuará ningún pago)"
|
msgstr "Se cobrar después del período de prueba. No se requiere tarjeta de crédito en este momento."
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.input-error"
|
||||||
|
msgstr "No puedes establecer menos editores de los que tienes ahora. Cambia el rol (de editor/administrador a lector) para las personas que realmente no editan archivos en la configuración del equipo."
|
||||||
|
|
||||||
|
msgid "subscription.settings.management.dialog.unlimited-capped-warning"
|
||||||
|
msgstr "Consejo: Puedes aumentar ahora el número de asientos para adelantarte a las invitaciones. Con más de 25 editores en todos tus equipos, disfrutarás de una tarifa plana de 175 $ al mes."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:124
|
#: src/app/main/ui/settings/subscription.cljs:124
|
||||||
#, markdown
|
#, markdown
|
||||||
msgid "subscription.settings.management.dialog.price-month"
|
msgid "subscription.settings.management.dialog.price-month"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"**$%s** por mes"
|
"**$%s**/mes"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:112
|
|
||||||
msgid "subscription.settings.management.dialog.select-editors"
|
|
||||||
msgstr "Seleccione el número de editores (puestos) que necesitas:"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:97
|
#: src/app/main/ui/settings/subscription.cljs:97
|
||||||
msgid "subscription.settings.management.dialog.title"
|
msgid "subscription.settings.management.dialog.title"
|
||||||
@@ -4513,16 +4488,16 @@ msgid "subscription.settings.professional"
|
|||||||
msgstr "Professional"
|
msgstr "Professional"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:239, src/app/main/ui/settings/subscription.cljs:290
|
#: src/app/main/ui/settings/subscription.cljs:239, src/app/main/ui/settings/subscription.cljs:290
|
||||||
msgid "subscription.settings.professional.projects-files"
|
msgid "subscription.settings.professional.autosave-benefit"
|
||||||
msgstr "Proyectos, archivos y borradores ilimitados"
|
msgstr "Versiones con autoguardado de 7 días y recuperación de archivos"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:241, src/app/main/ui/settings/subscription.cljs:292
|
#: src/app/main/ui/settings/subscription.cljs:241, src/app/main/ui/settings/subscription.cljs:292
|
||||||
msgid "subscription.settings.professional.storage-autosave"
|
msgid "subscription.settings.professional.storage-benefit"
|
||||||
msgstr "10 GB de almacenamiento y versiones de autoguardado de 7 días y recuperación de archivos"
|
msgstr "10 GB de almacenamiento"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:240, src/app/main/ui/settings/subscription.cljs:291
|
#: src/app/main/ui/settings/subscription.cljs:240, src/app/main/ui/settings/subscription.cljs:291
|
||||||
msgid "subscription.settings.professional.teams-editors"
|
msgid "subscription.settings.professional.teams-editors-benefit"
|
||||||
msgstr "Equipos ilimitados de hasta 8 redactores"
|
msgstr "Equipos ilimitados. Hasta 8 editores en todos tus equipos."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:235
|
#: src/app/main/ui/settings/subscription.cljs:235
|
||||||
msgid "subscription.settings.section-plan"
|
msgid "subscription.settings.section-plan"
|
||||||
@@ -4539,6 +4514,9 @@ msgstr "Comenzar prueba gratuita"
|
|||||||
msgid "subscription.settings.subscribe"
|
msgid "subscription.settings.subscribe"
|
||||||
msgstr "Suscríbete"
|
msgstr "Suscríbete"
|
||||||
|
|
||||||
|
msgid "subscription.settings.success.dialog.thanks"
|
||||||
|
msgstr "¡Gracias por elegir el plan %s de Penpot!"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:167
|
#: src/app/main/ui/settings/subscription.cljs:167
|
||||||
msgid "subscription.settings.success.dialog.description"
|
msgid "subscription.settings.success.dialog.description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4571,17 +4549,16 @@ msgstr "Unlimited (prueba)"
|
|||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305
|
#: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305
|
||||||
msgid "subscription.settings.unlimited.bill"
|
msgid "subscription.settings.unlimited.bill"
|
||||||
msgstr "Factura mensual limitada"
|
msgstr "Factura mensual limitada en $175"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:250, src/app/main/ui/settings/subscription.cljs:261, src/app/main/ui/settings/subscription.cljs:306
|
#: src/app/main/ui/settings/subscription.cljs:250, src/app/main/ui/settings/subscription.cljs:261, src/app/main/ui/settings/subscription.cljs:306
|
||||||
msgid "subscription.settings.unlimited.storage-autosave"
|
msgid "subscription.settings.unlimited.storage-benefit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"25 GB de almacenamiento y 30 días de autoguardado de versiones y recuperación "
|
"25 GB de almacenamiento"
|
||||||
"de archivos"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/subscription.cljs:248, src/app/main/ui/settings/subscription.cljs:259, src/app/main/ui/settings/subscription.cljs:304
|
#: src/app/main/ui/settings/subscription.cljs:248, src/app/main/ui/settings/subscription.cljs:259, src/app/main/ui/settings/subscription.cljs:304
|
||||||
msgid "subscription.settings.unlimited.teams"
|
msgid "subscription.settings.unlimited.autosave-benefit"
|
||||||
msgstr "Equipos ilimitados, independientemente de su tamaño"
|
msgstr "Versiones con autoguardado de 30 días y recuperación de archivos"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/subscription.cljs:133, src/app/main/ui/workspace/main_menu.cljs:928
|
#: src/app/main/ui/dashboard/subscription.cljs:133, src/app/main/ui/workspace/main_menu.cljs:928
|
||||||
msgid "subscription.workspace.header.menu.option.power-up"
|
msgid "subscription.workspace.header.menu.option.power-up"
|
||||||
|
|||||||
Reference in New Issue
Block a user