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"