mirror of
https://github.com/penpot/penpot.git
synced 2026-09-06 12:09:24 +00:00
✨ Refactor subscriptions page
This commit is contained in:
parent
6e186143d5
commit
bc13dfcf9e
@ -27,75 +27,114 @@
|
|||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc plan-card*
|
(mf/defc plan-card*
|
||||||
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [card-title
|
[{:keys [card-title
|
||||||
card-title-icon
|
card-title-icon
|
||||||
price-value price-period
|
price-value price-period
|
||||||
cancel-at
|
cancel-at
|
||||||
benefits-title benefits
|
benefits-title benefits
|
||||||
cta-text
|
cta-text cta-link
|
||||||
cta-link
|
cta-text-trial cta-link-trial
|
||||||
cta-text-trial
|
cta-text-with-icon cta-link-with-icon
|
||||||
cta-link-trial
|
|
||||||
cta-text-with-icon
|
|
||||||
cta-link-with-icon
|
|
||||||
code-action
|
code-action
|
||||||
editors
|
editors
|
||||||
recommended
|
recommended
|
||||||
show-button-cta]}]
|
show-button-cta]}]
|
||||||
|
|
||||||
[:div {:class (stl/css-case :plan-card true
|
(let [has-trial? (and cta-link-trial cta-text-trial)
|
||||||
:plan-card-highlight recommended)}
|
has-cta-with-icon? (and cta-link-with-icon cta-text-with-icon)
|
||||||
[:div {:class (stl/css :plan-card-header)}
|
has-cta-button (and cta-link cta-text show-button-cta)
|
||||||
[:div {:class (stl/css :plan-card-title-container)}
|
has-cta-link (and cta-link cta-text (not show-button-cta))]
|
||||||
(when card-title-icon
|
[:div {:class (stl/css-case :plan-card true
|
||||||
[:> icon* {:icon-id card-title-icon
|
:plan-card-highlight recommended)}
|
||||||
:class (stl/css :plan-title-icon)
|
[:div {:class (stl/css :plan-card-header)}
|
||||||
:size "s"}])
|
[:div {:class (stl/css :plan-card-title-container)}
|
||||||
[:h4 {:class (stl/css :plan-card-title)} card-title]
|
(when card-title-icon
|
||||||
(when recommended
|
[:> icon* {:icon-id card-title-icon
|
||||||
[:& badge-notification {:content (tr "subscription.settings.recommended")
|
:class (stl/css :plan-title-icon)
|
||||||
:size :small
|
:size "s"}])
|
||||||
:is-focus true}])
|
[:h4 {:class (stl/css :plan-card-title)} card-title]
|
||||||
(when editors [:span {:class (stl/css :plan-editors)} (tr "subscription.settings.editors" editors)])]
|
(when recommended
|
||||||
(when (and price-value price-period)
|
[:& badge-notification {:content (tr "subscription.settings.recommended")
|
||||||
[:div {:class (stl/css :plan-price)}
|
:size :small
|
||||||
[:span {:class (stl/css :plan-price-value)} price-value]
|
:is-focus true}])
|
||||||
[:span {:class (stl/css :plan-price-period)} " / " price-period]])
|
(when editors
|
||||||
(when cancel-at
|
[:span {:class (stl/css :plan-editors)} (tr "subscription.settings.editors" editors)])]
|
||||||
[:div {:class (stl/css :plan-cancel)}
|
(when (and price-value price-period)
|
||||||
[:span {:class (stl/css :plan-cancel-date)} cancel-at]])]
|
[:div {:class (stl/css :plan-price)}
|
||||||
(when benefits-title [:h5 {:class (stl/css :benefits-title)} benefits-title])
|
[:span {:class (stl/css :plan-price-value)} price-value]
|
||||||
[:ul {:class (stl/css :benefits-list)}
|
[:span {:class (stl/css :plan-price-period)} " / " price-period]])
|
||||||
(for [benefit benefits]
|
(when cancel-at
|
||||||
[:li {:key (dm/str benefit) :class (stl/css :benefit)} "- " benefit])]
|
[:div {:class (stl/css :plan-cancel)}
|
||||||
(when (and cta-link cta-text show-button-cta)
|
[:span {:class (stl/css :plan-cancel-date)} cancel-at]])]
|
||||||
[:> button* {:variant "primary"
|
(when benefits-title
|
||||||
:type "button"
|
[:h5 {:class (stl/css :benefits-title)} benefits-title])
|
||||||
:class (stl/css-case :bottom-button (not (and cta-link-trial cta-text-trial)))
|
[:ul {:class (stl/css :benefits-list)}
|
||||||
:on-click cta-link} cta-text])
|
(for [benefit benefits]
|
||||||
(when (and cta-link-trial cta-text-trial)
|
[:li {:key (dm/str benefit) :class (stl/css :benefit)} "- " benefit])]
|
||||||
[:button {:class (stl/css :cta-button :bottom-link)
|
|
||||||
:on-click cta-link-trial} cta-text-trial])
|
(when has-cta-button
|
||||||
(when (and cta-link-with-icon cta-text-with-icon)
|
[:> button* {:variant "primary"
|
||||||
[:button {:class (stl/css :cta-button :more-info)
|
:type "button"
|
||||||
:on-click cta-link-with-icon} cta-text-with-icon
|
:class (stl/css-case :bottom-button (not has-trial?))
|
||||||
[:> icon* {:icon-id "open-link"
|
:on-click cta-link} cta-text])
|
||||||
:size "s"}]])
|
(when has-trial?
|
||||||
(when (and cta-link cta-text (not show-button-cta))
|
[:button {:class (stl/css :cta-button :bottom-link)
|
||||||
[:button {:class (stl/css-case :cta-button true
|
:on-click cta-link-trial} cta-text-trial])
|
||||||
:bottom-link (not (or (and cta-link-trial cta-text-trial) code-action)))
|
(when has-cta-with-icon?
|
||||||
:on-click cta-link} cta-text])
|
[:button {:class (stl/css :cta-button :more-info)
|
||||||
(when code-action
|
:on-click cta-link-with-icon} cta-text-with-icon
|
||||||
[:button {:class (stl/css-case :cta-button true
|
[:> icon* {:icon-id "open-link"
|
||||||
:activate-by-code (= code-action :activate)
|
:size "s"}]])
|
||||||
:renew-by-code (= code-action :renovate)
|
(when has-cta-link
|
||||||
:bottom-link (= code-action :renovate))
|
[:button {:class (stl/css-case :cta-button true
|
||||||
;; TODO add renovation modal
|
:bottom-link (not (or has-trial? code-action)))
|
||||||
:on-click (when (= code-action :activate)
|
:on-click cta-link} cta-text])
|
||||||
#(st/emit! (modal/show {:type :nitrate-code-activation})))}
|
(when code-action
|
||||||
(if (= code-action :activate)
|
[:button {:class (stl/css-case :cta-button true
|
||||||
(tr "subscription.settings.activate-by-code")
|
:activate-by-code (= code-action :activate)
|
||||||
(tr "nitrate.subscription.settings.renew-with-code"))])])
|
:renew-by-code (= code-action :renovate)
|
||||||
|
:bottom-link (= code-action :renovate))
|
||||||
|
;; TODO add renovation modal
|
||||||
|
:on-click (when (= code-action :activate)
|
||||||
|
#(st/emit! (modal/show {:type :nitrate-code-activation})))}
|
||||||
|
(if (= code-action :activate)
|
||||||
|
(tr "subscription.settings.activate-by-code")
|
||||||
|
(tr "nitrate.subscription.settings.renew-with-code"))])]))
|
||||||
|
|
||||||
|
(defn- get-subscription-name [subscription-type subscribe-to-trial?]
|
||||||
|
(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"))))
|
||||||
|
|
||||||
|
(mf/defc ^:private editors-section*
|
||||||
|
[{:keys [editors]}]
|
||||||
|
(let [show-editors-list* (mf/use-state false)
|
||||||
|
show-editors-list (deref show-editors-list*)
|
||||||
|
handle-click (mf/use-fn
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(swap! show-editors-list* not)))]
|
||||||
|
[:*
|
||||||
|
[:p {:class (stl/css :editors-text)}
|
||||||
|
(tr "subscription.settings.management.dialog.currently-editors-title" (c (count editors)))]
|
||||||
|
[:button {:class (stl/css :cta-button :show-editors-button) :on-click handle-click}
|
||||||
|
(tr "subscription.settings.management.dialog.editors")
|
||||||
|
[:> icon* {:icon-id (if show-editors-list i/arrow-up i/arrow-down)
|
||||||
|
:class (stl/css :icon-dropdown)
|
||||||
|
:size "s"}]]
|
||||||
|
(when show-editors-list
|
||||||
|
[:*
|
||||||
|
[:p {:class (stl/css :editors-text :editors-list-warning)}
|
||||||
|
(tr "subscription.settings.management.dialog.editors-explanation")]
|
||||||
|
[:ul {:class (stl/css :editors-list)}
|
||||||
|
(for [editor editors]
|
||||||
|
[:li {:key (dm/str (:id editor)) :class (stl/css :team-name)} "- " (:name editor)])]])]))
|
||||||
|
|
||||||
(defn- make-management-form-schema [min-editors]
|
(defn- make-management-form-schema [min-editors]
|
||||||
[:map {:title "SeatsForm"}
|
[:map {:title "SeatsForm"}
|
||||||
@ -114,14 +153,7 @@
|
|||||||
(deref unlimited-modal-step*)
|
(deref unlimited-modal-step*)
|
||||||
|
|
||||||
subscription-name
|
subscription-name
|
||||||
(if subscribe-to-trial
|
(get-subscription-name subscription-type 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")))
|
|
||||||
|
|
||||||
min-editors
|
min-editors
|
||||||
(if (seq editors) (count editors) 1)
|
(if (seq editors) (count editors) 1)
|
||||||
@ -184,18 +216,6 @@
|
|||||||
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
|
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
|
||||||
(modal/hide!)))
|
(modal/hide!)))
|
||||||
|
|
||||||
show-editors-list*
|
|
||||||
(mf/use-state false)
|
|
||||||
|
|
||||||
show-editors-list
|
|
||||||
(deref show-editors-list*)
|
|
||||||
|
|
||||||
handle-click
|
|
||||||
(mf/use-fn
|
|
||||||
(fn [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(swap! show-editors-list* not)))
|
|
||||||
|
|
||||||
on-submit
|
on-submit
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps current-subscription unlimited-modal-step*)
|
(mf/deps current-subscription unlimited-modal-step*)
|
||||||
@ -225,20 +245,7 @@
|
|||||||
|
|
||||||
[:div {:class (stl/css :modal-content)}
|
[:div {:class (stl/css :modal-content)}
|
||||||
(when (and (seq editors) (not= unlimited-modal-step 2))
|
(when (and (seq editors) (not= unlimited-modal-step 2))
|
||||||
[:* [:p {:class (stl/css :editors-text)}
|
[:> editors-section* {:editors editors}])
|
||||||
(tr "subscription.settings.management.dialog.currently-editors-title" (c (count editors)))]
|
|
||||||
[:button {:class (stl/css :cta-button :show-editors-button) :on-click handle-click}
|
|
||||||
(tr "subscription.settings.management.dialog.editors")
|
|
||||||
[:> icon* {:icon-id (if show-editors-list i/arrow-up i/arrow-down)
|
|
||||||
:class (stl/css :icon-dropdown)
|
|
||||||
:size "s"}]]
|
|
||||||
(when show-editors-list
|
|
||||||
[:*
|
|
||||||
[:p {:class (stl/css :editors-text :editors-list-warning)}
|
|
||||||
(tr "subscription.settings.management.dialog.editors-explanation")]
|
|
||||||
[:ul {:class (stl/css :editors-list)}
|
|
||||||
(for [editor editors]
|
|
||||||
[:li {:key (dm/str (:id editor)) :class (stl/css :team-name)} "- " (:name editor)])]])])
|
|
||||||
|
|
||||||
(when (and
|
(when (and
|
||||||
(or (and (= subscription-type "professional")
|
(or (and (= subscription-type "professional")
|
||||||
@ -265,20 +272,20 @@
|
|||||||
:class (stl/css :input-field)}]]
|
:class (stl/css :input-field)}]]
|
||||||
[:div {:class (stl/css :editors-cost)}
|
[:div {:class (stl/css :editors-cost)}
|
||||||
[:span {:class (stl/css :modal-text-medium)}
|
[:span {:class (stl/css :modal-text-medium)}
|
||||||
(when (> (get-in @form [:clean-data :min-members]) 25)
|
(when (> (dm/get-in @form [:clean-data :min-members]) 25)
|
||||||
[:> i18n/tr-html*
|
[:> i18n/tr-html*
|
||||||
{:class (stl/css :modal-text-cap)
|
{:class (stl/css :modal-text-cap)
|
||||||
:tag-name "span"
|
:tag-name "span"
|
||||||
:content (tr "subscription.settings.management.dialog.price-month" "175")}])
|
:content (tr "subscription.settings.management.dialog.price-month" "175")}])
|
||||||
[:> i18n/tr-html*
|
[:> i18n/tr-html*
|
||||||
{:class (stl/css-case :text-strikethrough (> (get-in @form [:clean-data :min-members]) 25))
|
{:class (stl/css-case :text-strikethrough (> (dm/get-in @form [:clean-data :min-members]) 25))
|
||||||
:tag-name "span"
|
:tag-name "span"
|
||||||
:content (tr "subscription.settings.management.dialog.price-month"
|
:content (tr "subscription.settings.management.dialog.price-month"
|
||||||
(* 7 (or (get-in @form [:clean-data :min-members]) 0)))}]]
|
(* 7 (or (dm/get-in @form [:clean-data :min-members]) 0)))}]]
|
||||||
[:span {:class (stl/css :modal-text-medium)}
|
[:span {:class (stl/css :modal-text-medium)}
|
||||||
(tr "subscription.settings.management.dialog.payment-explanation")]]]
|
(tr "subscription.settings.management.dialog.payment-explanation")]]]
|
||||||
|
|
||||||
(when (get-in @form [:errors :min-members])
|
(when (dm/get-in @form [:errors :min-members])
|
||||||
[:div {:class (stl/css :error-message)}
|
[:div {:class (stl/css :error-message)}
|
||||||
(tr "subscription.settings.management.dialog.input-error")])
|
(tr "subscription.settings.management.dialog.input-error")])
|
||||||
|
|
||||||
@ -527,15 +534,15 @@
|
|||||||
:benefits ["Loren ipsum",
|
:benefits ["Loren ipsum",
|
||||||
"Loren ipsum",
|
"Loren ipsum",
|
||||||
"Loren ipsum"]
|
"Loren ipsum"]
|
||||||
:cta-text-with-icon (when (:licenses connectivity) "Control Center")
|
:cta-text-with-icon (when (not (:manual nitrate-license)) "Control Center")
|
||||||
:cta-link-with-icon (when (:licenses connectivity) dnt/go-to-nitrate-cc)
|
:cta-link-with-icon (when (not (:manual nitrate-license)) dnt/go-to-nitrate-cc)
|
||||||
:cta-text (if (:licenses connectivity)
|
:cta-text (if (and (:licenses connectivity) (not (:manual nitrate-license)))
|
||||||
(tr "subscription.settings.manage-your-subscription")
|
(tr "subscription.settings.manage-your-subscription")
|
||||||
(tr "nitrate.subscription.settings.manual-cancel"))
|
(tr "nitrate.subscription.settings.manual-cancel"))
|
||||||
:cta-link (if (:licenses connectivity)
|
:cta-link (if (and (:licenses connectivity) (not (:manual nitrate-license)))
|
||||||
dnt/go-to-nitrate-billing
|
dnt/go-to-nitrate-billing
|
||||||
open-cancel-contact-sales-modal)
|
open-cancel-contact-sales-modal)
|
||||||
:code-action (when (and (not (:licenses connectivity)) (:manual nitrate-license)) :renovate)}]
|
:code-action (when (:manual nitrate-license) :renovate)}]
|
||||||
(case subscription-type
|
(case subscription-type
|
||||||
"professional"
|
"professional"
|
||||||
[:> plan-card* {:card-title (tr "subscription.settings.professional")
|
[:> plan-card* {:card-title (tr "subscription.settings.professional")
|
||||||
@ -615,7 +622,11 @@
|
|||||||
(tr "subscription.settings.professional.autosave-benefit"),
|
(tr "subscription.settings.professional.autosave-benefit"),
|
||||||
(tr "subscription.settings.professional.teams-editors-benefit")]
|
(tr "subscription.settings.professional.teams-editors-benefit")]
|
||||||
:cta-text (tr "subscription.settings.subscribe")
|
:cta-text (tr "subscription.settings.subscribe")
|
||||||
:cta-link #(open-subscription-modal "professional")
|
:cta-link (if (and (contains? cf/flags :nitrate) nitrate? (= subscription-type "nitrate"))
|
||||||
|
(if (:licenses connectivity)
|
||||||
|
dnt/go-to-nitrate-billing
|
||||||
|
open-cancel-contact-sales-modal)
|
||||||
|
go-to-payments)
|
||||||
:cta-text-with-icon (tr "subscription.settings.more-information")
|
:cta-text-with-icon (tr "subscription.settings.more-information")
|
||||||
:cta-link-with-icon go-to-pricing-page}])
|
:cta-link-with-icon go-to-pricing-page}])
|
||||||
|
|
||||||
@ -769,8 +780,7 @@
|
|||||||
[:> icon* {:icon-id "close"
|
[:> icon* {:icon-id "close"
|
||||||
:size "m"}]]
|
:size "m"}]]
|
||||||
[:div {:class (stl/css :modal-title :subscription-title)}
|
[:div {:class (stl/css :modal-title :subscription-title)}
|
||||||
(str "Switch to " subscription-type " plan?")]
|
(dm/str "Switch to " subscription-type " plan?")]
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-content)}
|
[:div {:class (stl/css :modal-content)}
|
||||||
[:div {:class (stl/css :modal-text-medium)}
|
[:div {:class (stl/css :modal-text-medium)}
|
||||||
"When you downgrade:"]
|
"When you downgrade:"]
|
||||||
|
|||||||
@ -38,10 +38,6 @@
|
|||||||
margin-block-start: var(--sp-s);
|
margin-block-start: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.membership.first {
|
|
||||||
margin-block-start: var(--sp-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.membership-date {
|
.membership-date {
|
||||||
@include t.use-typography("body-small");
|
@include t.use-typography("body-small");
|
||||||
|
|
||||||
@ -109,11 +105,11 @@
|
|||||||
border: 1.75px solid var(--color-foreground-primary);
|
border: 1.75px solid var(--color-foreground-primary);
|
||||||
stroke-width: 2.25px;
|
stroke-width: 2.25px;
|
||||||
padding: px2rem(3);
|
padding: px2rem(3);
|
||||||
|
}
|
||||||
|
|
||||||
svg {
|
.plan-title-icon svg {
|
||||||
block-size: var(--sp-m);
|
block-size: var(--sp-m);
|
||||||
inline-size: var(--sp-m);
|
inline-size: var(--sp-m);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-card-title,
|
.plan-card-title,
|
||||||
@ -292,16 +288,16 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-inline-size: $sz-224;
|
max-inline-size: $sz-224;
|
||||||
|
|
||||||
svg {
|
|
||||||
inline-size: 100%;
|
|
||||||
block-size: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 992px) {
|
@media (width <= 992px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-start svg {
|
||||||
|
inline-size: 100%;
|
||||||
|
block-size: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.editors-text {
|
.editors-text {
|
||||||
@include t.use-typography("body-medium");
|
@include t.use-typography("body-medium");
|
||||||
|
|
||||||
@ -363,21 +359,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.radio-btns {
|
.radio-btns {
|
||||||
label {
|
|
||||||
@include t.use-typography("body-large");
|
|
||||||
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--color-foreground-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0 0 var(--sp-xl) 0;
|
padding-block-end: var(--sp-xl);
|
||||||
gap: var(--sp-s);
|
gap: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.radio-btns label {
|
||||||
|
@include t.use-typography("body-large");
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.modal-contact-content {
|
.modal-contact-content {
|
||||||
gap: var(--sp-xl);
|
gap: var(--sp-xl);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user