Subscribe with an activation code WIP

This commit is contained in:
María Valderrama 2026-04-23 15:15:40 +02:00
parent 6c4ab8940d
commit 75e5c3dca1
13 changed files with 347 additions and 41 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -369,6 +369,9 @@ async function generateSvgSprite(files, prefix) {
mode: { mode: {
symbol: { inline: true }, symbol: { inline: true },
}, },
shape: {
transform: [],
},
}); });
for (let path of files) { for (let path of files) {

View File

@ -40,7 +40,9 @@
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]] [app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.main.ui.nitrate.nitrate-code-activation-modal]
[app.main.ui.nitrate.nitrate-form] [app.main.ui.nitrate.nitrate-form]
[app.main.ui.nitrate.nitrate-activation-success-modal]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.dom.dnd :as dnd] [app.util.dom.dnd :as dnd]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]

View File

@ -21,6 +21,8 @@
(def ^:svg-id logo-subscription "logo-subscription") (def ^:svg-id logo-subscription "logo-subscription")
(def ^:svg-id logo-subscription-light "logo-subscription-light") (def ^:svg-id logo-subscription-light "logo-subscription-light")
(def ^:svg-id nitrate-welcome "nitrate-welcome") (def ^:svg-id nitrate-welcome "nitrate-welcome")
(def ^:svg-id nitrate-success-dark "nitrate-success-dark")
(def ^:svg-id nitrate-success-light "nitrate-success-light")
(def ^:svg-id marketing-arrows "marketing-arrows") (def ^:svg-id marketing-arrows "marketing-arrows")
(def ^:svg-id marketing-exchange "marketing-exchange") (def ^:svg-id marketing-exchange "marketing-exchange")
(def ^:svg-id marketing-file "marketing-file") (def ^:svg-id marketing-file "marketing-file")
@ -39,3 +41,4 @@
(assert (contains? raw-svg-list id) "invalid raw svg id") (assert (contains? raw-svg-list id) "invalid raw svg id")
[:> "svg" props [:> "svg" props
[:use {:href (dm/str "#asset-" id)}]]) [:use {:href (dm/str "#asset-" id)}]])

View File

@ -0,0 +1,85 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.nitrate.nitrate-activation-success-modal
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.time :as ct]
[app.main.data.modal :as modal]
[app.main.data.nitrate :as dnt]
[app.main.refs :as refs]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
[app.util.i18n :refer [tr]]
[app.util.theme :as theme]
[beicon.v2.core :as rx]
[rumext.v2 :as mf]))
(mf/defc nitrate-activation-success-modal*
{::mf/register modal/components
::mf/register-as :nitrate-activation-success
::mf/wrap-props true}
[connectivity]
(let [profile (mf/deref refs/profile)
profile-theme (get profile :theme theme/default)
system-theme* (mf/use-state theme/get-system-theme)
light?
(mf/with-memo [profile-theme (deref system-theme*)]
(let [resolved (cond
(= profile-theme "light") "light"
(= profile-theme "system") (deref system-theme*)
:else "dark")]
(= resolved "light")))
svg-id (if light? "nitrate-success-light" "nitrate-success-dark")
cancel-at (dm/get-in connectivity [:subscription :cancel-at])
date-str (when cancel-at
(ct/format-inst cancel-at "d MMMM, yyyy"))
on-create-org
(mf/use-fn
(fn []
(modal/hide!)
(dnt/go-to-nitrate-cc-create-org)))]
(mf/with-effect []
(let [s (->> (rx/from-event (.. js/window (matchMedia "(prefers-color-scheme: dark)")) "change")
(rx/map #(if (.-matches %) "dark" "light"))
(rx/subs! #(reset! system-theme* %)))]
(fn [] (rx/dispose! s))))
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)}
[:button {:class (stl/css :close-btn) :on-click modal/hide!}
[:> icon* {:icon-id "close"
:size "m"}]]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-start)}
[:> raw-svg* {:id svg-id}]]
[:div {:class (stl/css :modal-end)}
[:div {:class (stl/css :modal-title)}
"You are Business Nitrate!"]
[:p {:class (stl/css :modal-text-primary)}
(dm/str "Your plan is active until " (or date-str "—") ".")]
[:p {:class (stl/css :modal-text)}
"You can manage your subscription anytime from the Subscription page in your account settings."]
[:p {:class (stl/css :modal-text)}
"Enjoy your plan!"]
[:> button* {:variant "primary"
:on-click on-create-org
:class (stl/css :modal-button)}
"Create organization"]]]]]))

View File

@ -0,0 +1,79 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/typography.scss" as t;
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
.modal-overlay {
@extend %modal-overlay-base;
z-index: var(--z-index-notifications);
}
.modal-dialog {
@extend %modal-container-base;
max-block-size: initial;
min-inline-size: px2rem(608);
max-inline-size: px2rem(608);
padding: var(--sp-xxxl);
}
.close-btn {
@extend %modal-close-btn-base;
}
.modal-content {
display: flex;
gap: $sz-40;
}
.modal-start {
display: flex;
justify-content: center;
min-inline-size: $sz-224;
svg {
inline-size: 100%;
block-size: auto;
}
@media (width <= 640px) {
display: none;
}
}
.modal-end {
color: var(--color-foreground-secondary);
display: flex;
flex-direction: column;
gap: var(--sp-m);
}
.modal-title {
@include t.use-typography("title-large");
color: var(--modal-title-foreground-color);
}
.modal-text-primary {
@include t.use-typography("body-large");
color: var(--color-foreground-primary);
}
.modal-text {
@include t.use-typography("body-large");
}
.modal-button {
margin-block-start: var(--sp-s);
align-self: flex-start;
}

View File

@ -0,0 +1,69 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.nitrate.nitrate-code-activation-modal
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.modal :as modal]
[app.main.store :as st]
[app.main.ui.components.forms :as fm]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(def ^:private schema:activate-form
[:map {:title "ActivateForm"}
[:activation-code [:string {:min 1}]]])
(mf/defc nitrate-code-activation-modal*
{::mf/register modal/components
::mf/register-as :nitrate-code-activation}
[_props]
(let [initial (mf/with-memo []
{:activation-code ""})
form (fm/use-form :schema schema:activate-form
:initial initial)
on-accept
(mf/use-fn
(mf/deps form)
(fn [_]
;; TODO: dispatch activation action with (-> @form :clean-data :activation-code)
(modal/hide!)
;; TODO: remove, only for flow testing
(st/emit! (modal/show {:type :nitrate-activation-success}))))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)}
[:> icon-button* {:variant "ghost"
:class (stl/css :close-btn)
:aria-label (tr "labels.close")
:on-click modal/hide!
:icon i/close}]
[:div {:class (stl/css :modal-header)}
[:h2 {:class (stl/css :modal-title)} "Activate Nitrate"]]
[:div {:class (stl/css :modal-content)}
[:& fm/form {:form form :on-submit on-accept}
[:& fm/input {:name :activation-code
:auto-focus? true
:label "Enter your activation code"
:type "text"
:placeholder "XXXX-XXXXX-XXXXX-XXXXX"}]]
[:input
{:type "button"
:class (stl/css-case :accept-btn true
:global/disabled (not (:valid @form)))
:disabled (not (:valid @form))
:value "Activate"
:on-click on-accept}]
[:div {:class (stl/css :footer-text)}
"Need a code? Contact us: "
[:a {:class (stl/css :link)
:href "mailto:sales@nitrate.com"}
"sales@nitrate.com"]]]]]))

View File

@ -0,0 +1,65 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/typography.scss" as t;
@use "ds/spacing.scss" as *;
@use "ds/_sizes.scss" as *;
.close-btn {
@extend %modal-close-btn-base;
}
.modal-overlay {
@extend %modal-overlay-base;
z-index: var(--z-index-notifications);
}
.modal-dialog {
@extend %modal-container-base;
min-inline-size: px2rem(480);
padding: var(--sp-xxxl);
}
.modal-title {
@include t.use-typography("title-large");
color: var(--modal-title-foreground-color);
margin-block-end: var(--sp-xxxl);
}
.modal-content {
display: flex;
flex-direction: column;
gap: var(--sp-m);
color: var(--color-foreground-secondary);
}
.modal-description {
@include t.use-typography("body-large");
}
.action-buttons {
@extend %modal-action-btns;
}
.accept-btn {
@extend %modal-accept-btn;
width: 100%;
}
.footer-text {
@include t.use-typography("body-medium");
color: var(--color-foreground-secondary);
margin-block-start: var(--sp-xxxl);
}
.link {
color: var(--color-accent-primary);
}

View File

@ -11,6 +11,7 @@
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.nitrate :as dnt] [app.main.data.nitrate :as dnt]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
@ -37,7 +38,12 @@
(mf/use-fn (mf/use-fn
(mf/deps form) (mf/deps form)
(fn [] (fn []
(dnt/go-to-buy-nitrate-license (-> @form :clean-data :subscription name))))] (dnt/go-to-buy-nitrate-license (-> @form :clean-data :subscription name))))
on-activate-click
(mf/use-fn
(fn []
(st/emit! (modal/show {:type :nitrate-code-activation}))))]
[:div {:class (stl/css :modal-overlay)} [:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog :subscription-success)} [:div {:class (stl/css :modal-dialog :subscription-success)}
@ -80,6 +86,10 @@
[:div {:class (stl/css :modal-text-small :modal-info)} [:div {:class (stl/css :modal-text-small :modal-info)}
"Cancel anytime before your next billing cycle."]]] "Cancel anytime before your next billing cycle."]]]
[:p {:class (stl/css :modal-text-medium)}
"Have an activation code? " [:a {:class (stl/css :link)
:on-click on-activate-click}
"Enter activation code"]]
[:p {:class (stl/css :modal-text-medium)} [:p {:class (stl/css :modal-text-medium)}
[:a {:class (stl/css :link) :href dnt/go-to-subscription-url} [:a {:class (stl/css :link) :href dnt/go-to-subscription-url}
@ -92,6 +102,13 @@
"Contact us to try Nitrate for 14 days:")] "Contact us to try Nitrate for 14 days:")]
[:p {:class (stl/css :modal-text-large)} [:p {:class (stl/css :modal-text-large)}
[:a {:class (stl/css :link) :href "mailto:sales@penpot.app"} [:a {:class (stl/css :link) :href "mailto:sales@penpot.app"}
"sales@penpot.app"]]])]]]])) "sales@penpot.app"]]
[:div {:class (stl/css :activation-code)}
[:p {:class (stl/css :modal-text-large)}
"Have an activation code?"]
[:p {:class (stl/css :modal-text-large)}
[:a {:class (stl/css :link)
:on-click on-activate-click}
"Enter activation code"]]]])]]]]))

View File

@ -107,6 +107,10 @@
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
} }
.activation-code {
margin-block-start: var(--sp-xxxl);
}
.link { .link {
color: var(--color-accent-primary); color: var(--color-accent-primary);
} }

View File

@ -18,6 +18,7 @@
[app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]] [app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
[app.main.ui.nitrate.nitrate-activation-success-modal]
[app.main.ui.notifications.badge :refer [badge-notification]] [app.main.ui.notifications.badge :refer [badge-notification]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr c]] [app.util.i18n :as i18n :refer [tr c]]
@ -37,6 +38,7 @@
cta-link-trial cta-link-trial
cta-text-with-icon cta-text-with-icon
cta-link-with-icon cta-link-with-icon
show-activation-by-code
editors editors
recommended recommended
show-button-cta]}] show-button-cta]}]
@ -66,6 +68,14 @@
[:ul {:class (stl/css :benefits-list)} [:ul {:class (stl/css :benefits-list)}
(for [benefit benefits] (for [benefit benefits]
[:li {:key (dm/str benefit) :class (stl/css :benefit)} "- " benefit])] [: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) (when (and cta-link-with-icon cta-text-with-icon)
[:button {:class (stl/css :cta-button :more-info) [:button {:class (stl/css :cta-button :more-info)
:on-click cta-link-with-icon} cta-text-with-icon :on-click cta-link-with-icon} cta-text-with-icon
@ -75,14 +85,10 @@
[:button {:class (stl/css-case :cta-button true [:button {:class (stl/css-case :cta-button true
:bottom-link (not (and cta-link-trial cta-text-trial))) :bottom-link (not (and cta-link-trial cta-text-trial)))
:on-click cta-link} cta-text]) :on-click cta-link} cta-text])
(when (and cta-link cta-text show-button-cta) (when show-activation-by-code
[:> button* {:variant "primary" [:button {:class (stl/css :cta-button :activate-by-code)
:type "button" :on-click #(st/emit! (modal/show {:type :nitrate-code-activation}))}
:class (stl/css-case :bottom-button (not (and cta-link-trial cta-text-trial))) "Enter activation code"])])
: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])])
(defn- make-management-form-schema [min-editors] (defn- make-management-form-schema [min-editors]
[:map {:title "SeatsForm"} [:map {:title "SeatsForm"}
@ -360,36 +366,6 @@
:value (tr "labels.close") :value (tr "labels.close")
:on-click handle-close-dialog}]]]]]])) :on-click handle-close-dialog}]]]]]]))
(mf/defc nitrate-success-dialog
{::mf/register modal/components
::mf/register-as :nitrate-success}
[]
;; TODO add translations for this texts when we have the definitive ones
(let [profile (mf/deref refs/profile)]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog :subscription-success)}
[:button {:class (stl/css :close-btn) :on-click modal/hide!}
[:> icon* {:icon-id "close"
:size "m"}]]
[:div {:class (stl/css :modal-success-content)}
[:div {:class (stl/css :modal-start)}
[:> raw-svg* {:id (if (= "light" (:theme profile)) "logo-subscription-light" "logo-subscription")}]]
[:div {:class (stl/css :modal-end)}
[:div {:class (stl/css :modal-title)}
"You are Business Nitrate!"]
[:p {:class (stl/css :modal-text-large)}
(tr "subscription.settings.success.dialog.description")]
[:p {:class (stl/css :modal-text-large)}
(tr "subscription.settings.sucess.dialog.footer")]
[:div {:class (stl/css :success-action-buttons)}
[:input
{:class (stl/css :primary-button)
:type "button"
:value "CREATE ORGANIZATION"
:on-click dnt/go-to-nitrate-cc-create-org}]]]]]]))
(mf/defc subscription-page* (mf/defc subscription-page*
[{:keys [profile]}] [{:keys [profile]}]
@ -498,7 +474,7 @@
^boolean show-subscription-success-modal? ^boolean show-subscription-success-modal?
(st/emit! (st/emit!
(if (= params-subscription "subscribed-to-penpot-nitrate") (if (= params-subscription "subscribed-to-penpot-nitrate")
(modal/show :nitrate-success {}) (modal/show :nitrate-activation-success {})
(modal/show :subscription-success (modal/show :subscription-success
{:subscription-name (if (= params-subscription "subscribed-to-penpot-unlimited") {:subscription-name (if (= params-subscription "subscribed-to-penpot-unlimited")
(if (= success-modal-is-trial? "true") (if (= success-modal-is-trial? "true")
@ -658,6 +634,7 @@
:cta-link #(open-subscription-modal "nitrate" subscription) :cta-link #(open-subscription-modal "nitrate" subscription)
: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
:show-activation-by-code true
:show-button-cta (not nitrate-license)}])]]])) :show-button-cta (not nitrate-license)}])]]]))