From bc13dfcf9e1d22d97cc30f963ac665219fa3ec53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20L=C3=B3pez?= Date: Wed, 6 May 2026 15:54:06 +0200 Subject: [PATCH] :sparkles: Refactor subscriptions page --- .../app/main/ui/settings/subscription.cljs | 220 +++++++++--------- .../app/main/ui/settings/subscription.scss | 42 ++-- 2 files changed, 134 insertions(+), 128 deletions(-) diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs index 18aaca3e5c..51d5df31ba 100644 --- a/frontend/src/app/main/ui/settings/subscription.cljs +++ b/frontend/src/app/main/ui/settings/subscription.cljs @@ -27,75 +27,114 @@ [rumext.v2 :as mf])) (mf/defc plan-card* + {::mf/wrap [mf/memo]} [{:keys [card-title card-title-icon price-value price-period cancel-at benefits-title benefits - cta-text - cta-link - cta-text-trial - cta-link-trial - cta-text-with-icon - cta-link-with-icon + cta-text cta-link + cta-text-trial cta-link-trial + cta-text-with-icon cta-link-with-icon code-action editors recommended show-button-cta]}] - [:div {:class (stl/css-case :plan-card true - :plan-card-highlight recommended)} - [:div {:class (stl/css :plan-card-header)} - [:div {:class (stl/css :plan-card-title-container)} - (when card-title-icon - [:> icon* {:icon-id card-title-icon - :class (stl/css :plan-title-icon) - :size "s"}]) - [:h4 {:class (stl/css :plan-card-title)} card-title] - (when recommended - [:& badge-notification {:content (tr "subscription.settings.recommended") - :size :small - :is-focus true}]) - (when editors [:span {:class (stl/css :plan-editors)} (tr "subscription.settings.editors" editors)])] - (when (and price-value price-period) - [:div {:class (stl/css :plan-price)} - [:span {:class (stl/css :plan-price-value)} price-value] - [:span {:class (stl/css :plan-price-period)} " / " price-period]]) - (when cancel-at - [:div {:class (stl/css :plan-cancel)} - [:span {:class (stl/css :plan-cancel-date)} cancel-at]])] - (when benefits-title [:h5 {:class (stl/css :benefits-title)} benefits-title]) - [:ul {:class (stl/css :benefits-list)} - (for [benefit benefits] - [:li {:key (dm/str benefit) :class (stl/css :benefit)} "- " benefit])] - (when (and cta-link cta-text show-button-cta) - [:> button* {:variant "primary" - :type "button" - :class (stl/css-case :bottom-button (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]) - (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 - [:> icon* {:icon-id "open-link" - :size "s"}]]) - (when (and cta-link cta-text (not show-button-cta)) - [:button {:class (stl/css-case :cta-button true - :bottom-link (not (or (and cta-link-trial cta-text-trial) code-action))) - :on-click cta-link} cta-text]) - (when code-action - [:button {:class (stl/css-case :cta-button true - :activate-by-code (= code-action :activate) - :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"))])]) + (let [has-trial? (and cta-link-trial cta-text-trial) + has-cta-with-icon? (and cta-link-with-icon cta-text-with-icon) + has-cta-button (and cta-link cta-text show-button-cta) + has-cta-link (and cta-link cta-text (not show-button-cta))] + [:div {:class (stl/css-case :plan-card true + :plan-card-highlight recommended)} + [:div {:class (stl/css :plan-card-header)} + [:div {:class (stl/css :plan-card-title-container)} + (when card-title-icon + [:> icon* {:icon-id card-title-icon + :class (stl/css :plan-title-icon) + :size "s"}]) + [:h4 {:class (stl/css :plan-card-title)} card-title] + (when recommended + [:& badge-notification {:content (tr "subscription.settings.recommended") + :size :small + :is-focus true}]) + (when editors + [:span {:class (stl/css :plan-editors)} (tr "subscription.settings.editors" editors)])] + (when (and price-value price-period) + [:div {:class (stl/css :plan-price)} + [:span {:class (stl/css :plan-price-value)} price-value] + [:span {:class (stl/css :plan-price-period)} " / " price-period]]) + (when cancel-at + [:div {:class (stl/css :plan-cancel)} + [:span {:class (stl/css :plan-cancel-date)} cancel-at]])] + (when benefits-title + [:h5 {:class (stl/css :benefits-title)} benefits-title]) + [:ul {:class (stl/css :benefits-list)} + (for [benefit benefits] + [:li {:key (dm/str benefit) :class (stl/css :benefit)} "- " benefit])] + + (when has-cta-button + [:> button* {:variant "primary" + :type "button" + :class (stl/css-case :bottom-button (not has-trial?)) + :on-click cta-link} cta-text]) + (when has-trial? + [:button {:class (stl/css :cta-button :bottom-link) + :on-click cta-link-trial} cta-text-trial]) + (when has-cta-with-icon? + [:button {:class (stl/css :cta-button :more-info) + :on-click cta-link-with-icon} cta-text-with-icon + [:> icon* {:icon-id "open-link" + :size "s"}]]) + (when has-cta-link + [:button {:class (stl/css-case :cta-button true + :bottom-link (not (or has-trial? code-action))) + :on-click cta-link} cta-text]) + (when code-action + [:button {:class (stl/css-case :cta-button true + :activate-by-code (= code-action :activate) + :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] [:map {:title "SeatsForm"} @@ -114,14 +153,7 @@ (deref unlimited-modal-step*) 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"))) + (get-subscription-name subscription-type subscribe-to-trial) min-editors (if (seq editors) (count editors) 1) @@ -184,18 +216,6 @@ (st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"})) (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 (mf/use-fn (mf/deps current-subscription unlimited-modal-step*) @@ -225,20 +245,7 @@ [:div {:class (stl/css :modal-content)} (when (and (seq editors) (not= unlimited-modal-step 2)) - [:* [: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)])]])]) + [:> editors-section* {:editors editors}]) (when (and (or (and (= subscription-type "professional") @@ -265,20 +272,20 @@ :class (stl/css :input-field)}]] [:div {:class (stl/css :editors-cost)} [: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* {:class (stl/css :modal-text-cap) :tag-name "span" :content (tr "subscription.settings.management.dialog.price-month" "175")}]) [:> 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" :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)} (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)} (tr "subscription.settings.management.dialog.input-error")]) @@ -527,15 +534,15 @@ :benefits ["Loren ipsum", "Loren ipsum", "Loren ipsum"] - :cta-text-with-icon (when (:licenses connectivity) "Control Center") - :cta-link-with-icon (when (:licenses connectivity) dnt/go-to-nitrate-cc) - :cta-text (if (:licenses connectivity) + :cta-text-with-icon (when (not (:manual nitrate-license)) "Control Center") + :cta-link-with-icon (when (not (:manual nitrate-license)) dnt/go-to-nitrate-cc) + :cta-text (if (and (:licenses connectivity) (not (:manual nitrate-license))) (tr "subscription.settings.manage-your-subscription") (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 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 "professional" [:> plan-card* {:card-title (tr "subscription.settings.professional") @@ -615,7 +622,11 @@ (tr "subscription.settings.professional.autosave-benefit"), (tr "subscription.settings.professional.teams-editors-benefit")] :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-link-with-icon go-to-pricing-page}]) @@ -769,8 +780,7 @@ [:> icon* {:icon-id "close" :size "m"}]] [: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-text-medium)} "When you downgrade:"] diff --git a/frontend/src/app/main/ui/settings/subscription.scss b/frontend/src/app/main/ui/settings/subscription.scss index 18ca4400f2..09f1f8e370 100644 --- a/frontend/src/app/main/ui/settings/subscription.scss +++ b/frontend/src/app/main/ui/settings/subscription.scss @@ -38,10 +38,6 @@ margin-block-start: var(--sp-s); } -.membership.first { - margin-block-start: var(--sp-l); -} - .membership-date { @include t.use-typography("body-small"); @@ -109,11 +105,11 @@ border: 1.75px solid var(--color-foreground-primary); stroke-width: 2.25px; padding: px2rem(3); +} - svg { - block-size: var(--sp-m); - inline-size: var(--sp-m); - } +.plan-title-icon svg { + block-size: var(--sp-m); + inline-size: var(--sp-m); } .plan-card-title, @@ -292,16 +288,16 @@ justify-content: center; max-inline-size: $sz-224; - svg { - inline-size: 100%; - block-size: auto; - } - @media (width <= 992px) { display: none; } } +.modal-start svg { + inline-size: 100%; + block-size: auto; +} + .editors-text { @include t.use-typography("body-medium"); @@ -363,21 +359,21 @@ } .radio-btns { - label { - @include t.use-typography("body-large"); - - padding: 0; - display: flex; - align-items: center; - color: var(--color-foreground-secondary); - } - display: flex; flex-direction: column; - padding: 0 0 var(--sp-xl) 0; + padding-block-end: var(--sp-xl); 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 { gap: var(--sp-xl); }