🐛 Add fixes from subscription design review (#6870)

* 🐛 Fixes from subscription design review

* 🐛 Fix to consider professional plan the unpaid and canceled status

* 📎 Fixes PR feedback
This commit is contained in:
Marina López 2025-07-10 11:55:16 +02:00 committed by GitHub
parent 66c5841d48
commit ba6a02d1d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 312 additions and 154 deletions

View file

@ -140,7 +140,7 @@
WHEN 'professional' THEN 'active'
ELSE COALESCE(p.props->'~:subscription'->>'~:status', 'incomplete')
END,
'~:seats', p.props->'~:quantity'
'~:seats', p.props->'~:subscription'->'~:quantity'
) AS subscription
FROM team_profile_rel AS tp
JOIN team AS t ON (t.id = tp.team_id)

View file

@ -0,0 +1,25 @@
{
"~:email": "foo@example.com",
"~:is-demo": false,
"~:auth-backend": "penpot",
"~:fullname": "Princesa Leia",
"~:modified-at": "~m1713533116365",
"~:is-active": true,
"~:default-project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
"~:is-muted": false,
"~:default-team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
"~:created-at": "~m1713533116365",
"~:is-blocked": false,
"~:props": {
"~:subscription": {
"~:quantity": 2,
"~:status": "canceled",
"~:type": "enterprise",
"~start-date": "~m1746444667"
},
"~:v2-info-shown": true,
"~:viewed-tutorial?": false,
"~:viewed-walkthrough?": false
}
}

View file

@ -0,0 +1,25 @@
{
"~:email": "foo@example.com",
"~:is-demo": false,
"~:auth-backend": "penpot",
"~:fullname": "Princesa Leia",
"~:modified-at": "~m1713533116365",
"~:is-active": true,
"~:default-project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
"~:is-muted": false,
"~:default-team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
"~:created-at": "~m1713533116365",
"~:is-blocked": false,
"~:props": {
"~:subscription": {
"~:quantity": 2,
"~:status": "unpaid",
"~:type": "unlimited",
"~start-date": "~m1746444667"
},
"~:v2-info-shown": true,
"~:viewed-tutorial?": false,
"~:viewed-walkthrough?": false
}
}

View file

@ -17,7 +17,8 @@
},
"~:subscription": {
"~:type": "enterprise",
"~:status": "trialing"
"~:status": "trialing",
"~:seats": null
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",

View file

@ -41,7 +41,8 @@
},
"~:subscription": {
"~:type": "professional",
"~:status": "active"
"~:status": "active",
"~:seats": null
},
"~:name": "Second team",
"~:modified-at": "~m1701164272671",

View file

@ -17,7 +17,8 @@
},
"~:subscription": {
"~:type": "unlimited",
"~:status": "trialing"
"~:status": "trialing",
"~:seats": 2
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",

View file

@ -41,7 +41,8 @@
},
"~:subscription": {
"~:type": "unlimited",
"~:status": "trialing"
"~:status": "trialing",
"~:seats": 2
},
"~:name": "Second team",
"~:modified-at": "~m1701164272671",

View file

@ -41,7 +41,8 @@
},
"~:subscription": {
"~:type": "unlimited",
"~:status": "trialing"
"~:status": "trialing",
"~:seats": 2
},
"~:name": "Second team",
"~:modified-at": "~m1701164272671",

View file

@ -79,6 +79,74 @@ test.describe("Subscriptions: dashboard", () => {
"Unlimited plan (trial)",
);
});
test("When the subscription status is unpaid, the sidebar dropdown displays the name Professional for the Unlimited subscription", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-unpaid-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await expect(page.getByTestId("subscription-name")).toHaveText(
"Professional plan",
);
});
test("When the subscription status is canceled, the sidebar dropdown displays the name Professional for the Enterprise subscription", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-canceled-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await expect(page.getByTestId("subscription-name")).toHaveText(
"Professional plan",
);
});
});
test.describe("Subscriptions: team members and invitations", () => {

View file

@ -8,6 +8,7 @@
// INPUT
.input-wrapper {
--input-icon-padding: var(--sp-l);
display: flex;
flex-direction: column;
align-items: center;
@ -108,7 +109,7 @@
align-items: center;
justify-content: center;
position: absolute;
right: $s-16;
right: var(--input-icon-padding);
top: calc(50% - $s-8);
svg {
width: $s-12;

View file

@ -132,7 +132,7 @@
[:> team-members-page* {:team team :profile profile}]
:dashboard-invitations
[:> team-invitations-page* {:team team :profile profile}]
[:> team-invitations-page* {:team team}]
:dashboard-webhooks
[:> webhooks-page* {:team team}]

View file

@ -27,7 +27,7 @@
[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 :refer [subscription-sidebar* menu-team-icon*]]
[app.main.ui.dashboard.subscription :refer [subscription-sidebar* menu-team-icon* get-subscription-type]]
[app.main.ui.dashboard.team-form]
[app.main.ui.icons :as i :refer [icon-xref]]
[app.util.dom :as dom]
@ -333,10 +333,10 @@
:alt (:name team-item)}]
(if (and (contains? cf/flags :subscriptions)
(or (= "unlimited" (:type (:subscription team-item))) (= "enterprise" (:type (:subscription team-item)))))
(#{"unlimited" "enterprise"} (get-subscription-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))}]]
[:> menu-team-icon* {:subscription-type (get-subscription-type (:subscription team-item))}]]
[:span {:class (stl/css :team-text)
:title (:name team-item)} (:name team-item)])
(when (= (:id team-item) (:id team))
@ -654,7 +654,7 @@
(fn []
(reset! show-teams-ddwn? false))
subscription (:subscription team)
subscription-name (:type subscription)]
subscription-type (get-subscription-type subscription)]
[:div {:class (stl/css :sidebar-team-switch)}
[:div {:class (stl/css :switch-content)}
@ -669,18 +669,18 @@
(and (contains? cf/flags :subscriptions)
(not (:is-default team))
(or (= "unlimited" subscription-name) (= "enterprise" subscription-name)))
(or (= "unlimited" subscription-type) (= "enterprise" subscription-type)))
[: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}]]]
[:> menu-team-icon* {:subscription-type subscription-type}]]]
(and (not (:is-default team))
(or (not= "unlimited" subscription-name) (not= "enterprise" subscription-name)))
(or (not= "unlimited" subscription-type) (not= "enterprise" subscription-type)))
[:div {:class (stl/css :team-name)}
[:img {:src (cf/resolve-team-photo-url team)
:class (stl/css :team-picture)

View file

@ -18,6 +18,12 @@
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(defn get-subscription-type
[{:keys [type status] :as subscription}]
(if (and subscription (not (contains? #{"unpaid" "canceled"} status)))
type
"professional"))
(mf/defc cta-power-up*
[{:keys [top-title top-description bottom-description has-dropdown]}]
(let [show-data* (mf/use-state false)
@ -46,13 +52,11 @@
(mf/defc subscription-sidebar*
[{:keys [profile]}]
(let [subscription (:subscription (:props profile))
subscription-name (if subscription
(:type subscription)
"professional")
subscription-type (get-subscription-type subscription)
subscription-is-trial (= (:status subscription) "trialing")
subscription-href (dm/str (u/join cf/public-uri "#/settings/subscriptions"))]
(case subscription-name
(case subscription-type
"professional"
[:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
@ -88,7 +92,7 @@
(mf/defc team*
[{:keys [is-owner team]}]
(let [subscription (:subscription team)
subscription-name (:type subscription)
subscription-type (get-subscription-type subscription)
subscription-is-trial (= "trialing" (:status subscription))
go-to-manage-subscription
@ -106,23 +110,23 @@
[:div {:class (stl/css :team-label)}
(tr "subscription.dashboard.team-plan")]
[:span {:class (stl/css :team-text)}
(case subscription-name
(case subscription-type
"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"))
(when (and is-owner (not= subscription-type "professional"))
[:button {:class (stl/css :manage-subscription-link)
:on-click go-to-manage-subscription
:data-testid "manage-subscription-link"}
(tr "subscription.settings.manage-your-subscription")])]))
(mf/defc menu-team-icon*
[{:keys [subscription-name]}]
[{:keys [subscription-type]}]
[:span {:class (stl/css :subscription-icon) :data-testid "subscription-icon"}
(case subscription-name
(case subscription-type
"unlimited" i/character-u
"enterprise" i/character-e)])
@ -139,15 +143,15 @@
[:span {:class (stl/css :item-name)} (tr "subscription.workspace.header.menu.option.power-up")]]))
(mf/defc members-cta*
[{:keys [banner-is-expanded team profile]}]
[{:keys [banner-is-expanded team]}]
(let [subscription (:subscription team)
subscription-name (:type subscription)
is-owner (:is-owner (:permissions team))
subscription-type (get-subscription-type subscription)
is-owner (-> team :permissions :is-owner)
email-owner (:email (some #(when (:is-owner %) %) (:members team)))
mail-to-owner (str "<a href=\"" "mailto:" email-owner "\">" email-owner "</a>")
go-to-subscription (dm/str (u/join cf/public-uri "#/settings/subscriptions"))
seats (-> profile :props :subscription :quantity)
seats (or (:seats subscription) 0)
editors (count (filterv :can-edit (:members team)))
link
@ -157,24 +161,24 @@
cta-title
(cond
(and (= "professional" subscription-name) (>= editors 8))
(and (= "professional" subscription-type) (>= editors 8))
(tr "subscription.dashboard.cta.professional-plan-designed")
(and (= "unlimited" subscription-name) (< seats editors))
(and (= "unlimited" subscription-type) (< seats editors))
(tr "subscription.dashboard.cta.unlimited-many-editors" seats editors))
cta-message
(cond
(and (= "professional" subscription-name) (>= editors 8) is-owner)
(and (= "professional" subscription-type) (>= editors 8) is-owner)
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" link)
(and (= "professional" subscription-name) (>= editors 8) (not is-owner))
(and (= "professional" subscription-type) (>= editors 8) (not is-owner))
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-member" link)
(and (= "unlimited" subscription-name) (< seats editors) is-owner)
(and (= "unlimited" subscription-type) (< seats editors) is-owner)
(tr "subscription.dashboard.cta.upgrade-more-seats-owner" link)
(and (= "unlimited" subscription-name) (< seats editors) (not is-owner))
(and (= "unlimited" subscription-type) (< seats editors) (not is-owner))
(tr "subscription.dashboard.cta.upgrade-more-seats-member" link))]
[:> cta* {:class (stl/css-case ::members-cta-full-width banner-is-expanded :members-cta (not banner-is-expanded)) :title cta-title}
@ -184,25 +188,26 @@
:content cta-message}]]))
(defn show-subscription-members-main-banner?
[team profile]
(let [seats (-> profile :props :subscription :quantity)
editors (count (filter :can-edit (:members team)))]
[team]
(let [subscription-type (get-subscription-type (:subscription team))
seats (-> team :subscription :seats)
editors (count (filter :can-edit (:members team)))]
(or
(and (= (:type (:subscription team)) "professional")
(and (= subscription-type "professional")
(> editors 8))
(and
(= (:type (:subscription team)) "unlimited")
(= subscription-type "unlimited")
(>= editors 8)
(< seats editors)))))
(defn show-subscription-members-small-banner?
[team profile]
(let [seats (-> profile :props :subscription :quantity)
[team]
(let [subscription-type (get-subscription-type (:subscription team))
seats (-> team :subscription :seats)
editors (count (filterv :can-edit (:members team)))]
(or
(and (= (:type (:subscription team)) "professional")
(and (= subscription-type "professional")
(= editors 8))
(and (= (:type (:subscription team)) "unlimited")
(and (= subscription-type "unlimited")
(< editors 8)
(< seats editors)))))

View file

@ -544,18 +544,18 @@
[:section {:class (stl/css-case
:dashboard-container true
:dashboard-team-members true
:dashboard-top-cta (show-subscription-members-main-banner? team profile))}
:dashboard-top-cta (show-subscription-members-main-banner? team))}
(when (and (contains? cfg/flags :subscriptions)
(show-subscription-members-main-banner? team profile))
[:> members-cta* {:banner-is-expanded true :team team :profile profile}])
(show-subscription-members-main-banner? team))
[:> members-cta* {:banner-is-expanded true :team team}])
[:> team-members*
{:profile profile
:team team}]
(when (and
(contains? cfg/flags :subscriptions)
(show-subscription-members-small-banner? team profile))
[:> members-cta* {:banner-is-expanded false :team team :profile profile}])]])
(show-subscription-members-small-banner? team))
[:> members-cta* {:banner-is-expanded false :team team}])]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INVITATIONS SECTION
@ -803,7 +803,7 @@
(mf/defc team-invitations-page*
{::mf/props :obj}
[{:keys [team profile]}]
[{:keys [team]}]
(mf/with-effect [team]
(dom/set-html-title
@ -820,14 +820,14 @@
:team team}]
[:section {:class (stl/css-case
:dashboard-team-invitations true
:dashboard-top-cta (show-subscription-members-main-banner? team profile))}
:dashboard-top-cta (show-subscription-members-main-banner? team))}
(when (and (contains? cfg/flags :subscriptions)
(show-subscription-members-main-banner? team profile))
[:> members-cta* {:banner-is-expanded true :team team :profile profile}])
(show-subscription-members-main-banner? team))
[:> members-cta* {:banner-is-expanded true :team team}])
[:> invitation-section* {:team team}]
(when (and (contains? cfg/flags :subscriptions)
(show-subscription-members-small-banner? team profile))
[:> members-cta* {:banner-is-expanded false :team team :profile profile}])]])
(show-subscription-members-small-banner? team))
[:> members-cta* {:banner-is-expanded false :team team}])]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; WEBHOOKS SECTION

View file

@ -2,7 +2,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.schema :as sm]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
[app.main.data.profile :as du]
@ -10,6 +10,8 @@
[app.main.repo :as rp]
[app.main.router :as rt]
[app.main.store :as st]
[app.main.ui.components.forms :as fm]
[app.main.ui.dashboard.subscription :refer [get-subscription-type]]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@ -52,42 +54,45 @@
: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])])
(def ^:private schema:seats-form
[:map {:title "SeatsForm"}
[:min-members [::sm/number {:min 1 :max 9999}]]])
(mf/defc subscribe-management-dialog
{::mf/register modal/components
::mf/register-as :management-dialog}
[{:keys [subscription-name teams subscribe-to-trial]}]
[{:keys [subscription-type teams subscribe-to-trial]}]
(let [min-members* (mf/use-state (or (some->> teams (map :total-editors) (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 (= subscription-name "unlimited")
(mf/use-fn
(mf/deps min-members)
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
:type "unlimited"
:quantity min-members}))
(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)))))
(let [subscription-name (if subscribe-to-trial
(if (= subscription-type "unlimited")
(tr "subscription.settings.unlimited-trial")
(tr "subscription.settings.enterprise-trial"))
(case subscription-type
"professional" (tr "subscription.settings.professional")
"unlimited" (tr "subscription.settings.unlimited")
"enterprise" (tr "subscription.settings.enterprise")))
initial (mf/with-memo []
{:min-members (or (some->> teams (map :total-editors) (apply max)) 1)})
form (fm/use-form :schema schema:seats-form
:initial initial)
subscribe-to-unlimited (mf/use-fn
(fn [form]
(let [data (:clean-data @form)
return-url (-> (rt/get-current-href) (rt/encode-url))
href (dm/str "payments/subscriptions/create?type=unlimited&quantity=" (:min-members data) "&returnUrl=" return-url)]
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
:type "unlimited"
:quantity (:min-members data)})
(rt/nav-raw :href href)))))
subscribe-to-enterprise (mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
:type "enterprise"}))
(let [return-url (-> (rt/get-current-href) (rt/encode-url))
href (dm/str "payments/subscriptions/create?type=enterprise&returnUrl=" return-url)]
(st/emit! (rt/nav-raw :href href)))))
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
:type "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-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-management"
@ -107,62 +112,71 @@
[: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)]
(tr "subscription.settings.management.dialog.title" 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")]
[:* [: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)]
(for [team teams]
[:li {:key (dm/str (:id team)) :class (stl/css :team-name)}
(:name team) (tr "subscription.settings.management.dialog.members" (:total-editors 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
:max 1000
:on-change #(let [new-value (js/parseInt (.. % -target -value))]
(reset! min-members*
(let [v (cond
(or (mth/nan? new-value) (zero? new-value)) 1
(> new-value 1000) 1000
:else (max 1 new-value))]
v)))}]]
[: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"))
(or (= subscription-type "professional") (= subscription-type "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}]
(if (and (= subscription-type "unlimited") subscribe-to-trial)
[:& fm/form {:on-submit subscribe-to-unlimited
:class (stl/css :seats-form)
:form form}
[:label {:for "editors-subscription" :class (stl/css :modal-text :editors-label)}
(tr "subscription.settings.management.dialog.select-editors")]
[: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)}]]]]]]))
[:div {:class (stl/css :editors-wrapper)}
[:div {:class (stl/css :fields-row)}
[:& fm/input {:type "number"
:name :min-members
:show-error false
:label ""
:class (stl/css :input-field)}]]
[:div {:class (stl/css :editors-cost)}
[:span {:class (stl/css :modal-text-small)}
(tr "subscription.settings.management.dialog.price-month" (or (get-in @form [:clean-data :min-members]) 0))]
[:span {:class (stl/css :modal-text-small)}
(tr "subscription.settings.management.dialog.payment-explanation")]]]
[: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}]
[:> fm/submit-button*
{:label (tr "subscription.settings.start-trial")
:class (stl/css :primary-button)}]]]]
[: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 subscribe-to-enterprise handle-accept-dialog)}]]])]]]))
(mf/defc subscription-success-dialog
{::mf/register modal/components
@ -201,13 +215,12 @@
(let [route (mf/deref refs/route)
params (:params route)
params-subscription (:subscription (:query params))
show-subscription-success-modal (and (:query params)
(or (= (:subscription (:query params)) "subscribed-to-penpot-unlimited")
(= (:subscription (:query params)) "subscribed-to-penpot-enterprise")))
show-trial-subscription-modal (or (= params-subscription "subscription-to-penpot-unlimited")
(= params-subscription "subscription-to-penpot-enterprise"))
show-subscription-success-modal (or (= params-subscription "subscribed-to-penpot-unlimited")
(= params-subscription "subscribed-to-penpot-enterprise"))
subscription (:subscription (:props profile))
subscription-name (if subscription
(:type subscription)
"professional")
subscription-type (get-subscription-type subscription)
subscription-is-trial (= (:status subscription) "trialing")
teams* (mf/use-state nil)
teams (deref teams*)
@ -229,11 +242,12 @@
(st/emit! (rt/nav-raw :href href)))))
open-subscription-modal (mf/use-fn
(mf/deps teams)
(fn [subscription-name]
(st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-modal"}))
(fn [subscription-type]
(st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-modal"
::ev/origin "settings:in-app"}))
(st/emit!
(modal/show :management-dialog
{:subscription-name subscription-name
{:subscription-type subscription-type
:teams teams :subscribe-to-trial (not subscription)}))))]
(mf/with-effect []
@ -244,6 +258,19 @@
(mf/with-effect []
(dom/set-html-title (tr "subscription.labels")))
(mf/with-effect [show-trial-subscription-modal subscription]
(when show-trial-subscription-modal
(st/emit!
(ptk/event ::ev/event {::ev/name "open-subscription-modal"
::ev/origin "settings:from-pricing-page"})
(modal/show :management-dialog
{:subscription-type (if (= params-subscription "subscription-to-penpot-unlimited")
"unlimited"
"enterprise")
:teams teams
:subscribe-to-trial (not subscription)})
(rt/nav :settings-subscription {} {::rt/replace true}))))
(mf/with-effect [show-subscription-success-modal subscription]
(when show-subscription-success-modal
(st/emit!
@ -266,7 +293,7 @@
[:div {:class (stl/css :your-subscription)}
[:h3 {:class (stl/css :plan-section-title)} (tr "subscription.settings.section-plan")]
(case subscription-name
(case subscription-type
"professional"
[:> plan-card* {:card-title (tr "subscription.settings.professional")
:benefits [(tr "subscription.settings.professional.projects-files"),
@ -327,7 +354,7 @@
[: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-type "professional")
[:> plan-card* {:card-title (tr "subscription.settings.professional")
:price-value "$0"
:price-period (tr "subscription.settings.price-editor-month")
@ -339,7 +366,7 @@
: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-type "unlimited")
[:> plan-card* {:card-title (tr "subscription.settings.unlimited")
:card-title-icon i/character-u
:price-value "$7"
@ -353,7 +380,7 @@
: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-type "enterprise")
[:> plan-card* {:card-title (tr "subscription.settings.enterprise")
:card-title-icon i/character-e
:price-value "$950"

View file

@ -253,8 +253,8 @@
margin-block: var(--sp-xxl);
}
.input-wrapper {
@extend .input-element;
.input-field {
--input-icon-padding: var(--sp-s);
width: $s-80;
}

View file

@ -30,7 +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.dashboard.subscription :refer [main-menu-power-up* get-subscription-type]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.hooks.resize :as r]
[app.main.ui.icons :as i]
@ -825,9 +825,7 @@
(modal/show :plugin-management {}))))
subscription (:subscription (:props profile))
subscription-name (if subscription
(:type subscription)
"professional")]
subscription-type (get-subscription-type subscription)]
(mf/with-effect []
(let [disposable (->> st/stream
@ -913,7 +911,7 @@
[: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))
(when (and (contains? cf/flags :subscriptions) (not= "enterprise" subscription-type))
[:> main-menu-power-up* {:close-sub-menu close-sub-menu}])
;; TODO remove this block when subscriptions is full implemented

View file

@ -17,6 +17,7 @@
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.dashboard.subscription :refer [get-subscription-type]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.product.autosaved-milestone :refer [autosaved-milestone*]]
@ -36,26 +37,27 @@
(defn get-versions-stored-days
[team]
(let [subscription-name (-> team :subscription :type)]
(let [subscription-type (get-subscription-type (:subscription team))]
(cond
(= subscription-name "unlimited") 30
(= subscription-name "enterprise") 90
(= subscription-type "unlimited") 30
(= subscription-type "enterprise") 90
:else 7)))
(defn get-versions-warning-subtext
[team]
(let [subscription-name (-> team :subscription :type)
(let [subscription-type (get-subscription-type (:subscription team))
is-owner? (-> team :permissions :is-owner)
email-owner (:email (some #(when (:is-owner %) %) (:members team)))
support-email "support@penpot.app"
go-to-subscription (dm/str (u/join cfg/public-uri "#/settings/subscriptions"))]
(if (contains? cfg/flags :subscriptions)
(if is-owner?
(if (= "enterprise" subscription-name)
(tr "subscription.workspace.versions.warning.enterprise.subtext-owner" "support@penpot.app")
(if (= "enterprise" subscription-type)
(tr "subscription.workspace.versions.warning.enterprise.subtext-owner" support-email support-email)
(tr "subscription.workspace.versions.warning.subtext-owner" go-to-subscription))
(tr "subscription.workspace.versions.warning.subtext-member" email-owner email-owner))
(tr "workspace.versions.warning.subtext" "support@penpot.app"))))
(tr "workspace.versions.warning.subtext" support-email))))
(defn group-snapshots
[data]

View file

@ -7883,6 +7883,7 @@ msgstr ""
"If you'd like to increase this limit, "
"[upgrade your plan|target:self](%s)"
#, markdown
msgid "subscription.workspace.versions.warning.enterprise.subtext-owner"
msgstr ""
"If you'd like to increase this limit, write to us at "

View file

@ -7816,6 +7816,7 @@ msgstr ""
"Si quieres aumentar este límite, "
"[mejora tu plan|target:self](%s)"
#, markdown
msgid "subscription.workspace.versions.warning.enterprise.subtext-owner"
msgstr ""
"Si quieres aumentar este límite, escríbenos a "