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-tiered-file-data-storage \
enable-file-validation \ enable-file-validation \
enable-file-schema-validation \ enable-file-schema-validation \
enable-subscriptons \ enable-subscriptions \
enable-subscriptons-old"; enable-subscriptions-old";
# Default deletion delay for devenv # Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h" export PENPOT_DELETION_DELAY="24h"

View file

@ -24,8 +24,8 @@ export PENPOT_FLAGS="\
enable-tiered-file-data-storage \ enable-tiered-file-data-storage \
enable-file-validation \ enable-file-validation \
enable-file-schema-validation \ enable-file-schema-validation \
enable-subscriptons \ enable-subscriptions \
enable-subscriptons-old "; enable-subscriptions-old ";
# Default deletion delay for devenv # Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h" 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] (update [_ state]
(assoc state :router (create routes))))) (assoc state :router (create routes)))))
(defn encode-url
[url]
(js/encodeURIComponent url))
(defn match (defn match
"Given routing tree and current path, return match with possibly "Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found." coerced parameters. Return nil if no match found."

View file

@ -21,12 +21,13 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.router :as rt] [app.main.router :as rt]
[app.main.store :as st] [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.components.link :refer [link]]
[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 :as subscription] [app.main.ui.dashboard.subscription :refer [subscription-sidebar* menu-team-icon*]]
[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]
@ -330,8 +331,14 @@
[:img {:src (cf/resolve-team-photo-url team-item) [:img {:src (cf/resolve-team-photo-url team-item)
:class (stl/css :team-picture) :class (stl/css :team-picture)
:alt (:name team-item)}] :alt (:name team-item)}]
[:span {:class (stl/css :team-text)
:title (:name team-item)} (: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)])
(when (= (:id team-item) (:id team)) (when (= (:id team-item) (:id team))
tick-icon)]) tick-icon)])
@ -645,19 +652,35 @@
handle-close-team handle-close-team
(fn [] (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 :sidebar-team-switch)}
[:div {:class (stl/css :switch-content)} [:div {:class (stl/css :switch-content)}
[:button {:class (stl/css :current-team) [:button {:class (stl/css :current-team)
:on-click handle-show-team-click :on-click handle-show-team-click
:on-key-down handle-show-team-keydown} :on-key-down handle-show-team-keydown}
(cond
(if (:is-default team) (:is-default team)
[:div {:class (stl/css :team-name)} [:div {:class (stl/css :team-name)}
[:span {:class (stl/css :penpot-icon)} i/logo-icon] [:span {:class (stl/css :penpot-icon)} i/logo-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.default-team-name")]] [: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)} [:div {:class (stl/css :team-name)}
[:img {:src (cf/resolve-team-photo-url team) [:img {:src (cf/resolve-team-photo-url team)
:class (stl/css :team-picture) :class (stl/css :team-picture)
@ -964,7 +987,7 @@
[:* [:*
(when (contains? cf/flags :subscriptions) (when (contains? cf/flags :subscriptions)
[:> subscription/sidebar*]) [:> 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)
@ -974,7 +997,7 @@
[:span (tr "dashboard.upgrade-plan.penpot-free")] [:span (tr "dashboard.upgrade-plan.penpot-free")]
[:span {:class (stl/css :no-limits)} (tr "dashboard.upgrade-plan.no-limits")]] [:span {:class (stl/css :no-limits)} (tr "dashboard.upgrade-plan.no-limits")]]
[:div {:class (stl/css :power-up)} [:div {:class (stl/css :power-up)}
(tr "dashboard.upgrade-plan.power-up")]]) (tr "subscription.dashboard.upgrade-plan.power-up")]])
(when (and team profile) (when (and team profile)
[:& comments-section [:& comments-section

View file

@ -84,11 +84,18 @@
.team-text { .team-text {
@include textEllipsis; @include textEllipsis;
@include smallTitleTipography; @include smallTitleTipography;
width: $s-144; width: auto;
text-align: left; text-align: left;
color: var(--menu-foreground-color-hover); 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 // This icon still use the old svg
.penpot-icon { .penpot-icon {
@include flexCenter; @include flexCenter;

View file

@ -3,15 +3,21 @@
(ns app.main.ui.dashboard.subscription (ns app.main.ui.dashboard.subscription
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm]
[app.config :as cf]
[app.main.router :as rt] [app.main.router :as rt]
[app.main.store :as st] [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.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]]
[app.util.keyboard :as kbd]
[lambdaisland.uri :as u]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc cta-power-up* (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) (let [show-data* (mf/use-state false)
show-data (deref show-data*) show-data (deref show-data*)
handle-click handle-click
@ -24,55 +30,166 @@
:on-click handle-click} :on-click handle-click}
[:button {:class (stl/css :cta-top-section)} [:button {:class (stl/css :cta-top-section)}
[:div {:class (stl/css :content)} [:div {:class (stl/css :content)}
[:span {:class (stl/css :cta-title)} top-title] [:span {:class (stl/css :cta-title :cta-text)} top-title]
[:span {:class (stl/css :cta-text)} top-description]] [:span {:class (stl/css :cta-text-m)} top-description]]
(when has-dropdown [:span {:class (stl/css :icon-dropdown)} i/arrow])] (when has-dropdown [:span {:class (stl/css :icon-dropdown)} i/arrow])]
(when (and has-dropdown show-data) (when (and has-dropdown show-data)
[:div {:class (stl/css :cta-bottom-section)} [:div {:class (stl/css :cta-bottom-section)}
[:> i18n/tr-html* {:content bottom-description [:> i18n/tr-html* {:content bottom-description
:class (stl/css :content) :class (stl/css :content)
:tag-name "button"}] :tag-name "span"}]])]))
[:button {:class (stl/css :cta-highlight :cta-link) :on-click cta-link}
cta-text]])]))
(mf/defc sidebar* (mf/defc subscription-sidebar*
[] [{:keys [profile]}]
(let [;; TODO subscription cases professional/unlimited/enterprise (let [subscription (:subscription (:props profile))
subscription-name :unlimited subscription-name (if subscription
subscription-is-trial false (:type subscription)
"professional")
go-to-subscription subscription-is-trial (= (:status subscription) "trialing")
(mf/use-fn #(st/emit! (rt/nav :settings-subscription)))] subscription-href (dm/str (u/join cf/public-uri "#/settings/subscriptions"))]
(case subscription-name (case subscription-name
:professional "professional"
[:> cta-power-up* [:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.professional.top-title") {:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "dashboard.upgrade-plan.no-limits") :top-description (tr "subscription.dashboard.power-up.professional.top-title")
:bottom-description (tr "subscription.dashboard.power-up.professional.bottom-description") :bottom-description (tr "subscription.dashboard.power-up.professional.bottom-description", subscription-href)
:cta-text (tr "dashboard.upgrade-plan.power-up")
:cta-link go-to-subscription
:has-dropdown true}] :has-dropdown true}]
:unlimited "unlimited"
(if subscription-is-trial (if subscription-is-trial
[:> cta-power-up* [:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.trial.top-title") {:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.trial.top-description") :top-description (tr "subscription.dashboard.power-up.trial.top-title")
:bottom-description (tr "subscription.dashboard.power-up.trial.bottom-description") :bottom-description (tr "subscription.dashboard.power-up.trial.bottom-description", subscription-href)
:cta-text (tr "subscription.dashboard.power-up.subscribe")}] :has-dropdown true}]
[:> cta-power-up* [:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.unlimited-plan") {:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.unlimited.top-description") :top-description (tr "subscription.dashboard.power-up.unlimited-plan")
:bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom-description") :bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom-description", subscription-href)
:cta-text (tr "subscription.dashboard.power-up.unlimited.cta")
:cta-link go-to-subscription
:has-dropdown true}]) :has-dropdown true}])
:enterprise "enterprise"
[:> cta-power-up* [:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.enterprise-plan") {:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.enterprise.description") :top-description (tr "subscription.dashboard.power-up.enterprise-plan")
:has-dropdown false}]))) :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 "common/refactor/common-dashboard";
@use "../ds/typography.scss" as t; @use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *; @use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
.cta-power-up { .cta-power-up {
display: flex; display: flex;
@ -27,7 +28,7 @@
.icon-dropdown { .icon-dropdown {
@include flexCenter; @include flexCenter;
height: 100%; height: 100%;
width: $s-16; width: var(--sp-l);
} }
.icon-dropdown svg { .icon-dropdown svg {
@ -37,42 +38,114 @@
} }
.cta-bottom-section { .cta-bottom-section {
border-block-start: $s-1 solid var(--color-background-quaternary); border-block-start: $b-1 solid var(--color-background-quaternary);
display: grid;
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
grid-template-columns: 1fr auto; margin-block-start: var(--sp-m);
margin-block-start: $s-12; padding-block-start: var(--sp-m);
padding-block-start: $s-12;
} }
.cta-bottom-section .content { .cta-bottom-section .content {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
@include buttonStyle; @include buttonStyle;
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
display: inline; display: inline-block;
text-align: left; text-align: left;
} }
.cta-text, .cta-text,
.cta-text-m,
.cta-title { .cta-title {
text-align: left; text-align: left;
} }
.cta-title {
margin-block-end: var(--sp-xs);
}
.cta-text { .cta-text {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
} }
.cta-title { .cta-text-m {
@include t.use-typography("body-medium"); @include t.use-typography("body-medium");
} }
.cta-bottom-section .content strong, .cta-bottom-section .content a {
.cta-highlight {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
color: var(--color-accent-tertiary); color: var(--color-accent-tertiary);
margin-inline-start: var(--sp-xs);
} }
.cta-link { .cta-link {
@include buttonStyle; @include buttonStyle;
align-self: end; 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.file-uploader :refer [file-uploader]]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.dashboard.change-owner] [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.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]
@ -537,10 +541,22 @@
[:* [:*
[:& header {:section :dashboard-team-members :team team}] [:& 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* [:> team-members*
{:profile profile {: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 ;; INVITATIONS SECTION
@ -803,8 +819,18 @@
[:* [:*
[:& header {:section :dashboard-team-invitations [:& header {:section :dashboard-team-invitations
:team team}] :team team}]
[:section {:class (stl/css :dashboard-team-invitations)} [:section {:class (stl/css-case
[:> invitation-section* {:team team}]]]) :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 ;; WEBHOOKS SECTION
@ -1159,5 +1185,8 @@
[:div {:class (stl/css :block-content)} [:div {:class (stl/css :block-content)}
document-icon document-icon
[:span {:class (stl/css :block-text)} [: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
.dashboard-team-settings { .dashboard-team-settings {
display: grid; display: flex;
grid-template-rows: auto auto 1fr; flex-direction: column;
justify-items: center; align-items: center;
gap: $s-24; gap: $s-24;
width: 100%; width: 100%;
border-top: $s-1 solid var(--panel-border-color); border-top: $s-1 solid var(--panel-border-color);
overflow-y: auto; overflow-y: auto;
padding-inline: $s-24;
} }
.block { .block {
@ -23,7 +24,7 @@
grid-auto-rows: min-content; grid-auto-rows: min-content;
gap: $s-8; gap: $s-8;
max-width: $s-1000; max-width: $s-1000;
width: $s-1000; width: 100%;
} }
.info-block { .info-block {
@ -105,22 +106,28 @@
// TEAM MEMBERS PAGE // TEAM MEMBERS PAGE
.dashboard-team-members { .dashboard-team-members {
display: grid; display: flex;
justify-items: center; justify-content: center;
width: 100%; width: 100%;
height: 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); border-top: $s-1 solid var(--panel-border-color);
overflow-y: auto; overflow-y: auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
.dashboard-team-members.dashboard-top-cta {
flex-direction: column;
justify-content: flex-start;
}
.team-members { .team-members {
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
height: fit-content; height: fit-content;
max-width: $s-1000; max-width: $s-1000;
width: $s-1000; width: 100%;
} }
.table-header { .table-header {
@ -275,22 +282,28 @@
// TEAM INVITATION PAGE // TEAM INVITATION PAGE
.dashboard-team-invitations { .dashboard-team-invitations {
display: grid; display: flex;
justify-items: center; justify-content: center;
width: 100%; width: 100%;
height: 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); border-top: $s-1 solid var(--panel-border-color);
overflow-y: auto; overflow-y: auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
.dashboard-team-invitations .dashboard-top-cta {
flex-direction: flex;
justify-content: flex-start;
}
.invitations { .invitations {
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
height: fit-content; height: fit-content;
max-width: $s-1000; max-width: $s-1000;
width: $s-1000; width: 100%;
} }
.table-row-invitations { .table-row-invitations {

View file

@ -19,6 +19,7 @@
(def ^:svg-id loader "loader") (def ^:svg-id loader "loader")
(def ^:svg-id logo-error-screen "logo-error-screen") (def ^:svg-id logo-error-screen "logo-error-screen")
(def ^:svg-id login-illustration "login-illustration") (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-arrows "marketing-arrows")
(def ^:svg-id marketing-exchange "marketing-exchange") (def ^:svg-id marketing-exchange "marketing-exchange")
(def ^:svg-id marketing-file "marketing-file") (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-icon (icon-xref :penpot-logo-icon))
(def ^:icon logo-error-screen (icon-xref :logo-error-screen)) (def ^:icon logo-error-screen (icon-xref :logo-error-screen))
(def ^:icon login-illustration (icon-xref :login-illustration)) (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-openid (icon-xref :brand-openid))
(def ^:icon brand-github (icon-xref :brand-github)) (def ^:icon brand-github (icon-xref :brand-github))

View file

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

View file

@ -1,16 +1,24 @@
(ns app.main.ui.settings.subscription (ns app.main.ui.settings.subscription
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (: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.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.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]]
[app.util.time :as dt] [app.util.time :as dt]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc plan-card* (mf/defc plan-card*
{::mf/props :obj} {::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)}
[:div {:class (stl/css :plan-card-header)} [:div {:class (stl/css :plan-card-header)}
[:div {:class (stl/css :plan-card-title-container)} [:div {:class (stl/css :plan-card-title-container)}
@ -23,25 +31,195 @@
(when benefits-title [:h5 {:class (stl/css :benefits-title)} benefits-title]) (when benefits-title [:h5 {:class (stl/css :benefits-title)} benefits-title])
[:ul {:class (stl/css :benefits-list)} [:ul {:class (stl/css :benefits-list)}
(for [benefit benefits] (for [benefit benefits]
[:li {:key (str benefit) :class (stl/css :benefit)} "- " benefit])] [:li {:key (dm/str benefit) :class (stl/css :benefit)} "- " benefit])]
(when (and cta-link cta-text) [:a {:class (stl/css :cta-button) (when (and cta-link-with-icon cta-text-with-icon) [:button {:class (stl/css :cta-button :more-info)
:href cta-link} cta-text])]) :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* (mf/defc subscription-page*
[] [{:keys [profile]}]
(let [;; TODO subscription cases professional/unlimited/enterprise (let [route (mf/deref refs/route)
subscription-name :unlimited params (:params route)
subscription-is-trial false show-subscription-success-modal (and (:query params)
locale (mf/deref i18n/locale) (or (= (:subscription (:query params)) "subscribed-to-penpot-unlimited")
profile (mf/deref refs/profile) (= (:subscription (:query params)) "subscribed-to-penpot-enterprise")))
penpot-member (dt/format-date-locale-short (:created-at profile) {:locale locale}) subscription (:subscription (:props profile))
;; TODO get subscription member date subscription-name (if subscription
subscription-member "January 17, 2024" (:type subscription)
;; TODO update url to penpot payments "professional")
go-to-payments "https://penpot.app/pricing"] subscription-is-trial (= (:status subscription) "trialing")
teams* (mf/use-state nil)
teams (deref teams*)
locale (mf/deref i18n/locale)
penpot-member (dt/format-date-locale-short (:created-at profile) {:locale locale})
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 [] (mf/with-effect []
(dom/set-html-title (tr "subscription.labels"))) (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)} [:section {:class (stl/css :dashboard-section)}
[:div {:class (stl/css :dashboard-content)} [:div {:class (stl/css :dashboard-content)}
[:h2 {:class (stl/css :title-section)} (tr "subscription.labels")] [:h2 {:class (stl/css :title-section)} (tr "subscription.labels")]
@ -50,13 +228,13 @@
[:div {:class (stl/css :your-subscription)} [:div {:class (stl/css :your-subscription)}
[:h3 {:class (stl/css :plan-section-title)} (tr "subscription.settings.section-plan")] [:h3 {:class (stl/css :plan-section-title)} (tr "subscription.settings.section-plan")]
(case subscription-name (case subscription-name
: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.projects-files"),
(tr "subscription.settings.professional.teams-editors"), (tr "subscription.settings.professional.teams-editors"),
(tr "subscription.settings.professional.storage")]}] (tr "subscription.settings.professional.storage")]}]
: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
@ -65,7 +243,9 @@
(tr "subscription.settings.unlimited.bill"), (tr "subscription.settings.unlimited.bill"),
(tr "subscription.settings.unlimited.storage")] (tr "subscription.settings.unlimited.storage")]
: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.unlimited") [:> plan-card* {:card-title (tr "subscription.settings.unlimited")
:card-title-icon i/character-u :card-title-icon i/character-u
@ -76,10 +256,10 @@
: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}])
:enterprise "enterprise"
[:> 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-professiona-benefits") :benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
:benefits [(tr "subscription.settings.enterprise.support"), :benefits [(tr "subscription.settings.enterprise.support"),
(tr "subscription.settings.enterprise.security"), (tr "subscription.settings.enterprise.security"),
(tr "subscription.settings.enterprise.logs")] (tr "subscription.settings.enterprise.logs")]
@ -97,36 +277,42 @@
[:div {:class (stl/css :other-subscriptions)} [:div {:class (stl/css :other-subscriptions)}
[:h3 {:class (stl/css :plan-section-title)} (tr "subscription.settings.other-plans")] [: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") [:> 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.projects-files"),
(tr "subscription.settings.professional.teams-editors"), (tr "subscription.settings.professional.teams-editors"),
(tr "subscription.settings.professional.storage")] (tr "subscription.settings.professional.storage")]
:cta-text (tr "subscription.dashboard.power-up.subscribe") :cta-text (tr "subscription.settings.subscribe")
:cta-link go-to-payments}]) :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") [:> plan-card* {:card-title (tr "subscription.settings.unlimited")
:card-title-icon i/character-u :card-title-icon i/character-u
: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-professiona-benefits") :benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
:benefits [(tr "subscription.settings.unlimited.teams"), :benefits [(tr "subscription.settings.unlimited.teams"),
(tr "subscription.settings.unlimited.bill"), (tr "subscription.settings.unlimited.bill"),
(tr "subscription.settings.unlimited.storage")] (tr "subscription.settings.unlimited.storage")]
:cta-text (tr "subscription.settings.ulimited.try-it-free") :cta-text (if subscription (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
:cta-link go-to-payments}]) :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") [:> plan-card* {:card-title (tr "subscription.settings.enterprise")
:card-title-icon i/character-e :card-title-icon i/character-e
: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-professiona-benefits") :benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
:benefits [(tr "subscription.settings.enterprise.support"), :benefits [(tr "subscription.settings.enterprise.support"),
(tr "subscription.settings.enterprise.security"), (tr "subscription.settings.enterprise.security"),
(tr "subscription.settings.enterprise.logs")] (tr "subscription.settings.enterprise.logs")]
:cta-text (tr "subscription.dashboard.power-up.subscribe") :cta-text (if subscription (tr "subscription.settings.subscribe") (tr "subscription.settings.try-it-free"))
:cta-link go-to-payments}])]]])) :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 "common/refactor/common-refactor.scss" as *;
@use "../ds/typography.scss" as t; @use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
.dashboard-section { .dashboard-section {
display: flex; display: flex;
@ -19,30 +21,30 @@
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
max-width: $s-500; max-width: $s-500;
margin-bottom: $s-32; margin-block-end: var(--sp-xxxl);
width: $s-580; width: $s-580;
margin: $s-92 auto $s-120 auto; margin: $s-92 auto $s-120 auto;
justify-content: center; justify-content: center;
} }
.membership-container { .membership-container {
margin-block-start: $s-16; margin-block-start: var(--sp-l);
} }
.membership { .membership {
align-items: center; align-items: center;
display: flex; display: flex;
margin-block-start: $s-8; margin-block-start: var(--sp-s);
} }
.membership.first { .membership.first {
margin-block-start: $s-16; margin-block-start: var(--sp-l);
} }
.membership-date { .membership-date {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
margin-inline-start: $s-8; margin-inline-start: var(--sp-s);
} }
.subscription-member, .subscription-member,
@ -61,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: $s-16; margin-block-end: var(--sp-l);
} }
.plan-section-title { .plan-section-title {
@ -70,29 +72,32 @@
} }
.plan-card { .plan-card {
border: $s-1 solid var(--color-foreground-secondary); border: $b-1 solid var(--color-foreground-secondary);
border-radius: $s-8; border-radius: var(--sp-s);
margin-block-start: $s-16; margin-block-start: var(--sp-l);
padding: $s-16; padding: var(--sp-l);
} }
.plan-card-header { .plan-card-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-block-end: $s-8; margin-block-end: var(--sp-s);
} }
.plan-card-title-container { .plan-card-title-container {
display: flex; display: flex;
align-items: center; align-items: center;
gap: $s-8; gap: var(--sp-s);
} }
.plan-title-icon { .plan-title-icon {
@extend .button-icon; @extend .button-icon;
stroke: var(--color-foreground-primary); stroke: var(--color-foreground-primary);
border-radius: $s-4; height: var(--sp-xl);
border: $s-1 solid var(--color-foreground-primary); width: var(--sp-xl);
border-radius: var(--sp-xs);
border: $b-1 solid var(--color-foreground-primary);
padding: $s-1;
} }
.plan-card-title, .plan-card-title,
@ -122,5 +127,134 @@
.cta-button { .cta-button {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
@include buttonStyle;
color: var(--color-accent-tertiary); 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.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.context :as ctx] [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.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.hooks.resize :as r] [app.main.ui.hooks.resize :as r]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@ -819,7 +820,12 @@
(reset! sub-menu* nil) (reset! sub-menu* nil)
(st/emit! (st/emit!
(ptk/event ::ev/event {::ev/name "open-plugins-manager" ::ev/origin "workspace:menu"}) (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 [] (mf/with-effect []
(let [disposable (->> st/stream (let [disposable (->> st/stream
@ -904,6 +910,10 @@
:id "file-menu-help-info"} :id "file-menu-help-info"}
[:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.help-info")] [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.help-info")]
[:span {:class (stl/css :open-arrow)} i/arrow]] [: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 ;; TODO remove this block when subscriptions is full implemented
(when (contains? cf/flags :subscriptions-old) (when (contains? cf/flags :subscriptions-old)
[:> dropdown-menu-item* {:class (stl/css-case :menu-item true) [:> dropdown-menu-item* {:class (stl/css-case :menu-item true)
@ -913,7 +923,7 @@
(on-power-up-click))) (on-power-up-click)))
:on-pointer-enter close-sub-menu :on-pointer-enter close-sub-menu
:id "file-menu-power-up"} :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 (case sub-menu
:file :file

View file

@ -1067,11 +1067,72 @@ msgstr "No limits on creativity"
msgid "dashboard.upgrade-plan.penpot-free" msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Free" msgstr "Penpot Free"
#: src/app/main/ui/dashboard/sidebar.cljs:977, src/app/main/ui/dashboard/subscription.cljs:54 #: src/app/main/ui/dashboard/sidebar.cljs:972
msgid "dashboard.upgrade-plan.power-up" msgid "subscription.dashboard.upgrade-plan.power-up"
msgstr "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" msgid "dashboard.webhooks.active"
msgstr "Is active" msgstr "Is active"
@ -4267,13 +4328,6 @@ msgstr "Enterprise plan"
msgid "subscription.dashboard.power-up.enterprise.description" msgid "subscription.dashboard.power-up.enterprise.description"
msgstr "Advanced security, activity logs, dedicated support and more." 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 #: src/app/main/ui/dashboard/subscription.cljs:51
msgid "subscription.dashboard.power-up.professional.top-title" msgid "subscription.dashboard.power-up.professional.top-title"
msgstr "Professional plan" msgstr "Professional plan"
@ -4282,10 +4336,6 @@ msgstr "Professional plan"
msgid "subscription.dashboard.power-up.subscribe" msgid "subscription.dashboard.power-up.subscribe"
msgstr "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 #: src/app/main/ui/dashboard/subscription.cljs:62
msgid "subscription.dashboard.power-up.trial.top-description" msgid "subscription.dashboard.power-up.trial.top-description"
msgstr "Extra editors, storage and autosaved version, file backup and more." 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" msgid "subscription.dashboard.power-up.unlimited-plan"
msgstr "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 #: src/app/main/ui/dashboard/subscription.cljs:69
#, markdown #, markdown
msgid "subscription.dashboard.power-up.unlimited.bottom-description" msgid "subscription.dashboard.power-up.unlimited.bottom-description"
msgstr "" msgstr ""
"Get advanced security, activity logs, dedicated support and more with " "Get advanced security, activity logs, dedicated support and more. Take a look to the"
"**Enterprise plan**" "[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 #: src/app/main/ui/dashboard/subscription.cljs:70
msgid "subscription.dashboard.power-up.unlimited.cta" msgid "subscription.dashboard.power-up.unlimited.cta"
@ -4466,7 +4529,127 @@ msgstr "Password - Penpot"
msgid "title.settings.profile" msgid "title.settings.profile"
msgstr "Profile - Penpot" 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" msgid "title.team-invitations"
msgstr "Invitations - %s - Penpot" msgstr "Invitations - %s - Penpot"
@ -4474,7 +4657,6 @@ msgstr "Invitations - %s - Penpot"
msgid "title.team-members" msgid "title.team-members"
msgstr "Members - %s - Penpot" msgstr "Members - %s - Penpot"
#: src/app/main/ui/dashboard/team.cljs:1105
msgid "title.team-settings" msgid "title.team-settings"
msgstr "Settings - %s - Penpot" msgstr "Settings - %s - Penpot"
@ -4932,12 +5114,6 @@ msgstr "Help & info"
#: src/app/main/ui/workspace/main_menu.cljs:916 #: src/app/main/ui/workspace/main_menu.cljs:916
msgid "workspace.header.menu.option.power-up" msgid "workspace.header.menu.option.power-up"
msgstr "Power up your plan" 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" msgid "workspace.header.menu.option.view"
msgstr "View" msgstr "View"

View file

@ -1079,11 +1079,86 @@ msgstr "Sin límites a la creatividad"
msgid "dashboard.upgrade-plan.penpot-free" msgid "dashboard.upgrade-plan.penpot-free"
msgstr "Penpot Gratis" msgstr "Penpot Gratis"
#: src/app/main/ui/dashboard/sidebar.cljs:977, src/app/main/ui/dashboard/subscription.cljs:54 #: src/app/main/ui/dashboard/sidebar.cljs:972
msgid "dashboard.upgrade-plan.power-up" msgid "subscription.dashboard.upgrade-plan.power-up"
msgstr "Mejora" 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" msgid "dashboard.webhooks.active"
msgstr "Activo" msgstr "Activo"
@ -4489,7 +4564,121 @@ msgstr "Contraseña - Penpot"
msgid "title.settings.profile" msgid "title.settings.profile"
msgstr "Perfil - Penpot" 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" msgid "title.team-invitations"
msgstr "Invitaciones - %s - Penpot" msgstr "Invitaciones - %s - Penpot"
@ -4954,10 +5143,6 @@ msgstr "Archivo"
msgid "workspace.header.menu.option.help-info" msgid "workspace.header.menu.option.help-info"
msgstr "Ayuda e información" 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 #: src/app/main/ui/workspace/main_menu.cljs:881
msgid "workspace.header.menu.option.preferences" msgid "workspace.header.menu.option.preferences"
msgstr "Preferencias" msgstr "Preferencias"