Visual indicators subscription for teams and project settings (#6546)

*  Visual indicators subscription for teams and project settings

* 📎 Fixes PR feedback

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Marina López 2025-05-26 12:56:40 +02:00 committed by GitHub
parent 5e8929e504
commit e5bc369e56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1116 additions and 155 deletions

View file

@ -31,8 +31,8 @@ export PENPOT_FLAGS="\
enable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation \
enable-subscriptons \
enable-subscriptons-old";
enable-subscriptions \
enable-subscriptions-old";
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"

View file

@ -24,8 +24,8 @@ export PENPOT_FLAGS="\
enable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation \
enable-subscriptons \
enable-subscriptons-old ";
enable-subscriptions \
enable-subscriptions-old ";
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 78 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -46,6 +46,10 @@
(update [_ state]
(assoc state :router (create routes)))))
(defn encode-url
[url]
(js/encodeURIComponent url))
(defn match
"Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found."

View file

@ -21,12 +21,13 @@
[app.main.refs :as refs]
[app.main.router :as rt]
[app.main.store :as st]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item*]]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu
dropdown-menu-item*]]
[app.main.ui.components.link :refer [link]]
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
[app.main.ui.dashboard.subscription :as subscription]
[app.main.ui.dashboard.subscription :refer [subscription-sidebar* menu-team-icon*]]
[app.main.ui.dashboard.team-form]
[app.main.ui.icons :as i :refer [icon-xref]]
[app.util.dom :as dom]
@ -330,8 +331,14 @@
[:img {:src (cf/resolve-team-photo-url team-item)
:class (stl/css :team-picture)
:alt (:name team-item)}]
(if (and (contains? cf/flags :subscriptions)
(or (= "unlimited" (:type (:subscription team-item))) (= "enterprise" (:type (:subscription team-item)))))
[:div {:class (stl/css :team-text-with-icon)}
[:span {:class (stl/css :team-text) :title (:name team-item)} (:name team-item)]
[:> menu-team-icon* {:subscription-name (:type (:subscription team-item))}]]
[:span {:class (stl/css :team-text)
:title (:name team-item)} (:name team-item)]
:title (:name team-item)} (:name team-item)])
(when (= (:id team-item) (:id team))
tick-icon)])
@ -645,19 +652,35 @@
handle-close-team
(fn []
(reset! show-teams-ddwn? false))]
(reset! show-teams-ddwn? false))
subscription (:subscription team)
subscription-name (:type subscription)]
[:div {:class (stl/css :sidebar-team-switch)}
[:div {:class (stl/css :switch-content)}
[:button {:class (stl/css :current-team)
:on-click handle-show-team-click
:on-key-down handle-show-team-keydown}
(if (:is-default team)
(cond
(:is-default team)
[:div {:class (stl/css :team-name)}
[:span {:class (stl/css :penpot-icon)} i/logo-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.default-team-name")]]
(and (contains? cf/flags :subscriptions)
(not (:is-default team))
(or (= "unlimited" subscription-name) (= "enterprise" subscription-name)))
[:div {:class (stl/css :team-name)}
[:img {:src (cf/resolve-team-photo-url team)
:class (stl/css :team-picture)
:alt (:name team)}]
[:div {:class (stl/css :team-text-with-icon)}
[:span {:class (stl/css :team-text) :title (:name team)} (:name team)]
[:> menu-team-icon* {:subscription-name subscription-name}]]]
(and (not (:is-default team))
(not (contains? cf/flags :subscriptions)))
[:div {:class (stl/css :team-name)}
[:img {:src (cf/resolve-team-photo-url team)
:class (stl/css :team-picture)
@ -964,7 +987,7 @@
[:*
(when (contains? cf/flags :subscriptions)
[:> subscription/sidebar*])
[:> subscription-sidebar* {:profile profile}])
;; TODO remove this block when subscriptions is full implemented
(when (contains? cf/flags :subscriptions-old)
@ -974,7 +997,7 @@
[:span (tr "dashboard.upgrade-plan.penpot-free")]
[:span {:class (stl/css :no-limits)} (tr "dashboard.upgrade-plan.no-limits")]]
[:div {:class (stl/css :power-up)}
(tr "dashboard.upgrade-plan.power-up")]])
(tr "subscription.dashboard.upgrade-plan.power-up")]])
(when (and team profile)
[:& comments-section

View file

@ -84,11 +84,18 @@
.team-text {
@include textEllipsis;
@include smallTitleTipography;
width: $s-144;
width: auto;
text-align: left;
color: var(--menu-foreground-color-hover);
}
.team-text-with-icon {
display: flex;
gap: $s-8;
max-width: 100%;
overflow: hidden;
}
// This icon still use the old svg
.penpot-icon {
@include flexCenter;

View file

@ -3,15 +3,21 @@
(ns app.main.ui.dashboard.subscription
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.config :as cf]
[app.main.router :as rt]
[app.main.store :as st]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu-item*]]
[app.main.ui.ds.product.cta :refer [cta*]]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[lambdaisland.uri :as u]
[rumext.v2 :as mf]))
(mf/defc cta-power-up*
[{:keys [top-title top-description bottom-description cta-text cta-link has-dropdown]}]
[{:keys [top-title top-description bottom-description has-dropdown]}]
(let [show-data* (mf/use-state false)
show-data (deref show-data*)
handle-click
@ -24,55 +30,166 @@
:on-click handle-click}
[:button {:class (stl/css :cta-top-section)}
[:div {:class (stl/css :content)}
[:span {:class (stl/css :cta-title)} top-title]
[:span {:class (stl/css :cta-text)} top-description]]
[:span {:class (stl/css :cta-title :cta-text)} top-title]
[:span {:class (stl/css :cta-text-m)} top-description]]
(when has-dropdown [:span {:class (stl/css :icon-dropdown)} i/arrow])]
(when (and has-dropdown show-data)
[:div {:class (stl/css :cta-bottom-section)}
[:> i18n/tr-html* {:content bottom-description
:class (stl/css :content)
:tag-name "button"}]
[:button {:class (stl/css :cta-highlight :cta-link) :on-click cta-link}
cta-text]])]))
:tag-name "span"}]])]))
(mf/defc sidebar*
[]
(let [;; TODO subscription cases professional/unlimited/enterprise
subscription-name :unlimited
subscription-is-trial false
go-to-subscription
(mf/use-fn #(st/emit! (rt/nav :settings-subscription)))]
(mf/defc subscription-sidebar*
[{:keys [profile]}]
(let [subscription (:subscription (:props profile))
subscription-name (if subscription
(:type subscription)
"professional")
subscription-is-trial (= (:status subscription) "trialing")
subscription-href (dm/str (u/join cf/public-uri "#/settings/subscriptions"))]
(case subscription-name
:professional
"professional"
[:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.professional.top-title")
:top-description (tr "dashboard.upgrade-plan.no-limits")
:bottom-description (tr "subscription.dashboard.power-up.professional.bottom-description")
:cta-text (tr "dashboard.upgrade-plan.power-up")
:cta-link go-to-subscription
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.professional.top-title")
:bottom-description (tr "subscription.dashboard.power-up.professional.bottom-description", subscription-href)
:has-dropdown true}]
:unlimited
"unlimited"
(if subscription-is-trial
[:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.trial.top-title")
:top-description (tr "subscription.dashboard.power-up.trial.top-description")
:bottom-description (tr "subscription.dashboard.power-up.trial.bottom-description")
:cta-text (tr "subscription.dashboard.power-up.subscribe")}]
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.trial.top-title")
:bottom-description (tr "subscription.dashboard.power-up.trial.bottom-description", subscription-href)
:has-dropdown true}]
[:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.unlimited-plan")
:top-description (tr "subscription.dashboard.power-up.unlimited.top-description")
:bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom-description")
:cta-text (tr "subscription.dashboard.power-up.unlimited.cta")
:cta-link go-to-subscription
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.unlimited-plan")
:bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom-description", subscription-href)
:has-dropdown true}])
:enterprise
"enterprise"
[:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.enterprise-plan")
:top-description (tr "subscription.dashboard.power-up.enterprise.description")
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.enterprise-plan")
:has-dropdown false}])))
(mf/defc team*
[{:keys [is-owner team]}]
(let [subscription (:subscription team)
subscription-name (:type subscription)
subscription-is-trial (= "trialing" (:status subscription))
go-to-manage-subscription
(mf/use-fn
(fn []
;; TODO add event tracking
(let [href (-> (rt/get-current-href)
(rt/encode-url))
href (str "payments/subscriptions/show?returnUrl=" href)]
(st/emit! (rt/nav-raw :href href)))))]
[:div {:class (stl/css :team)}
[:div {:class (stl/css :team-label)}
(tr "subscription.dashboard.team-plan")]
[:span {:class (stl/css :team-text)}
(case subscription-name
"professional" (tr "subscription.settings.professional")
"unlimited" (if subscription-is-trial
(tr "subscription.settings.unlimited-trial")
(tr "subscription.settings.unlimited"))
"enterprise" (tr "subscription.settings.enterprise"))]
(when (and is-owner (not= subscription-name "professional"))
[:button {:class (stl/css :manage-subscription-link)
:on-click go-to-manage-subscription}
(tr "subscription.settings.manage-your-subscription")])]))
(mf/defc menu-team-icon*
[{:keys [subscription-name]}]
[:span {:class (stl/css :subscription-icon)}
(case subscription-name
"unlimited" i/character-u
"enterprise" i/character-e)])
(mf/defc main-menu-power-up*
[{:keys [close-sub-menu]}]
(let [go-to-subscription (mf/use-fn #(st/emit! (rt/nav :settings-subscription)))]
[:> dropdown-menu-item* {:class (stl/css-case :menu-item true)
:on-click go-to-subscription
:on-key-down (fn [event]
(when (kbd/enter? event)
(go-to-subscription)))
:on-pointer-enter close-sub-menu
:id "file-menu-power-up"}
[:span {:class (stl/css :item-name)} (tr "subscription.workspace.header.menu.option.power-up")]]))
(mf/defc members-cta*
[{:keys [banner-is-expanded team profile]}]
(let [subscription (:subscription team)
subscription-name (:type subscription)
subscription-is-trial (= "trialing" (:status subscription))
is-owner (:is-owner (:permissions team))
email-owner (:email (some #(when (:is-admin %) %) (: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"))
link
(if is-owner
go-to-subscription
mail-to-owner)
cta-title
(cond
(= "professional" subscription-name)
(tr "subscription.dashboard.cta.professional-plan-designed")
subscription-is-trial
(tr "subscription.dashboard.cta.trial-plan-designed")
(= "unlimited" subscription-name)
(tr "subscription.dashboard.cta.unlimited-many-editors" (:quantity (:subscription (:props profile)))))
cta-message
(cond
(and (= "professional" subscription-name) is-owner)
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" link)
(and (= "professional" subscription-name) (not is-owner))
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-member" link)
(and subscription-is-trial is-owner)
(tr "subscription.dashboard.cta.upgrade-to-full-access-owner" link)
(and subscription-is-trial (not is-owner))
(tr "subscription.dashboard.cta.upgrade-to-full-access-member" link)
(and (= "unlimited" subscription-name) (not subscription-is-trial))
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats" 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*
{:tag-name "span"
:class (stl/css :cta-message)
:content cta-message}]]))
(defn show-subscription-members-main-banner?
[team profile]
(or
(and (= (:type (:subscription team)) "professional") (>= (count (:members team)) 8))
(and
(= (:type (:subscription team)) "unlimited")
(not (= (:status (:subscription team)) "trialing"))
(>= (count (:members team)) (:quantity (:subscription (:props profile))))
(:is-owner (:permissions team)))
(= (:status (:subscription team)) "paused")))
(defn show-subscription-invitations-main-banner?
[team]
(or
(and (= (:type (:subscription team)) "professional")
(>= (count (:members team)) 8))
(= (:status (:subscription team)) "paused")))

View file

@ -2,6 +2,7 @@
@use "common/refactor/common-dashboard";
@use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
.cta-power-up {
display: flex;
@ -27,7 +28,7 @@
.icon-dropdown {
@include flexCenter;
height: 100%;
width: $s-16;
width: var(--sp-l);
}
.icon-dropdown svg {
@ -37,42 +38,114 @@
}
.cta-bottom-section {
border-block-start: $s-1 solid var(--color-background-quaternary);
display: grid;
border-block-start: $b-1 solid var(--color-background-quaternary);
color: var(--color-foreground-secondary);
grid-template-columns: 1fr auto;
margin-block-start: $s-12;
padding-block-start: $s-12;
margin-block-start: var(--sp-m);
padding-block-start: var(--sp-m);
}
.cta-bottom-section .content {
@include t.use-typography("body-small");
@include buttonStyle;
color: var(--color-foreground-secondary);
display: inline;
display: inline-block;
text-align: left;
}
.cta-text,
.cta-text-m,
.cta-title {
text-align: left;
}
.cta-title {
margin-block-end: var(--sp-xs);
}
.cta-text {
@include t.use-typography("body-small");
}
.cta-title {
.cta-text-m {
@include t.use-typography("body-medium");
}
.cta-bottom-section .content strong,
.cta-highlight {
.cta-bottom-section .content a {
@include t.use-typography("body-small");
color: var(--color-accent-tertiary);
margin-inline-start: var(--sp-xs);
}
.cta-link {
@include buttonStyle;
align-self: end;
margin-inline-start: $s-4;
margin-inline-start: var(--sp-xs);
}
.team {
display: grid;
grid-auto-rows: min-content;
gap: var(--sp-s);
max-width: $s-1000;
width: 100%;
}
.team-label {
@include t.use-typography("headline-small");
color: var(--title-foreground-color);
}
.team-text {
@include t.use-typography("body-large");
color: var(--color-foreground-primary);
}
.manage-subscription-link {
@include buttonStyle;
@include t.use-typography("body-small");
color: var(--color-accent-tertiary);
display: flex;
padding: 0;
}
.subscription-icon {
@extend .button-icon;
background: var(--color-background-primary);
stroke: var(--color-foreground-secondary);
border-radius: var(--sp-xs);
border: $b-1 solid var(--color-foreground-secondary);
}
.menu-item {
@extend .menu-item-base;
cursor: pointer;
&:hover {
color: var(--menu-foreground-color-hover);
.open-arrow {
svg {
stroke: var(--menu-foreground-color-hover);
}
}
}
}
.members-cta {
height: fit-content;
margin-block-start: var(--sp-s);
margin-inline-start: $s-68;
max-width: $s-200;
}
.members-cta-full-width {
max-width: $s-1000;
}
.cta-message {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
a {
color: var(--color-accent-primary);
}
}

View file

@ -22,6 +22,10 @@
[app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.components.forms :as fm]
[app.main.ui.dashboard.change-owner]
[app.main.ui.dashboard.subscription :refer [team*
members-cta*
show-subscription-members-main-banner?
show-subscription-invitations-main-banner?]]
[app.main.ui.dashboard.team-form]
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
[app.main.ui.icons :as i]
@ -537,10 +541,22 @@
[:*
[:& header {:section :dashboard-team-members :team team}]
[:section {:class (stl/css :dashboard-container :dashboard-team-members)}
[:section {:class (stl/css-case
:dashboard-container true
:dashboard-team-members true
:dashboard-top-cta (show-subscription-members-main-banner? team profile))}
(when (and (contains? cfg/flags :subscriptions)
(show-subscription-members-main-banner? team profile))
[:> members-cta* {:banner-is-expanded true :team team :profile profile}])
[:> team-members*
{:profile profile
:team team}]]])
:team team}]
(when (and
(contains? cfg/flags :subscriptions)
(or
(and (= (:type (:subscription team)) "professional") (< (count (:members team)) 8))
(= (:status (:subscription team)) "trialing")))
[:> members-cta* {:banner-is-expanded false :team team}])]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INVITATIONS SECTION
@ -803,8 +819,18 @@
[:*
[:& header {:section :dashboard-team-invitations
:team team}]
[:section {:class (stl/css :dashboard-team-invitations)}
[:> invitation-section* {:team team}]]])
[:section {:class (stl/css-case
:dashboard-team-invitations true
:dashboard-top-cta (show-subscription-invitations-main-banner? team))}
(when (and (contains? cfg/flags :subscriptions)
(show-subscription-invitations-main-banner? team))
[:> members-cta* {:banner-is-expanded true :team team}])
[:> invitation-section* {:team team}]
(when (and (contains? cfg/flags :subscriptions)
(or
(and (= (:type (:subscription team)) "professional") (< (count (:members team)) 8))
(= (:status (:subscription team)) "trialing")))
[:> members-cta* {:banner-is-expanded false :team team}])]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; WEBHOOKS SECTION
@ -1159,5 +1185,8 @@
[:div {:class (stl/css :block-content)}
document-icon
[:span {:class (stl/css :block-text)}
(tr "labels.num-of-files" (i18n/c (:files stats)))]]]]]))
(tr "labels.num-of-files" (i18n/c (:files stats)))]]]
(when (contains? cfg/flags :subscriptions)
[:> team* {:is-owner (:is-owner permissions) :team team}])]]))

View file

@ -9,13 +9,14 @@
// Dashboard team settings
.dashboard-team-settings {
display: grid;
grid-template-rows: auto auto 1fr;
justify-items: center;
display: flex;
flex-direction: column;
align-items: center;
gap: $s-24;
width: 100%;
border-top: $s-1 solid var(--panel-border-color);
overflow-y: auto;
padding-inline: $s-24;
}
.block {
@ -23,7 +24,7 @@
grid-auto-rows: min-content;
gap: $s-8;
max-width: $s-1000;
width: $s-1000;
width: 100%;
}
.info-block {
@ -105,22 +106,28 @@
// TEAM MEMBERS PAGE
.dashboard-team-members {
display: grid;
justify-items: center;
display: flex;
justify-content: center;
width: 100%;
height: 100%;
padding-top: $s-20;
padding-inline-start: $s-20;
padding-block-start: $s-20;
border-top: $s-1 solid var(--panel-border-color);
overflow-y: auto;
scrollbar-gutter: stable;
}
.dashboard-team-members.dashboard-top-cta {
flex-direction: column;
justify-content: flex-start;
}
.team-members {
display: grid;
grid-template-rows: auto 1fr;
height: fit-content;
max-width: $s-1000;
width: $s-1000;
width: 100%;
}
.table-header {
@ -275,22 +282,28 @@
// TEAM INVITATION PAGE
.dashboard-team-invitations {
display: grid;
justify-items: center;
display: flex;
justify-content: center;
width: 100%;
height: 100%;
padding-top: $s-20;
padding-inline-start: $s-20;
padding-block-start: $s-20;
border-top: $s-1 solid var(--panel-border-color);
overflow-y: auto;
scrollbar-gutter: stable;
}
.dashboard-team-invitations .dashboard-top-cta {
flex-direction: flex;
justify-content: flex-start;
}
.invitations {
display: grid;
grid-template-rows: auto 1fr;
height: fit-content;
max-width: $s-1000;
width: $s-1000;
width: 100%;
}
.table-row-invitations {

View file

@ -19,6 +19,7 @@
(def ^:svg-id loader "loader")
(def ^:svg-id logo-error-screen "logo-error-screen")
(def ^:svg-id login-illustration "login-illustration")
(def ^:svg-id logo-subscription "logo-subscription")
(def ^:svg-id marketing-arrows "marketing-arrows")
(def ^:svg-id marketing-exchange "marketing-exchange")
(def ^:svg-id marketing-file "marketing-file")

View file

@ -18,6 +18,7 @@
(def ^:icon logo-icon (icon-xref :penpot-logo-icon))
(def ^:icon logo-error-screen (icon-xref :logo-error-screen))
(def ^:icon login-illustration (icon-xref :login-illustration))
(def ^:icon logo-subscription (icon-xref :logo-subscription))
(def ^:icon brand-openid (icon-xref :brand-openid))
(def ^:icon brand-github (icon-xref :brand-github))

View file

@ -69,7 +69,7 @@
[:& options-page]
:settings-subscription
[:> subscription-page*]
[:> subscription-page* {:profile profile}]
:settings-access-tokens
[:& access-tokens-page]

View file

@ -1,16 +1,24 @@
(ns app.main.ui.settings.subscription
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.router :as rt]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.time :as dt]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc plan-card*
{::mf/props :obj}
[{:keys [card-title card-title-icon price-value price-period benefits-title benefits cta-text cta-link]}]
[{:keys [card-title card-title-icon price-value price-period benefits-title benefits cta-text cta-link cta-text-trial cta-link-trial cta-text-with-icon cta-link-with-icon]}]
[:div {:class (stl/css :plan-card)}
[:div {:class (stl/css :plan-card-header)}
[:div {:class (stl/css :plan-card-title-container)}
@ -23,25 +31,195 @@
(when benefits-title [:h5 {:class (stl/css :benefits-title)} benefits-title])
[:ul {:class (stl/css :benefits-list)}
(for [benefit benefits]
[:li {:key (str benefit) :class (stl/css :benefit)} "- " benefit])]
(when (and cta-link cta-text) [:a {:class (stl/css :cta-button)
:href cta-link} cta-text])])
[:li {:key (dm/str benefit) :class (stl/css :benefit)} "- " benefit])]
(when (and cta-link-with-icon cta-text-with-icon) [:button {:class (stl/css :cta-button :more-info)
:on-click cta-link-with-icon} cta-text-with-icon i/open-link])
(when (and cta-link cta-text) [:button {:class (stl/css-case :cta-button true
:bottom-link (not (and cta-link-trial cta-text-trial)))
:on-click cta-link} cta-text])
(when (and cta-link-trial cta-text-trial) [:button {:class (stl/css :cta-button :bottom-link)
:on-click cta-link-trial} cta-text-trial])])
(mf/defc subscribe-management-dialog
{::mf/register modal/components
::mf/register-as :management-dialog}
[{:keys [subscription-name teams subscribe-to-trial]}]
(let [min-members* (mf/use-state (or (some->> teams (map :total-members) (apply max)) 1))
min-members (deref min-members*)
formatted-subscription-name (if subscribe-to-trial
(if (= subscription-name "unlimited")
(tr "subscription.settings.unlimited-trial")
(tr "subscription.settings.enterprise-trial"))
(case subscription-name
"professional" (tr "subscription.settings.professional")
"unlimited" (tr "subscription.settings.unlimited")
"enterprise" (tr "subscription.settings.enterprise")))
handle-subscription-trial (if "unlimited"
(mf/use-fn
(mf/deps min-members)
(fn []
;; TODO add event tracking subscribe trial unlimited
(let [current-href (rt/get-current-href)
returnUrl (js/encodeURIComponent current-href)
href (dm/str "payments/subscriptions/create?type=unlimited&quantity=" min-members "&returnUrl=" returnUrl)]
(st/emit! (rt/nav-raw :href href)))))
(mf/use-fn
(fn []
;; TODO add event tracking subscribe trial enterprise
(let [current-href (rt/get-current-href)
returnUrl (js/encodeURIComponent current-href)
href (dm/str "payments/subscriptions/create?type=enterprise&returnUrl=" returnUrl)]
(st/emit! (rt/nav-raw :href href))))))
handle-accept-dialog (mf/use-callback
(fn []
;; TODO add event subscribe to another subscription
(let [current-href (rt/get-current-href)
returnUrl (js/encodeURIComponent current-href)
href (dm/str "payments/subscriptions/show?returnUrl=" returnUrl)]
(st/emit! (rt/nav-raw :href href)))
(modal/hide!)))
handle-close-dialog (mf/use-callback
(fn []
;; TODO add event tracking close modal/cancel subscription
(modal/hide!)))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)}
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} i/close]
[:div {:class (stl/css :modal-title :subscription-title)}
(tr "subscription.settings.management.dialog.title" formatted-subscription-name)]
[:div {:class (stl/css :modal-content)}
(if (seq teams)
[* [:div {:class (stl/css :modal-text)}
(tr "subscription.settings.management.dialog.choose-this-plan")]
[:ul {:class (stl/css :teams-list)}
(for [team (js->clj teams :keywordize-keys true)]
[:li {:key (dm/str (:id team)) :class (stl/css :team-name)}
(:name team) (tr "subscription.settings.management.dialog.members" (:total-members team))])]]
[:div {:class (stl/css :modal-text)}
(tr "subscription.settings.management.dialog.no-teams")])
(when (and (= subscription-name "unlimited") subscribe-to-trial)
[[: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 :input-wrapper)}
[:input {:id "editors-subscription"
:class (stl/css :input-field)
:type "number"
:value min-members
:min 1
:on-change #(let [new-value (js/parseInt (.. % -target -value))]
(reset! min-members* (if (or (js/isNaN new-value) (zero? new-value)) 1 (max 1 new-value))))}]]
[:div {:class (stl/css :editors-cost)}
[:span {:class (stl/css :modal-text-small)}
(tr "subscription.settings.management.dialog.price-month" min-members)]
[:span {:class (stl/css :modal-text-small)}
(tr "subscription.settings.management.dialog.payment-explanation")]]]])
(when (and
(or (= subscription-name "professional") (= subscription-name "unlimited"))
(not subscribe-to-trial))
[:div {:class (stl/css :modal-text)}
(tr "subscription.settings.management.dialog.downgrade")])
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)}
[:input
{:class (stl/css :cancel-button)
:type "button"
:value (tr "ds.confirm-cancel")
:on-click handle-close-dialog}]
[:input
{:class (stl/css :primary-button)
:type "button"
:value (if subscribe-to-trial (tr "subscription.settings.start-trial") (tr "labels.continue"))
:on-click (if subscribe-to-trial handle-subscription-trial handle-accept-dialog)}]]]]]]))
(mf/defc subscription-success-dialog
{::mf/register modal/components
::mf/register-as :subscription-success}
[{:keys [subscription-name]}]
(let [handle-close-dialog (mf/use-callback
(fn []
;; TODO add event tracking close modal
(modal/hide!)))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog :subscription-success)}
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} i/close]
[:div {:class (stl/css :modal-success-content)}
[:div {:class (stl/css :modal-start)}
i/logo-subscription]
[:div {:class (stl/css :modal-end)}
[:div {:class (stl/css :modal-title)} (tr "subscription.settings.sucess.dialog.title" 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.sucess.dialog.footer")]
[:div {:class (stl/css :success-action-buttons)}
[:input
{:class (stl/css :primary-button)
:type "button"
:value (tr "labels.close")
:on-click handle-close-dialog}]]]]]]))
(mf/defc subscription-page*
[]
(let [;; TODO subscription cases professional/unlimited/enterprise
subscription-name :unlimited
subscription-is-trial false
[{:keys [profile]}]
(let [route (mf/deref refs/route)
params (:params route)
show-subscription-success-modal (and (:query params)
(or (= (:subscription (:query params)) "subscribed-to-penpot-unlimited")
(= (:subscription (:query params)) "subscribed-to-penpot-enterprise")))
subscription (:subscription (:props profile))
subscription-name (if subscription
(:type subscription)
"professional")
subscription-is-trial (= (:status subscription) "trialing")
teams* (mf/use-state nil)
teams (deref teams*)
locale (mf/deref i18n/locale)
profile (mf/deref refs/profile)
penpot-member (dt/format-date-locale-short (:created-at profile) {:locale locale})
;; TODO get subscription member date
subscription-member "January 17, 2024"
;; TODO update url to penpot payments
go-to-payments "https://penpot.app/pricing"]
subscription-member (dt/format-date-locale-short (:start-date subscription) {:locale locale})
go-to-pricing-page (mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-pricing-click" ::ev/origin "settings" :section "subscription"}))
(dom/open-new-window "https://penpot.app/pricing")))
go-to-payments (mf/use-fn
(fn []
;; TODO add event tracking manage subscription in stripe
(let [current-href (rt/get-current-href)
returnUrl (js/encodeURIComponent current-href)
href (dm/str "payments/subscriptions/show?returnUrl=" returnUrl)]
(st/emit! (rt/nav-raw :href href)))))
open-subscription-modal (mf/use-fn
(mf/deps teams)
(fn [subscription-name]
;; TODO add event tracking open modal to try trial
(st/emit!
(modal/show :management-dialog
{:subscription-name subscription-name
:teams teams :subscribe-to-trial (not subscription)}))))]
(mf/with-effect []
(->> (rp/cmd! :get-owned-teams)
(rx/subs! (fn [teams]
(reset! teams* teams)))))
(mf/with-effect []
(dom/set-html-title (tr "subscription.labels")))
(when show-subscription-success-modal
;; add name subscription from params
(st/emit! (modal/show :subscription-success
{:subscription-name (if (= (:subscription (:query params)) "subscribed-to-penpot-unlimited")
(tr "subscription.settings.unlimited-trial-modal")
(tr "subscription.settings.enterprise-trial-modal"))})))
[:section {:class (stl/css :dashboard-section)}
[:div {:class (stl/css :dashboard-content)}
[:h2 {:class (stl/css :title-section)} (tr "subscription.labels")]
@ -50,13 +228,13 @@
[:div {:class (stl/css :your-subscription)}
[:h3 {:class (stl/css :plan-section-title)} (tr "subscription.settings.section-plan")]
(case subscription-name
:professional
"professional"
[:> plan-card* {:card-title (tr "subscription.settings.professional")
:benefits [(tr "subscription.settings.professional.projects-files"),
(tr "subscription.settings.professional.teams-editors"),
(tr "subscription.settings.professional.storage")]}]
:unlimited
"unlimited"
(if subscription-is-trial
[:> plan-card* {:card-title (tr "subscription.settings.unlimited-trial")
:card-title-icon i/character-u
@ -65,7 +243,9 @@
(tr "subscription.settings.unlimited.bill"),
(tr "subscription.settings.unlimited.storage")]
: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.unlimited")
:card-title-icon i/character-u
@ -76,10 +256,10 @@
:cta-text (tr "subscription.settings.manage-your-subscription")
:cta-link go-to-payments}])
:enterprise
"enterprise"
[:> plan-card* {:card-title (tr "subscription.settings.enterprise")
:card-title-icon i/character-e
:benefits-title (tr "subscription.settings.benefits.all-professiona-benefits")
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
:benefits [(tr "subscription.settings.enterprise.support"),
(tr "subscription.settings.enterprise.security"),
(tr "subscription.settings.enterprise.logs")]
@ -97,36 +277,42 @@
[:div {:class (stl/css :other-subscriptions)}
[:h3 {:class (stl/css :plan-section-title)} (tr "subscription.settings.other-plans")]
(when (not= subscription-name :professional)
(when (not= subscription-name "professional")
[:> plan-card* {:card-title (tr "subscription.settings.professional")
:price-value "$0"
:price-period (tr "subscription.settings.price-editor-month")
:benefits [(tr "subscription.settings.professional.projects-files"),
(tr "subscription.settings.professional.teams-editors"),
(tr "subscription.settings.professional.storage")]
:cta-text (tr "subscription.dashboard.power-up.subscribe")
:cta-link go-to-payments}])
:cta-text (tr "subscription.settings.subscribe")
:cta-link #(open-subscription-modal "professional")
:cta-text-with-icon (tr "subscription.settings.more-information")
:cta-link-with-icon go-to-pricing-page}])
(when (not= subscription-name :unlimited)
(when (not= subscription-name "unlimited")
[:> plan-card* {:card-title (tr "subscription.settings.unlimited")
:card-title-icon i/character-u
:price-value "$7"
:price-period (tr "subscription.settings.price-editor-month")
:benefits-title (tr "subscription.settings.benefits.all-professiona-benefits")
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
:benefits [(tr "subscription.settings.unlimited.teams"),
(tr "subscription.settings.unlimited.bill"),
(tr "subscription.settings.unlimited.storage")]
:cta-text (tr "subscription.settings.ulimited.try-it-free")
:cta-link go-to-payments}])
:cta-text (if subscription (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
:cta-link #(open-subscription-modal "unlimited")
:cta-text-with-icon (tr "subscription.settings.more-information")
:cta-link-with-icon go-to-pricing-page}])
(when (not= subscription-name :enterprise)
(when (not= subscription-name "enterprise")
[:> plan-card* {:card-title (tr "subscription.settings.enterprise")
:card-title-icon i/character-e
:price-value "$950"
:price-period (tr "subscription.settings.price-organization-month")
:benefits-title (tr "subscription.settings.benefits.all-professiona-benefits")
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
:benefits [(tr "subscription.settings.enterprise.support"),
(tr "subscription.settings.enterprise.security"),
(tr "subscription.settings.enterprise.logs")]
:cta-text (tr "subscription.dashboard.power-up.subscribe")
:cta-link go-to-payments}])]]]))
:cta-text (if subscription (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
:cta-link #(open-subscription-modal "enterprise")
:cta-text-with-icon (tr "subscription.settings.more-information")
:cta-link-with-icon go-to-pricing-page}])]]]))

View file

@ -6,6 +6,8 @@
@use "common/refactor/common-refactor.scss" as *;
@use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
.dashboard-section {
display: flex;
@ -19,30 +21,30 @@
justify-content: center;
flex-direction: column;
max-width: $s-500;
margin-bottom: $s-32;
margin-block-end: var(--sp-xxxl);
width: $s-580;
margin: $s-92 auto $s-120 auto;
justify-content: center;
}
.membership-container {
margin-block-start: $s-16;
margin-block-start: var(--sp-l);
}
.membership {
align-items: center;
display: flex;
margin-block-start: $s-8;
margin-block-start: var(--sp-s);
}
.membership.first {
margin-block-start: $s-16;
margin-block-start: var(--sp-l);
}
.membership-date {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
margin-inline-start: $s-8;
margin-inline-start: var(--sp-s);
}
.subscription-member,
@ -61,7 +63,7 @@
.title-section {
@include t.use-typography("title-large");
color: var(--color-foreground-primary);
margin-block-end: $s-16;
margin-block-end: var(--sp-l);
}
.plan-section-title {
@ -70,29 +72,32 @@
}
.plan-card {
border: $s-1 solid var(--color-foreground-secondary);
border-radius: $s-8;
margin-block-start: $s-16;
padding: $s-16;
border: $b-1 solid var(--color-foreground-secondary);
border-radius: var(--sp-s);
margin-block-start: var(--sp-l);
padding: var(--sp-l);
}
.plan-card-header {
display: flex;
justify-content: space-between;
margin-block-end: $s-8;
margin-block-end: var(--sp-s);
}
.plan-card-title-container {
display: flex;
align-items: center;
gap: $s-8;
gap: var(--sp-s);
}
.plan-title-icon {
@extend .button-icon;
stroke: var(--color-foreground-primary);
border-radius: $s-4;
border: $s-1 solid var(--color-foreground-primary);
height: var(--sp-xl);
width: var(--sp-xl);
border-radius: var(--sp-xs);
border: $b-1 solid var(--color-foreground-primary);
padding: $s-1;
}
.plan-card-title,
@ -122,5 +127,134 @@
.cta-button {
@include t.use-typography("body-small");
@include buttonStyle;
color: var(--color-accent-tertiary);
display: flex;
margin-block-start: var(--sp-m);
}
.cta-button svg {
@extend .button-icon;
height: var(--sp-l);
width: var(--sp-l);
stroke: var(--color-accent-tertiary);
margin-inline-start: var(--sp-xs);
}
.bottom-link {
margin-block-start: var(--sp-xs);
}
.modal-overlay {
@extend .modal-overlay-base;
}
.modal-dialog {
@extend .modal-container-base;
display: grid;
grid-template-rows: auto 1fr auto;
max-height: initial;
min-width: $s-520;
}
.modal-dialog.subscription-success {
min-width: $s-612;
}
.close-btn {
@extend .modal-close-btn-base;
}
.modal-title {
@include t.use-typography("title-large");
margin-block-end: var(--sp-xxxl);
color: var(--modal-title-foreground-color);
display: flex;
gap: var(--sp-m);
}
.subscription-title {
margin-block-end: var(--sp-l);
}
.modal-text-lage {
@include t.use-typography("body-large");
}
.modal-text-small {
@include t.use-typography("body-small");
}
.modal-content,
.modal-end {
color: var(--color-foreground-secondary);
display: flex;
flex-direction: column;
}
.modal-success-content {
display: flex;
gap: $s-40;
}
.modal-footer {
margin-block-start: $s-40;
}
.action-buttons {
@extend .modal-action-btns;
}
.success-action-buttons {
margin-block-start: var(--sp-l);
}
.primary-button {
@extend .modal-accept-btn;
}
.cancel-button {
@extend .modal-cancel-btn;
}
.modal-start {
display: flex;
justify-content: center;
max-width: $s-220;
svg {
width: 100%;
height: auto;
}
@media (max-width: 992px) {
display: none;
}
}
.teams-list {
list-style-position: inside;
list-style-type: disc;
margin-inline-start: var(--sp-xl);
margin-block: var(--sp-xxl);
}
.input-wrapper {
@extend .input-element;
width: $s-80;
}
.editors-label {
margin-block-start: var(--sp-xxl);
}
.editors-wrapper {
display: flex;
gap: var(--sp-xl);
margin-block-start: var(--sp-l);
}
.editors-cost {
display: flex;
flex-direction: column;
}

View file

@ -30,6 +30,7 @@
[app.main.store :as st]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item*]]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.subscription :refer [main-menu-power-up*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.hooks.resize :as r]
[app.main.ui.icons :as i]
@ -819,7 +820,12 @@
(reset! sub-menu* nil)
(st/emit!
(ptk/event ::ev/event {::ev/name "open-plugins-manager" ::ev/origin "workspace:menu"})
(modal/show :plugin-management {}))))]
(modal/show :plugin-management {}))))
subscription (:subscription (:props profile))
subscription-name (if subscription
(:type subscription)
"professional")]
(mf/with-effect []
(let [disposable (->> st/stream
@ -904,6 +910,10 @@
:id "file-menu-help-info"}
[:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.help-info")]
[:span {:class (stl/css :open-arrow)} i/arrow]]
(when (and (contains? cf/flags :subscriptions) (not= "enterprise" subscription-name))
[:> main-menu-power-up* {:close-sub-menu close-sub-menu}])
;; TODO remove this block when subscriptions is full implemented
(when (contains? cf/flags :subscriptions-old)
[:> dropdown-menu-item* {:class (stl/css-case :menu-item true)
@ -913,7 +923,7 @@
(on-power-up-click)))
:on-pointer-enter close-sub-menu
:id "file-menu-power-up"}
[:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.power-up")]])]
[:span {:class (stl/css :item-name)} (tr "subscription.workspace.header.menu.option.power-up")]])]
(case sub-menu
:file

View file

@ -1067,11 +1067,72 @@ msgstr "No limits on creativity"
msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Free"
#: src/app/main/ui/dashboard/sidebar.cljs:977, src/app/main/ui/dashboard/subscription.cljs:54
msgid "dashboard.upgrade-plan.power-up"
#: src/app/main/ui/dashboard/sidebar.cljs:972
msgid "subscription.dashboard.upgrade-plan.power-up"
msgstr "Power up"
#: src/app/main/ui/dashboard/team.cljs:924
#: src/app/main/ui/workspace/main_menu.cljs:910
msgid "subscription.workspace.header.menu.option.power-up"
msgstr "Power up your plan"
msgid "subscription.dashboard.team-plan"
msgstr "Team plan"
msgid "subscription.dashboard.power-up.your-subscription"
msgstr "Your subscription:"
msgid "subscription.dashboard.power-up.professional.top-title"
msgstr "Professional plan"
msgid "subscription.dashboard.power-up.unlimited-plan"
msgstr "Unlimited plan"
msgid "subscription.dashboard.power-up.enterprise-plan"
msgstr "Enterprise plan"
msgid "subscription.dashboard.power-up.trial.top-title"
msgstr "Unlimited plan (trial)"
msgid "subscription.dashboard.cta.professional-plan-designed"
msgstr "The Professional plan is designed for teams of up to 8 editors (owner, admin, and editor)."
msgid "subscription.dashboard.cta.unlimited-many-editors"
msgstr "Looks like your team has grown! Your plan includes %s seats, but you're now using more than that."
msgid "subscription.dashboard.cta.trial-plan-designed"
msgstr "The Unlimited (trial) plan is designed for teams of more than 8 editors (owner, admin, and editor)."
#, 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.](%s)"
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats"
msgstr ""
"Please upgrade to match your usage. "
"[Subscribe now.](%s)"
#, 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"
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-owner"
msgstr ""
"Unlock full access forever. "
"[Subscribe now.](%s)"
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-member"
msgstr ""
"Unlock full access forever. Contact with the team owner: "
"%s"
#: src/app/main/ui/dashboard/team.cljs:909
msgid "dashboard.webhooks.active"
msgstr "Is active"
@ -4267,13 +4328,6 @@ msgstr "Enterprise plan"
msgid "subscription.dashboard.power-up.enterprise.description"
msgstr "Advanced security, activity logs, dedicated support and more."
#: src/app/main/ui/dashboard/subscription.cljs:53
#, markdown
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr ""
"Get extra editors and storage, file backup and more with the **Unlimited "
"plan**"
#: src/app/main/ui/dashboard/subscription.cljs:51
msgid "subscription.dashboard.power-up.professional.top-title"
msgstr "Professional plan"
@ -4282,10 +4336,6 @@ msgstr "Professional plan"
msgid "subscription.dashboard.power-up.subscribe"
msgstr "Subscribe"
#: src/app/main/ui/dashboard/subscription.cljs:63
msgid "subscription.dashboard.power-up.trial.bottom-description"
msgstr "Enjoying your trial? Unlock full access forever."
#: src/app/main/ui/dashboard/subscription.cljs:62
msgid "subscription.dashboard.power-up.trial.top-description"
msgstr "Extra editors, storage and autosaved version, file backup and more."
@ -4298,12 +4348,25 @@ msgstr "Unlimited plan (trial)"
msgid "subscription.dashboard.power-up.unlimited-plan"
msgstr "Unlimited plan"
#: src/app/main/ui/dashboard/subscription.cljs:53
#, markdown
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr ""
"Get extra editors and storage, file backup and more with the Unlimited plan"
"[Power up](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:69
#, markdown
msgid "subscription.dashboard.power-up.unlimited.bottom-description"
msgstr ""
"Get advanced security, activity logs, dedicated support and more with "
"**Enterprise plan**"
"Get advanced security, activity logs, dedicated support and more. Take a look to the"
"[Enterprise plan.](%s)"
#, markdown
msgid "subscription.dashboard.power-up.trial.bottom-description"
msgstr ""
"Enjoying your trial? Unlock full access forever."
"[Subscribe](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:70
msgid "subscription.dashboard.power-up.unlimited.cta"
@ -4466,7 +4529,127 @@ msgstr "Password - Penpot"
msgid "title.settings.profile"
msgstr "Profile - Penpot"
#: src/app/main/ui/dashboard/team.cljs:795
msgid "subscription.settings.section-plan"
msgstr "Your subscription"
msgid "subscription.settings.other-plans"
msgstr "Other penpot plans"
msgid "subscription.settings.unlimited"
msgstr "Unlimited"
msgid "subscription.settings.unlimited-trial"
msgstr "Unlimited (trial)"
msgid "subscription.settings.unlimited-trial-modal"
msgstr "Unlimited trial"
msgid "subscription.settings.enterprise"
msgstr "Enterprise"
msgid "subscription.settings.enterprise-trial"
msgstr "Enterprise (trial)"
msgid "subscription.settings.enterprise-trial-modal"
msgstr "Enterprise trial"
msgid "subscription.settings.support-us-since"
msgstr "You've been supporting us with this plan since %s"
msgid "subscription.settings.member-since"
msgstr "Penpot member since %s"
msgid "subscription.settings.price-editor-month"
msgstr "editor per month"
msgid "subscription.settings.price-organization-month"
msgstr "organization per month"
msgid "subscription.settings.more-information"
msgstr "More information"
msgid "subscription.settings.try-it-free"
msgstr "Try it free for 14 days"
msgid "subscription.settings.subscribe"
msgstr "Subscribe"
msgid "subscription.settings.start-trial"
msgstr "Start free trial"
msgid "subscription.settings.professional.projects-files"
msgstr "Unlimited projects, files and drafts"
msgid "subscription.settings.professional.teams-editors"
msgstr "Unlimited teams of up to 8 editors"
msgid "subscription.settings.professional.storage"
msgstr "10GB of storage and 7-day autosave versions"
msgid "subscription.settings.benefits.all-professional-benefits"
msgstr "All Professional plan benefits and:"
msgid "subscription.settings.benefits.all-unlimited-benefits"
msgstr "All Unlimited plan benefits and:"
msgid "subscription.settings.unlimited.teams"
msgstr "Unlimited teams, no matter your team size"
msgid "subscription.settings.unlimited.bill"
msgstr "Capped monthly bill"
msgid "subscription.settings.unlimited.storage"
msgstr "25GB of storage and 30-day autosave versions and file backup"
msgid "subscription.settings.manage-your-subscription"
msgstr "Manage your subscription"
msgid "subscription.settings.add-payment-to-continue"
msgstr "Add a payment method to continue after your trial"
msgid "subscription.settings.enterprise.support"
msgstr "Dedicated support"
msgid "subscription.settings.enterprise.security"
msgstr "Advanced security"
msgid "subscription.settings.enterprise.logs"
msgstr "Activity logs"
msgid "subscription.settings.sucess.dialog.title"
msgstr "You are %s!"
msgid "subscription.settings.success.dialog.description"
msgstr "You can edit your subscription at any time from the 'Subscription' page in your account details."
msgid "subscription.settings.sucess.dialog.footer"
msgstr "Enjoy your plan!"
msgid "subscription.settings.management.dialog.title"
msgstr "Apply %s to your teams"
msgid "subscription.settings.management.dialog.choose-this-plan"
msgstr "You are choosing this plan for:"
msgid "subscription.settings.management.dialog.members"
msgstr " (%s editors)"
msgid "subscription.settings.management.dialog.no-teams"
msgstr "This plan will apply to all future teams you create or own."
msgid "subscription.settings.management.dialog.select-editors"
msgstr "Select number of editors (seats) you need:"
msgid "subscription.settings.management.dialog.price-month"
msgstr "$7 per editor/month x %s"
msgid "subscription.settings.management.dialog.payment-explanation"
msgstr "(No payment will be made now)"
msgid "subscription.settings.management.dialog.downgrade"
msgstr "Heads up: switching to a lower plan means less storage and shorter backups and version history."
#: src/app/main/ui/dashboard/team.cljs:779
msgid "title.team-invitations"
msgstr "Invitations - %s - Penpot"
@ -4474,7 +4657,6 @@ msgstr "Invitations - %s - Penpot"
msgid "title.team-members"
msgstr "Members - %s - Penpot"
#: src/app/main/ui/dashboard/team.cljs:1105
msgid "title.team-settings"
msgstr "Settings - %s - Penpot"
@ -4932,12 +5114,6 @@ msgstr "Help & info"
#: src/app/main/ui/workspace/main_menu.cljs:916
msgid "workspace.header.menu.option.power-up"
msgstr "Power up your plan"
#: src/app/main/ui/workspace/main_menu.cljs:881
msgid "workspace.header.menu.option.preferences"
msgstr "Preferences"
#: src/app/main/ui/workspace/main_menu.cljs:870
msgid "workspace.header.menu.option.view"
msgstr "View"

View file

@ -1079,11 +1079,86 @@ msgstr "Sin límites a la creatividad"
msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Gratis"
#: src/app/main/ui/dashboard/sidebar.cljs:977, src/app/main/ui/dashboard/subscription.cljs:54
msgid "dashboard.upgrade-plan.power-up"
#: src/app/main/ui/dashboard/sidebar.cljs:972
msgid "subscription.dashboard.upgrade-plan.power-up"
msgstr "Mejora"
#: src/app/main/ui/dashboard/team.cljs:924
#: src/app/main/ui/workspace/main_menu.cljs:910
msgid "subscription.workspace.header.menu.option.power-up"
msgstr "Mejora tu plan"
msgid "subscription.dashboard.team-plan"
msgstr "Plan de equipo"
msgid "subscription.dashboard.power-up.professional.top-title"
msgstr "Plan Professional"
#, markdown
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr "Consigue editores y almacenamiento adicionales, copias de seguridad de archivos y mucho más con el Plan Unlimited"
"[Mejóralo](%s)"
msgid "subscription.dashboard.power-up.unlimited-plan"
msgstr "Plan ilimitado"
#, markdown
msgid "subscription.dashboard.power-up.unlimited.bottom-description"
msgstr ""
"Obtenga seguridad avanzada, registros de actividad, asistencia dedicada y mucho más. Echa un ojo al"
"[Plan Enterprise](%s)"
msgid "subscription.dashboard.power-up.enterprise-plan"
msgstr "Plan Enterprise"
msgid "subscription.dashboard.power-up.trial.top-title"
msgstr "Plan Unlimited (Prueba)"
#, markdown
msgid "subscription.dashboard.power-up.trial.bottom-description"
msgstr ""
"¿Disfrutas de la prueba? Desbloquea el acceso completo para siempre."
"[Suscríbete](%s)"
msgid "subscription.dashboard.cta.unlimited-many-editors"
msgstr "¡Parece que tu equipo ha crecido! Tu plan incluye %s asientos, pero ahora estás usando más que eso."
msgid "subscription.dashboard.cta.professional-plan-designed"
msgstr "El plan Professional está diseñado para equipos de hasta 8 editores (propietario, administrador y editor)."
msgid "subscription.dashboard.cta.trial-plan-designed"
msgstr "El plan Unlimited (de prueba) está diseñado para equipos de más de 8 editores (propietario, administrador y editor)."
#, 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.](%s)"
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats"
msgstr ""
"Por favor, mejóralo para adaptarlo a tu uso. "
"[Suscríbete ahora.](%s)"
#, 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"
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-owner"
msgstr ""
"Desbloquea el acceso completo para siempre. "
"[Suscríbete ahora.](%s)"
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-member"
msgstr ""
"Desbloquea el acceso completo para siempre. Contacta con el propietario del equipo: "
"%s"
#: src/app/main/ui/dashboard/team.cljs:909
msgid "dashboard.webhooks.active"
msgstr "Activo"
@ -4489,7 +4564,121 @@ msgstr "Contraseña - Penpot"
msgid "title.settings.profile"
msgstr "Perfil - Penpot"
#: src/app/main/ui/dashboard/team.cljs:795
msgid "subscription.settings.section-plan"
msgstr "Tu suscripción"
msgid "subscription.settings.other-plans"
msgstr "Otros planes de penpot"
msgid "subscription.settings.professional"
msgstr "Professional"
msgid "subscription.settings.unlimited"
msgstr "Unlimited"
msgid "subscription.settings.unlimited-trial"
msgstr "Unlimited (prueba)"
msgid "subscription.settings.unlimited-trial-modal"
msgstr "Unlimited de prueba"
msgid "subscription.settings.enterprise"
msgstr "Enterprise"
msgid "subscription.settings.enterprise-trial"
msgstr "Enterprise (prueba)"
msgid "subscription.settings.enterprise-trial-modal"
msgstr "Enterprise de prueba"
msgid "subscription.settings.support-us-since"
msgstr "Nos has estado apoyando con este plan desde %s"
msgid "subscription.settings.member-since"
msgstr "Miembro de penpot desde %s"
msgid "subscription.settings.price-editor-month"
msgstr "editor por mes"
msgid "subscription.settings.price-organization-month"
msgstr "organización por mes"
msgid "subscription.settings.try-it-free"
msgstr "Pruébalo gratis durante 14 días"
msgid "subscription.settings.professional.projects-files"
msgstr "Proyectos, archivos y borradores ilimitados"
msgid "subscription.settings.professional.teams-editors"
msgstr "Equipos ilimitados de hasta 8 redactores"
msgid "subscription.settings.professional.storage"
msgstr "10 GB de almacenamiento y versiones de autoguardado de 7 días"
msgid "subscription.settings.benefits.all-professional-benefits"
msgstr "Todas las prestaciones del plan Professional y:"
msgid "subscription.settings.benefits.all-unlimited-benefits"
msgstr "Todas las prestaciones del plan Unlimited y:"
msgid "subscription.settings.unlimited.teams"
msgstr "Equipos ilimitados, independientemente de su tamaño"
msgid "subscription.settings.unlimited.bill"
msgstr "Factura mensual limitada"
msgid "subscription.settings.unlimited.storage"
msgstr "25 GB de almacenamiento y 30 días de autoguardado de versiones y copias de seguridad de archivos"
msgid "subscription.settings.manage-your-subscription"
msgstr "Gestionar tu suscripción"
msgid "subscription.settings.add-payment-to-continue"
msgstr "Añade un método de pago para continuar después del periodo de prueba"
msgid "subscription.settings.enterprise.support"
msgstr "Apoyo específico"
msgid "subscription.settings.enterprise.security"
msgstr "Seguridad avanzada"
msgid "subscription.settings.enterprise.logs"
msgstr "Registros de actividad"
msgid "subscription.settings.sucess.dialog.title"
msgstr "Eres %s!"
msgid "subscription.settings.success.dialog.description"
msgstr "Puedes modificar tu suscripción en cualquier momento desde la página 'Suscripción' en los datos de tu cuenta."
msgid "subscription.settings.sucess.dialog.footer"
msgstr "¡Disfruta de tu plan!"
msgid "subscription.settings.management.dialog.title"
msgstr "Aplica %s a tus equipos"
msgid "subscription.settings.management.dialog.choose-this-plan"
msgstr "Estás eligiendo este plan para:"
msgid "subscription.settings.management.dialog.members"
msgstr " (%s editores)"
msgid "subscription.settings.management.dialog.no-teams"
msgstr "Este plan se aplicará a todos los futuros equipos que crees o poseas."
msgid "subscription.settings.management.dialog.select-editors"
msgstr "Seleccione el número de editores (puestos) que necesitas:"
msgid "subscription.settings.management.dialog.price-month"
msgstr "$7 por editor/mes x %s"
msgid "subscription.settings.management.dialog.payment-explanation"
msgstr "(Ahora no se efectuará ningún pago)"
msgid "subscription.settings.management.dialog.downgrade"
msgstr "Ten en cuenta: cambiar a un plan inferior significa menos almacenamiento y copias de seguridad e historial de versiones más cortos."
#: src/app/main/ui/dashboard/team.cljs:779
msgid "title.team-invitations"
msgstr "Invitaciones - %s - Penpot"
@ -4954,10 +5143,6 @@ msgstr "Archivo"
msgid "workspace.header.menu.option.help-info"
msgstr "Ayuda e información"
#: src/app/main/ui/workspace/main_menu.cljs:916
msgid "workspace.header.menu.option.power-up"
msgstr "Mejora tu plan"
#: src/app/main/ui/workspace/main_menu.cljs:881
msgid "workspace.header.menu.option.preferences"
msgstr "Preferencias"