diff --git a/backend/scripts/repl b/backend/scripts/repl
index 04865d1004..87910ab202 100755
--- a/backend/scripts/repl
+++ b/backend/scripts/repl
@@ -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"
diff --git a/backend/scripts/start-dev b/backend/scripts/start-dev
index 5f446ef817..8bf85eb0b1 100755
--- a/backend/scripts/start-dev
+++ b/backend/scripts/start-dev
@@ -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"
diff --git a/frontend/resources/images/assets/logo-subscription.svg b/frontend/resources/images/assets/logo-subscription.svg
new file mode 100644
index 0000000000..17d2605eb6
--- /dev/null
+++ b/frontend/resources/images/assets/logo-subscription.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/resources/images/icons/logo-subscription.svg b/frontend/resources/images/icons/logo-subscription.svg
new file mode 100644
index 0000000000..17d2605eb6
--- /dev/null
+++ b/frontend/resources/images/icons/logo-subscription.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/app/main/router.cljs b/frontend/src/app/main/router.cljs
index 746d69ede6..1e234e8af1 100644
--- a/frontend/src/app/main/router.cljs
+++ b/frontend/src/app/main/router.cljs
@@ -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."
diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs
index 32386ef988..a3b9a82739 100644
--- a/frontend/src/app/main/ui/dashboard/sidebar.cljs
+++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs
@@ -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)}]
- [: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))
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
diff --git a/frontend/src/app/main/ui/dashboard/sidebar.scss b/frontend/src/app/main/ui/dashboard/sidebar.scss
index 328a7c0043..cdc4c57b46 100644
--- a/frontend/src/app/main/ui/dashboard/sidebar.scss
+++ b/frontend/src/app/main/ui/dashboard/sidebar.scss
@@ -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;
diff --git a/frontend/src/app/main/ui/dashboard/subscription.cljs b/frontend/src/app/main/ui/dashboard/subscription.cljs
index ba2dccdb57..e052d6b0ff 100644
--- a/frontend/src/app/main/ui/dashboard/subscription.cljs
+++ b/frontend/src/app/main/ui/dashboard/subscription.cljs
@@ -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 "" email-owner "")
+ 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")))
diff --git a/frontend/src/app/main/ui/dashboard/subscription.scss b/frontend/src/app/main/ui/dashboard/subscription.scss
index 2da07be401..cf773921c9 100644
--- a/frontend/src/app/main/ui/dashboard/subscription.scss
+++ b/frontend/src/app/main/ui/dashboard/subscription.scss
@@ -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);
+ }
}
diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs
index f31e552d9e..76ab3cd6f5 100644
--- a/frontend/src/app/main/ui/dashboard/team.cljs
+++ b/frontend/src/app/main/ui/dashboard/team.cljs
@@ -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}])]]))
diff --git a/frontend/src/app/main/ui/dashboard/team.scss b/frontend/src/app/main/ui/dashboard/team.scss
index cdb45f06a5..92b8022fef 100644
--- a/frontend/src/app/main/ui/dashboard/team.scss
+++ b/frontend/src/app/main/ui/dashboard/team.scss
@@ -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 {
diff --git a/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs b/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs
index bacf967e53..f9e4f37298 100644
--- a/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs
+++ b/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs
@@ -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")
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index 35e9afc826..ed60f89991 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -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))
diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs
index b562acf918..88929d928a 100644
--- a/frontend/src/app/main/ui/settings.cljs
+++ b/frontend/src/app/main/ui/settings.cljs
@@ -69,7 +69,7 @@
[:& options-page]
:settings-subscription
- [:> subscription-page*]
+ [:> subscription-page* {:profile profile}]
:settings-access-tokens
[:& access-tokens-page]
diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs
index 96ef703ada..f5d52828b0 100644
--- a/frontend/src/app/main/ui/settings/subscription.cljs
+++ b/frontend/src/app/main/ui/settings/subscription.cljs
@@ -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
- 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"]
+ [{: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)
+ 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 []
(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}])]]]))
diff --git a/frontend/src/app/main/ui/settings/subscription.scss b/frontend/src/app/main/ui/settings/subscription.scss
index a9e743c019..d1903f1b88 100644
--- a/frontend/src/app/main/ui/settings/subscription.scss
+++ b/frontend/src/app/main/ui/settings/subscription.scss
@@ -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;
}
diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs
index 149bb5dcda..7bc717faf6 100644
--- a/frontend/src/app/main/ui/workspace/main_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/main_menu.cljs
@@ -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
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index a13102ee0a..7e4700bf32 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -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"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 9abe0d7b82..b959980c77 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -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"