diff --git a/frontend/resources/images/assets/nitrate-success-dark.svg b/frontend/resources/images/assets/nitrate-success-dark.svg new file mode 100644 index 0000000000..239364da9b --- /dev/null +++ b/frontend/resources/images/assets/nitrate-success-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/resources/images/assets/nitrate-success-light.svg b/frontend/resources/images/assets/nitrate-success-light.svg new file mode 100644 index 0000000000..e1343cbc4c --- /dev/null +++ b/frontend/resources/images/assets/nitrate-success-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/resources/images/nitrate-success.png b/frontend/resources/images/nitrate-success.png new file mode 100644 index 0000000000..9d8e1b7853 Binary files /dev/null and b/frontend/resources/images/nitrate-success.png differ diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js index c6396f99bc..da6f277ea5 100644 --- a/frontend/scripts/_helpers.js +++ b/frontend/scripts/_helpers.js @@ -369,6 +369,9 @@ async function generateSvgSprite(files, prefix) { mode: { symbol: { inline: true }, }, + shape: { + transform: [], + }, }); for (let path of files) { diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 9663111d98..0e368ff25a 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -40,7 +40,9 @@ [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.icons :as deprecated-icon] + [app.main.ui.nitrate.nitrate-code-activation-modal] [app.main.ui.nitrate.nitrate-form] + [app.main.ui.nitrate.nitrate-activation-success-modal] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.i18n :as i18n :refer [tr]] diff --git a/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs b/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs index e744cfe292..ee74bf98b9 100644 --- a/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs +++ b/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs @@ -21,6 +21,8 @@ (def ^:svg-id logo-subscription "logo-subscription") (def ^:svg-id logo-subscription-light "logo-subscription-light") (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-exchange "marketing-exchange") (def ^:svg-id marketing-file "marketing-file") @@ -39,3 +41,4 @@ (assert (contains? raw-svg-list id) "invalid raw svg id") [:> "svg" props [:use {:href (dm/str "#asset-" id)}]]) + diff --git a/frontend/src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs b/frontend/src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs new file mode 100644 index 0000000000..da0a0746ee --- /dev/null +++ b/frontend/src/app/main/ui/nitrate/nitrate_activation_success_modal.cljs @@ -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"]]]]])) diff --git a/frontend/src/app/main/ui/nitrate/nitrate_activation_success_modal.scss b/frontend/src/app/main/ui/nitrate/nitrate_activation_success_modal.scss new file mode 100644 index 0000000000..72c5dfc339 --- /dev/null +++ b/frontend/src/app/main/ui/nitrate/nitrate_activation_success_modal.scss @@ -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; +} diff --git a/frontend/src/app/main/ui/nitrate/nitrate_code_activation_modal.cljs b/frontend/src/app/main/ui/nitrate/nitrate_code_activation_modal.cljs new file mode 100644 index 0000000000..2afa6d7cc3 --- /dev/null +++ b/frontend/src/app/main/ui/nitrate/nitrate_code_activation_modal.cljs @@ -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"]]]]])) diff --git a/frontend/src/app/main/ui/nitrate/nitrate_code_activation_modal.scss b/frontend/src/app/main/ui/nitrate/nitrate_code_activation_modal.scss new file mode 100644 index 0000000000..4fbe799df4 --- /dev/null +++ b/frontend/src/app/main/ui/nitrate/nitrate_code_activation_modal.scss @@ -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); +} diff --git a/frontend/src/app/main/ui/nitrate/nitrate_form.cljs b/frontend/src/app/main/ui/nitrate/nitrate_form.cljs index ef3bc46d38..c6a5aae89e 100644 --- a/frontend/src/app/main/ui/nitrate/nitrate_form.cljs +++ b/frontend/src/app/main/ui/nitrate/nitrate_form.cljs @@ -11,6 +11,7 @@ [app.main.data.modal :as modal] [app.main.data.nitrate :as dnt] [app.main.refs :as refs] + [app.main.store :as st] [app.main.ui.components.forms :as fm] [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] @@ -37,7 +38,12 @@ (mf/use-fn (mf/deps form) (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-dialog :subscription-success)} @@ -80,6 +86,10 @@ [:div {:class (stl/css :modal-text-small :modal-info)} "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)} [:a {:class (stl/css :link) :href dnt/go-to-subscription-url} @@ -92,6 +102,13 @@ "Contact us to try Nitrate for 14 days:")] [:p {:class (stl/css :modal-text-large)} [: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"]]]])]]]])) diff --git a/frontend/src/app/main/ui/nitrate/nitrate_form.scss b/frontend/src/app/main/ui/nitrate/nitrate_form.scss index d21a24c26c..69c33d87d5 100644 --- a/frontend/src/app/main/ui/nitrate/nitrate_form.scss +++ b/frontend/src/app/main/ui/nitrate/nitrate_form.scss @@ -107,6 +107,10 @@ color: var(--color-foreground-primary); } +.activation-code { + margin-block-start: var(--sp-xxxl); +} + .link { color: var(--color-accent-primary); } diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs index e22a65ff01..ccf88b31e5 100644 --- a/frontend/src/app/main/ui/settings/subscription.cljs +++ b/frontend/src/app/main/ui/settings/subscription.cljs @@ -18,6 +18,7 @@ [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.raw-svg :refer [raw-svg*]] + [app.main.ui.nitrate.nitrate-activation-success-modal] [app.main.ui.notifications.badge :refer [badge-notification]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr c]] @@ -37,6 +38,7 @@ cta-link-trial cta-text-with-icon cta-link-with-icon + show-activation-by-code editors recommended show-button-cta]}] @@ -66,6 +68,14 @@ [: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 @@ -75,14 +85,10 @@ [:button {:class (stl/css-case :cta-button true :bottom-link (not (and cta-link-trial cta-text-trial))) :on-click cta-link} cta-text]) - (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 show-activation-by-code + [:button {:class (stl/css :cta-button :activate-by-code) + :on-click #(st/emit! (modal/show {:type :nitrate-code-activation}))} + "Enter activation code"])]) (defn- make-management-form-schema [min-editors] [:map {:title "SeatsForm"} @@ -360,36 +366,6 @@ :value (tr "labels.close") :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* [{:keys [profile]}] @@ -498,7 +474,7 @@ ^boolean show-subscription-success-modal? (st/emit! (if (= params-subscription "subscribed-to-penpot-nitrate") - (modal/show :nitrate-success {}) + (modal/show :nitrate-activation-success {}) (modal/show :subscription-success {:subscription-name (if (= params-subscription "subscribed-to-penpot-unlimited") (if (= success-modal-is-trial? "true") @@ -658,6 +634,7 @@ :cta-link #(open-subscription-modal "nitrate" subscription) :cta-text-with-icon (tr "subscription.settings.more-information") :cta-link-with-icon go-to-pricing-page + :show-activation-by-code true :show-button-cta (not nitrate-license)}])]]]))