mirror of
https://github.com/penpot/penpot.git
synced 2025-07-16 17:15:17 +02:00
🐛 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:
parent
66c5841d48
commit
ba6a02d1d9
20 changed files with 312 additions and 154 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -17,7 +17,8 @@
|
|||
},
|
||||
"~:subscription": {
|
||||
"~:type": "enterprise",
|
||||
"~:status": "trialing"
|
||||
"~:status": "trialing",
|
||||
"~:seats": null
|
||||
},
|
||||
"~:name": "Default",
|
||||
"~:modified-at": "~m1713533116375",
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
},
|
||||
"~:subscription": {
|
||||
"~:type": "professional",
|
||||
"~:status": "active"
|
||||
"~:status": "active",
|
||||
"~:seats": null
|
||||
},
|
||||
"~:name": "Second team",
|
||||
"~:modified-at": "~m1701164272671",
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
},
|
||||
"~:subscription": {
|
||||
"~:type": "unlimited",
|
||||
"~:status": "trialing"
|
||||
"~:status": "trialing",
|
||||
"~:seats": 2
|
||||
},
|
||||
"~:name": "Default",
|
||||
"~:modified-at": "~m1713533116375",
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
},
|
||||
"~:subscription": {
|
||||
"~:type": "unlimited",
|
||||
"~:status": "trialing"
|
||||
"~:status": "trialing",
|
||||
"~:seats": 2
|
||||
},
|
||||
"~:name": "Second team",
|
||||
"~:modified-at": "~m1701164272671",
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
},
|
||||
"~:subscription": {
|
||||
"~:type": "unlimited",
|
||||
"~:status": "trialing"
|
||||
"~:status": "trialing",
|
||||
"~:seats": 2
|
||||
},
|
||||
"~:name": "Second team",
|
||||
"~:modified-at": "~m1701164272671",
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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 "
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue