mirror of
https://github.com/penpot/penpot.git
synced 2026-04-29 13:18:29 +00:00
✨ Subscribe to nitrate with an activation code
* ✨ Subscribe to nitrate with an activation code * 📎 Code review
This commit is contained in:
parent
3f40be6b4d
commit
e22a03e7e8
@ -55,7 +55,7 @@
|
||||
result)))))
|
||||
|
||||
|
||||
(defn- with-validate [handler uri schema]
|
||||
(defn- with-validate [handler uri schema & {:keys [throw-on-error?]}]
|
||||
(fn []
|
||||
(let [response (handler)
|
||||
status (:status response)]
|
||||
@ -73,7 +73,11 @@
|
||||
:uri uri
|
||||
:status status
|
||||
:body (:body response)))
|
||||
nil)
|
||||
(if throw-on-error?
|
||||
(ex/raise :type :nitrate-http-error
|
||||
:status status
|
||||
:hint (str "nitrate HTTP " status " at " uri))
|
||||
nil))
|
||||
(= status 204) ;; 204 doesn't return any body
|
||||
nil
|
||||
:else ;; For success status codes, validate the response
|
||||
@ -89,11 +93,11 @@
|
||||
nil)))))))
|
||||
|
||||
(defn- request-to-nitrate
|
||||
[cfg method uri schema {:keys [::rpc/profile-id request-params] :as params}]
|
||||
[cfg method uri schema {:keys [::rpc/profile-id request-params throw-on-error?] :as params}]
|
||||
(let [shared-key (-> cfg ::setup/shared-keys :nitrate)
|
||||
full-http-call (-> (request-builder cfg method uri shared-key profile-id request-params)
|
||||
(with-retries 3)
|
||||
(with-validate uri schema))]
|
||||
(with-validate uri schema :throw-on-error? throw-on-error?))]
|
||||
(full-http-call)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@ -340,6 +344,18 @@
|
||||
"/api/connectivity")
|
||||
schema:connectivity params)))
|
||||
|
||||
(def ^:private schema:redeem-result
|
||||
[:map
|
||||
[:cancel-at [:maybe schema:timestamp]]])
|
||||
|
||||
(defn- redeem-activation-code-api
|
||||
[cfg params]
|
||||
(let [baseuri (cf/get :nitrate-backend-uri)]
|
||||
(request-to-nitrate cfg :post
|
||||
(str baseuri "/api/activation-codes/redeem")
|
||||
schema:redeem-result
|
||||
(assoc params :throw-on-error? true))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; INITIALIZATION
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@ -359,7 +375,8 @@
|
||||
:delete-team (partial delete-team-api cfg)
|
||||
:remove-team-from-org (partial remove-team-from-org-api cfg)
|
||||
:get-subscription (partial get-subscription-api cfg)
|
||||
:connectivity (partial get-connectivity-api cfg)}))
|
||||
:connectivity (partial get-connectivity-api cfg)
|
||||
:redeem-activation-code (partial redeem-activation-code-api cfg)}))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; UTILS
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.db :as db]
|
||||
[app.nitrate :as nitrate]
|
||||
[app.rpc :as-alias rpc]
|
||||
@ -56,6 +57,41 @@
|
||||
[cfg _params]
|
||||
(nitrate/call cfg :connectivity {}))
|
||||
|
||||
(def ^:private schema:redeem-activation-code-params
|
||||
[:map {:title "RedeemActivationCodeParams"}
|
||||
[:activation-code ::sm/text]])
|
||||
|
||||
(def ^:private schema:redeem-activation-code-result
|
||||
[:map {:title "RedeemActivationCodeResult"}
|
||||
[:cancel-at [:maybe ct/schema:inst]]])
|
||||
|
||||
(sv/defmethod ::redeem-nitrate-activation-code
|
||||
{::rpc/auth true
|
||||
::doc/added "2.14"
|
||||
::sm/params schema:redeem-activation-code-params
|
||||
::sm/result schema:redeem-activation-code-result}
|
||||
[cfg {:keys [::rpc/profile-id activation-code]}]
|
||||
(let [profile (db/get cfg :profile {:id profile-id})]
|
||||
(try
|
||||
(let [result (nitrate/call cfg :redeem-activation-code
|
||||
{:request-params {:code activation-code
|
||||
:penpot-id profile-id
|
||||
:email (:email profile)}})]
|
||||
(when-not result
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-activation-code
|
||||
:hint "The activation code is invalid, expired or fully redeemed"))
|
||||
result)
|
||||
(catch Exception cause
|
||||
(let [{:keys [type status]} (ex-data cause)]
|
||||
(if (= type :nitrate-http-error)
|
||||
(ex/raise :type :validation
|
||||
:code (case status
|
||||
410 :expired-activation-code
|
||||
:invalid-activation-code)
|
||||
:cause cause)
|
||||
(throw cause)))))))
|
||||
|
||||
(def ^:private sql:prefix-team-name-and-unset-default
|
||||
"UPDATE team
|
||||
SET name = ? || name,
|
||||
|
||||
@ -39,3 +39,4 @@
|
||||
(assert (contains? raw-svg-list id) "invalid raw svg id")
|
||||
[:> "svg" props
|
||||
[:use {:href (dm/str "#asset-" id)}]])
|
||||
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
;; 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 :refer [icon*]]
|
||||
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc nitrate-activation-success-modal*
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :nitrate-activation-success
|
||||
::mf/wrap-props true}
|
||||
[props]
|
||||
|
||||
(let [profile (mf/deref refs/profile)
|
||||
light? (= "light" (:theme profile))
|
||||
svg-id (if light? "logo-subscription-light" "logo-subscription")
|
||||
|
||||
cancel-at (dm/get-in props [: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)))]
|
||||
|
||||
[: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)}
|
||||
(tr "nitrate.activation-success.title")]
|
||||
|
||||
[:p {:class (stl/css :modal-text-primary)}
|
||||
(tr "nitrate.activation-success.active-until" date-str)]
|
||||
|
||||
[:p {:class (stl/css :modal-text)}
|
||||
(tr "nitrate.activation-success.manage-info")]
|
||||
|
||||
[:p {:class (stl/css :modal-text)}
|
||||
(tr "nitrate.activation-success.enjoy")]
|
||||
|
||||
[:> button* {:variant "primary"
|
||||
:on-click on-create-org
|
||||
:class (stl/css :modal-button)}
|
||||
(tr "nitrate.activation-success.create-org")]]]]]))
|
||||
@ -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;
|
||||
|
||||
@media (width <= 640px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-start svg {
|
||||
inline-size: 100%;
|
||||
block-size: auto;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
;; 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.data.profile :as dprof]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.nitrate.nitrate-activation-success-modal]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc nitrate-code-activation-modal*
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :nitrate-code-activation}
|
||||
[_props]
|
||||
(let [value* (mf/use-state "")
|
||||
error* (mf/use-state nil)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(reset! error* nil)
|
||||
(reset! value* (dom/get-target-val event))))
|
||||
|
||||
on-accept
|
||||
(mf/use-fn
|
||||
(mf/deps value*)
|
||||
(fn [_]
|
||||
(let [code (str/trim @value*)]
|
||||
(when (seq code)
|
||||
(->> (rp/cmd! ::redeem-nitrate-activation-code {:activation-code code})
|
||||
(rx/subs!
|
||||
(fn [result]
|
||||
(modal/hide!)
|
||||
(st/emit!
|
||||
(modal/show {:type :nitrate-activation-success :subscription result})
|
||||
(dprof/refresh-profile)))
|
||||
(fn [error]
|
||||
;; TODO: "Already used" is not yet detectable (CC upserts on reuse).
|
||||
(let [code (-> error ex-data :code)]
|
||||
(reset! error* (case code
|
||||
:expired-activation-code (tr "nitrate.activation-code.expired-error")
|
||||
(tr "nitrate.activation-code.invalid-error")))))))))))
|
||||
|
||||
on-key-down
|
||||
(mf/use-fn
|
||||
(mf/deps on-accept)
|
||||
(fn [event]
|
||||
(when (and (= "Enter" (.-key event)) (.-ctrlKey event))
|
||||
(on-accept event))))]
|
||||
|
||||
[: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)} (tr "nitrate.code-activation.title")]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css-case :code-field true :invalid (some? @error*))}
|
||||
[:label {:class (stl/css :code-label)}
|
||||
(tr "nitrate.code-activation.input-label")]
|
||||
[:textarea {:class (stl/css :code-textarea)
|
||||
:auto-focus true
|
||||
:value @value*
|
||||
:placeholder (tr "nitrate.code-activation.placeholder")
|
||||
:on-change on-change
|
||||
:on-key-down on-key-down}]
|
||||
(when @error*
|
||||
[:span {:class (stl/css :error-msg)} @error*])]
|
||||
|
||||
[:input
|
||||
{:type "button"
|
||||
:class (stl/css-case :accept-btn true
|
||||
:global/disabled (empty? (str/trim @value*)))
|
||||
:disabled (empty? (str/trim @value*))
|
||||
:value (tr "nitrate.code-activation.submit")
|
||||
:on-click on-accept}]
|
||||
[:div {:class (stl/css :footer-text)}
|
||||
(tr "nitrate.code-activation.footer") " "
|
||||
[:a {:class (stl/css :link)
|
||||
:href "mailto:sales@nitrate.com"}
|
||||
"sales@nitrate.com"]]]]]))
|
||||
@ -0,0 +1,107 @@
|
||||
// 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 *;
|
||||
@use "ds/_borders.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;
|
||||
|
||||
inline-size: $sz-480;
|
||||
max-inline-size: $sz-480;
|
||||
max-block-size: none;
|
||||
max-height: none;
|
||||
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);
|
||||
}
|
||||
|
||||
.accept-btn {
|
||||
@extend %modal-accept-btn;
|
||||
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.code-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.code-label {
|
||||
@include t.use-typography("body-medium");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.code-textarea {
|
||||
@include t.use-typography("body-medium");
|
||||
|
||||
block-size: $sz-200;
|
||||
resize: vertical;
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
padding: var(--sp-s);
|
||||
border-radius: $br-8;
|
||||
border: $b-1 solid var(--input-border-color);
|
||||
background-color: var(--input-background-color);
|
||||
color: var(--color-foreground-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.code-textarea:focus {
|
||||
border-color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
.invalid .code-textarea {
|
||||
border-color: var(--input-border-color-error);
|
||||
}
|
||||
|
||||
.invalid .code-textarea:focus {
|
||||
border-color: var(--input-border-color-error);
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
color: var(--element-foreground-error);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
@ -11,10 +11,13 @@
|
||||
[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*]]
|
||||
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
|
||||
[app.main.ui.nitrate.nitrate-code-activation-modal]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:nitrate-form
|
||||
@ -37,7 +40,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)}
|
||||
@ -51,7 +59,7 @@
|
||||
|
||||
[:div {:class (stl/css :modal-end)}
|
||||
[:div {:class (stl/css :modal-title)}
|
||||
"Unlock Nitrate Features"]
|
||||
(tr "nitrate.form.title")]
|
||||
|
||||
[:p {:class (stl/css :modal-text-large)}
|
||||
"Prow scuttle parrel provost."]
|
||||
@ -64,8 +72,8 @@
|
||||
[:p {:class (stl/css :modal-text-large)}
|
||||
|
||||
[:& fm/radio-buttons
|
||||
{:options [{:label "Price Tag Montly" :value "monthly"}
|
||||
{:label "Price Tag Yearly (Discount)" :value "yearly"}]
|
||||
{:options [{:label (tr "nitrate.form.billing-monthly") :value "monthly"}
|
||||
{:label (tr "nitrate.form.billing-yearly") :value "yearly"}]
|
||||
:name :subscription
|
||||
:class (stl/css :radio-btns)}]]
|
||||
|
||||
@ -75,23 +83,34 @@
|
||||
:on-click on-click
|
||||
:class (stl/css :modal-button)}
|
||||
(if (:subscription profile)
|
||||
"UPGRADE TO NITRATE"
|
||||
"Try it free for 14 days")]
|
||||
(tr "nitrate.form.upgrade")
|
||||
(tr "nitrate.form.try-free"))]
|
||||
[:div {:class (stl/css :modal-text-small :modal-info)}
|
||||
"Cancel anytime before your next billing cycle."]]]
|
||||
(tr "nitrate.form.cancel-anytime")]]]
|
||||
|
||||
[:p {:class (stl/css :modal-text-medium)}
|
||||
(tr "nitrate.form.have-code") " " [:a {:class (stl/css :link)
|
||||
:on-click on-activate-click}
|
||||
(tr "nitrate.form.enter-code")]]
|
||||
|
||||
[:p {:class (stl/css :modal-text-medium)}
|
||||
[:a {:class (stl/css :link) :href dnt/go-to-subscription-url}
|
||||
"See my current plan"]]]
|
||||
(tr "nitrate.form.see-plan")]]]
|
||||
|
||||
[:div {:class (stl/css :contact)}
|
||||
[:p {:class (stl/css :modal-text-large)}
|
||||
(if (:subscription profile)
|
||||
"Contact us to upgrade to Nitrate:"
|
||||
"Contact us to try Nitrate for 14 days:")]
|
||||
(tr "nitrate.form.contact-upgrade")
|
||||
(tr "nitrate.form.contact-trial"))]
|
||||
[: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)}
|
||||
(tr "nitrate.form.have-code")]
|
||||
[:p {:class (stl/css :modal-text-large)}
|
||||
[:a {:class (stl/css :link)
|
||||
:on-click on-activate-click}
|
||||
(tr "nitrate.form.enter-code")]]]])]]]]))
|
||||
|
||||
|
||||
|
||||
@ -77,36 +77,40 @@
|
||||
justify-content: center;
|
||||
min-inline-size: $sz-284;
|
||||
|
||||
svg {
|
||||
inline-size: 100%;
|
||||
block-size: auto;
|
||||
}
|
||||
|
||||
@media (width <= 992px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-start svg {
|
||||
inline-size: 100%;
|
||||
block-size: auto;
|
||||
}
|
||||
|
||||
.radio-btns {
|
||||
label {
|
||||
@include t.use-typography("body-large");
|
||||
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--sp-l) 0 0 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.radio-btns label {
|
||||
@include t.use-typography("body-large");
|
||||
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.contact {
|
||||
margin-block-start: $sz-96;
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.activation-code {
|
||||
margin-block-start: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
@ -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]]
|
||||
@ -36,6 +37,7 @@
|
||||
cta-link-trial
|
||||
cta-text-with-icon
|
||||
cta-link-with-icon
|
||||
show-activation-by-code
|
||||
editors
|
||||
recommended
|
||||
show-button-cta]}]
|
||||
@ -65,6 +67,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
|
||||
@ -74,14 +84,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}))}
|
||||
(tr "subscription.settings.activate-by-code")])])
|
||||
|
||||
(defn- make-management-form-schema [min-editors]
|
||||
[:map {:title "SeatsForm"}
|
||||
@ -359,37 +365,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.success.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]}]
|
||||
(let [route (mf/deref refs/route)
|
||||
@ -500,7 +475,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")
|
||||
@ -523,7 +498,7 @@
|
||||
[:> plan-card* {:card-title "Business Nitrate"
|
||||
:card-title-icon i/character-b
|
||||
:cancel-at (when (:cancel-at nitrate-license)
|
||||
(dm/str "Active until " (ct/format-inst (:cancel-at nitrate-license) "d MMMM, yyyy")))
|
||||
(tr "nitrate.subscription.active-until" (ct/format-inst (:cancel-at nitrate-license) "d MMMM, yyyy")))
|
||||
:benefits-title "Loren ipsum",
|
||||
:benefits ["Loren ipsum",
|
||||
"Loren ipsum",
|
||||
@ -660,6 +635,7 @@
|
||||
:cta-link (if (= subscription-type "unlimited") #(open-contact-sales-modal subscription-type "Nitrate") #(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)}])]]]))
|
||||
|
||||
|
||||
|
||||
@ -9252,3 +9252,100 @@ msgstr "Are you sure you want to delete this team that is part of %s org?"
|
||||
|
||||
msgid "plugins.validation.message"
|
||||
msgstr "Field %s is invalid: %s"
|
||||
|
||||
msgid "nitrate.code-activation.title"
|
||||
msgstr "Activate Nitrate"
|
||||
|
||||
msgid "nitrate.code-activation.input-label"
|
||||
msgstr "Enter your activation code"
|
||||
|
||||
msgid "nitrate.code-activation.submit"
|
||||
msgstr "Activate"
|
||||
|
||||
msgid "nitrate.code-activation.footer"
|
||||
msgstr "Need a code? Contact us:"
|
||||
|
||||
msgid "nitrate.code-activation.placeholder"
|
||||
msgstr "Paste your activation code here"
|
||||
|
||||
|
||||
msgid "nitrate.activation-success.title"
|
||||
msgstr "You are Business Nitrate!"
|
||||
|
||||
msgid "nitrate.activation-success.active-until"
|
||||
msgstr "Your plan is active until %s."
|
||||
|
||||
msgid "nitrate.activation-success.manage-info"
|
||||
msgstr "You can manage your subscription anytime from the Subscription page in your account settings."
|
||||
|
||||
msgid "nitrate.activation-success.enjoy"
|
||||
msgstr "Enjoy your plan!"
|
||||
|
||||
msgid "nitrate.activation-success.create-org"
|
||||
msgstr "Create organization"
|
||||
|
||||
msgid "nitrate.form.title"
|
||||
msgstr "Unlock Nitrate Features"
|
||||
|
||||
msgid "nitrate.form.billing-monthly"
|
||||
msgstr "Price Tag Monthly"
|
||||
|
||||
msgid "nitrate.form.billing-yearly"
|
||||
msgstr "Price Tag Yearly (Discount)"
|
||||
|
||||
msgid "nitrate.form.upgrade"
|
||||
msgstr "Upgrade to Nitrate"
|
||||
|
||||
msgid "nitrate.form.try-free"
|
||||
msgstr "Try it free for 14 days"
|
||||
|
||||
msgid "nitrate.form.cancel-anytime"
|
||||
msgstr "Cancel anytime before your next billing cycle."
|
||||
|
||||
msgid "nitrate.form.have-code"
|
||||
msgstr "Have an activation code?"
|
||||
|
||||
msgid "nitrate.form.enter-code"
|
||||
msgstr "Enter activation code"
|
||||
|
||||
msgid "nitrate.form.see-plan"
|
||||
msgstr "See my current plan"
|
||||
|
||||
msgid "nitrate.form.contact-upgrade"
|
||||
msgstr "Contact us to upgrade to Nitrate:"
|
||||
|
||||
msgid "nitrate.form.contact-trial"
|
||||
msgstr "Contact us to try Nitrate for 14 days:"
|
||||
|
||||
msgid "nitrate.activation-code.invalid-error"
|
||||
msgstr "Invalid code."
|
||||
|
||||
msgid "nitrate.activation-code.expired-error"
|
||||
msgstr "This code has expired."
|
||||
|
||||
msgid "nitrate.contact-sales.title"
|
||||
msgstr "Switch to %s plan?"
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-title"
|
||||
msgstr "When you downgrade:"
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-org-deleted"
|
||||
msgstr "Your organization will be deleted."
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-teams-available"
|
||||
msgstr "The teams, projects and files will no longer be part of any organization but they will remain available."
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-storage-limited"
|
||||
msgstr "Your total storage, auto-version history, and file recovery period will be limited."
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-contact-info"
|
||||
msgstr "To switch to this plan, please contact our sales team. We'll help you update your subscription and ensure everything is set up correctly."
|
||||
|
||||
msgid "nitrate.contact-sales.button"
|
||||
msgstr "Contact sales"
|
||||
|
||||
msgid "nitrate.subscription.active-until"
|
||||
msgstr "Active until %s"
|
||||
|
||||
msgid "subscription.settings.activate-by-code"
|
||||
msgstr "Enter activation code"
|
||||
@ -8977,3 +8977,103 @@ msgstr "Pulsar para cerrar la ruta"
|
||||
|
||||
msgid "modals.delete-org-team-confirm.message"
|
||||
msgstr "¿Estás seguro de que deseas eliminar este equipo que forma parte de la organización %s?"
|
||||
|
||||
msgid "nitrate.code-activation.title"
|
||||
msgstr "Activar Nitrate"
|
||||
|
||||
msgid "nitrate.code-activation.input-label"
|
||||
msgstr "Introduce tu código de activación"
|
||||
|
||||
msgid "nitrate.code-activation.submit"
|
||||
msgstr "Activar"
|
||||
|
||||
msgid "nitrate.code-activation.footer"
|
||||
msgstr "¿Necesitas un código? Contáctanos:"
|
||||
|
||||
msgid "nitrate.code-activation.placeholder"
|
||||
msgstr "Pega aquí tu código de activación"
|
||||
|
||||
|
||||
msgid "nitrate.activation-success.title"
|
||||
msgstr "¡Ya eres Business Nitrate!"
|
||||
|
||||
msgid "nitrate.activation-success.active-until"
|
||||
msgstr "Tu plan está activo hasta el %s."
|
||||
|
||||
msgid "nitrate.activation-success.manage-info"
|
||||
msgstr "Puedes gestionar tu suscripción en cualquier momento desde la página de Suscripción en la configuración de tu cuenta."
|
||||
|
||||
msgid "nitrate.activation-success.enjoy"
|
||||
msgstr "¡Disfruta de tu plan!"
|
||||
|
||||
msgid "nitrate.activation-success.create-org"
|
||||
msgstr "Crear organización"
|
||||
|
||||
msgid "nitrate.form.title"
|
||||
msgstr "Desbloquea las funciones de Nitrate"
|
||||
|
||||
msgid "nitrate.form.billing-monthly"
|
||||
msgstr "Precio mensual"
|
||||
|
||||
msgid "nitrate.form.billing-yearly"
|
||||
msgstr "Precio anual (descuento)"
|
||||
|
||||
msgid "nitrate.form.upgrade"
|
||||
msgstr "Actualizar a Nitrate"
|
||||
|
||||
msgid "nitrate.form.try-free"
|
||||
msgstr "Pruébalo gratis durante 14 días"
|
||||
|
||||
msgid "nitrate.form.cancel-anytime"
|
||||
msgstr "Cancela en cualquier momento antes de tu próximo ciclo de facturación."
|
||||
|
||||
msgid "nitrate.form.have-code"
|
||||
msgstr "¿Tienes un código de activación?"
|
||||
|
||||
msgid "nitrate.form.enter-code"
|
||||
msgstr "Introducir código de activación"
|
||||
|
||||
msgid "nitrate.form.see-plan"
|
||||
msgstr "Ver mi plan actual"
|
||||
|
||||
msgid "nitrate.form.contact-upgrade"
|
||||
msgstr "Contáctanos para actualizar a Nitrate:"
|
||||
|
||||
msgid "nitrate.form.contact-trial"
|
||||
msgstr "Contáctanos para probar Nitrate durante 14 días:"
|
||||
|
||||
msgid "nitrate.activation-code.invalid-error"
|
||||
msgstr "Código inválido."
|
||||
|
||||
msgid "nitrate.activation-code.expired-error"
|
||||
msgstr "Este código ha caducado."
|
||||
|
||||
msgid "nitrate.contact-sales.title"
|
||||
msgstr "¿Cambiar al plan %s?"
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-title"
|
||||
msgstr "Al bajar de plan:"
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-org-deleted"
|
||||
msgstr "Tu organización será eliminada."
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-teams-available"
|
||||
msgstr "Los equipos, proyectos y archivos dejarán de pertenecer a la organización pero seguirán disponibles."
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-storage-limited"
|
||||
msgstr "Tu almacenamiento total, el historial de versiones automático y el período de recuperación de archivos serán limitados."
|
||||
|
||||
msgid "nitrate.contact-sales.downgrade-contact-info"
|
||||
msgstr "Para cambiar a este plan, contacta con nuestro equipo de ventas. Te ayudaremos a actualizar tu suscripción y a asegurarnos de que todo esté configurado correctamente."
|
||||
|
||||
msgid "nitrate.contact-sales.button"
|
||||
msgstr "Contactar con ventas"
|
||||
|
||||
msgid "nitrate.subscription.active-until"
|
||||
msgstr "Activo hasta el %s"
|
||||
|
||||
msgid "subscription.settings.more-information"
|
||||
msgstr "Más información"
|
||||
|
||||
msgid "subscription.settings.activate-by-code"
|
||||
msgstr "Introducir código de activación"
|
||||
Loading…
x
Reference in New Issue
Block a user