diff --git a/apps/web/package.json b/apps/web/package.json
index 883f5771a..03f95b7df 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -22,6 +22,7 @@
"emotion-theming": "^10.0.19",
"event-source-polyfill": "^1.0.25",
"fast-sort": "^2.1.1",
+ "fetch-jsonp": "^1.2.1",
"file-saver": "^2.0.5",
"framer-motion": "^4.1.17",
"hash-wasm": "^4.9.0",
diff --git a/apps/web/public/index.html b/apps/web/public/index.html
index ec4dcf5ef..d30c2153e 100644
--- a/apps/web/public/index.html
+++ b/apps/web/public/index.html
@@ -6,7 +6,7 @@
/security#csp-meta-tag -->
{
if (isDev) return plan === "monthly" ? 9822 : 0;
else return plan === "monthly" ? 648884 : 658759;
@@ -12,6 +18,15 @@ const PRODUCT_ID = (plan) => {
function loadPaddle(eventCallback) {
return new Promise((resolve) => {
+ if (window.Paddle) {
+ window.Paddle.Options({
+ vendor: VENDOR_ID,
+ eventCallback,
+ });
+ resolve();
+ return;
+ }
+
var script = document.createElement("script");
script.src = "https://cdn.paddle.com/paddle/paddle.js";
script.async = true;
@@ -28,6 +43,53 @@ function loadPaddle(eventCallback) {
});
}
+function inlineCheckout({ user, plan, coupon, country, onCheckoutLoaded }) {
+ return new Promise(async (resolve) => {
+ await loadPaddle((e) => {
+ console.log("E", e);
+ const data = e.eventData;
+ switch (e.event) {
+ case "Checkout.Loaded":
+ onCheckoutLoaded && onCheckoutLoaded(data);
+ resolve();
+ break;
+ case "Checkout.Coupon.Applied":
+ onCheckoutLoaded && onCheckoutLoaded(data);
+ resolve();
+ break;
+ default:
+ break;
+ }
+ });
+
+ const { Paddle } = window;
+ if (!Paddle) return;
+
+ // if (coupon) {
+ // trackEvent(ANALYTICS_EVENTS.offerClaimed, `[${coupon}] redeemed!`);
+ // } else {
+ // trackEvent(ANALYTICS_EVENTS.checkoutStarted, `Checkout requested`);
+ // }
+
+ Paddle.Checkout.open({
+ frameTarget: "checkout-container",
+ frameStyle: "position: relative; width: 100%; border: 0;",
+ frameInitialHeight: 416,
+ disableLogout: true,
+ allowQuantity: false,
+ method: "inline",
+ displayModeTheme: Config.get("theme", "light"),
+ product: PRODUCT_ID(plan),
+ country,
+ email: user.email,
+ coupon,
+ passthrough: JSON.stringify({
+ userId: user.id,
+ }),
+ });
+ });
+}
+
async function upgrade(user, coupon, plan) {
if (!window.Paddle) {
await loadPaddle();
@@ -66,35 +128,18 @@ async function openPaddleDialog(overrideUrl) {
});
}
-async function getCouponData(coupon, plan) {
- let url =
- plan === "monthly"
- ? "https://checkout-service.paddle.com/checkout/1122-chree0325aa1705-38b9ffa5ce/coupon"
- : "https://checkout-service.paddle.com/checkout/1122-chre94eb195cbde-12dcba6761/coupon";
-
- try {
- const response = await fetch(url, {
- headers: {
- accept: "application/json, text/plain, */*",
- "content-type": "application/json;charset=UTF-8",
- },
- body: coupon
- ? JSON.stringify({ data: { coupon_code: coupon } })
- : undefined,
- method: coupon ? "POST" : "DELETE",
- });
- const json = await response.json();
- if (response.ok) {
- return json.data;
- } else {
- throw new Error(json.errors[0].details);
- }
- } catch (e) {
- console.error("Error: ", e);
- return plan === "monthly"
- ? DEFAULT_DATA_MONTHLY.data
- : DEFAULT_DATA_YEARLY.data;
- }
+async function getPlans() {
+ const monthlyProductId = PRODUCT_ID("monthly");
+ const yearlyProductId = PRODUCT_ID("yearly");
+ const url = `https://checkout.paddle.com/api/2.0/prices?product_ids=${yearlyProductId},${monthlyProductId}&callback=getPrices`;
+ const response = await fetchJsonp(url, {
+ jsonpCallback: "callback",
+ jsonpCallbackFunction: "getPrices",
+ });
+ const json = await response.json();
+ if (!json || !json.success || !json.response?.products?.length)
+ throw new Error("Failed to get prices.");
+ return json.response.products;
}
-export { upgrade, openPaddleDialog, getCouponData };
+export { upgrade, openPaddleDialog, getPlans, inlineCheckout };
diff --git a/apps/web/src/common/db.js b/apps/web/src/common/db.js
index 2160f90cc..c0fbbb451 100644
--- a/apps/web/src/common/db.js
+++ b/apps/web/src/common/db.js
@@ -18,22 +18,22 @@ async function initializeDatabase() {
db = new Database(Storage, EventSource, FS);
// if (isTesting()) {
- db.host({
- API_HOST: "https://api.notesnook.com",
- AUTH_HOST: "https://auth.streetwriters.co",
- SSE_HOST: "https://events.streetwriters.co",
- });
+ // db.host({
+ // API_HOST: "https://api.notesnook.com",
+ // AUTH_HOST: "https://auth.streetwriters.co",
+ // SSE_HOST: "https://events.streetwriters.co",
+ // });
// } else {
// db.host({
// API_HOST: "http://localhost:5264",
// AUTH_HOST: "http://localhost:8264",
// SSE_HOST: "http://localhost:7264",
// });
- // db.host({
- // API_HOST: "http://192.168.10.29:5264",
- // AUTH_HOST: "http://192.168.10.29:8264",
- // SSE_HOST: "http://192.168.10.29:7264",
- // });
+ db.host({
+ API_HOST: "http://192.168.10.29:5264",
+ AUTH_HOST: "http://192.168.10.29:8264",
+ SSE_HOST: "http://192.168.10.29:7264",
+ });
// }
await db.init();
diff --git a/apps/web/src/common/monthly.json b/apps/web/src/common/monthly.json
deleted file mode 100644
index a2b3c31a9..000000000
--- a/apps/web/src/common/monthly.json
+++ /dev/null
@@ -1,172 +0,0 @@
-{
- "data": {
- "public_checkout_id": "112231879-chree0325aa1705-38b9ffa5ce",
- "type": "default",
- "vendor": { "id": 128190, "name": "Streetwriters (Private) Limited" },
- "display_currency": "USD",
- "charge_currency": "USD",
- "settings": {
- "feature_flags": {
- "show_save_payment_details_UI_without_taking_payment": false,
- "show_save_payment_details_UI_and_take_payment": false,
- "marketing_consent": true,
- "customer_name": true,
- "tax_code": false,
- "tax_code_link": false,
- "coupon": true,
- "coupon_link": true,
- "quantity_enabled": false,
- "quantity_selection": false,
- "hide_compliance_bar": false
- },
- "variant": "multipage",
- "expires": "2021-12-07 23:59:59",
- "marketing_consent_message": "Streetwriters (Private) Limited may send me product updates and offers via email. It is possible to opt-out at any time.",
- "language_code": "en",
- "disable_logout": false,
- "styles": { "primary_colour": null, "theme": "light" },
- "inline_styles": null
- },
- "customer": {
- "id": 11363557,
- "email": "enkaboot@gmail.com",
- "country_code": null,
- "postcode": null,
- "remember_me": false,
- "audience_opt_in": false
- },
- "items": [
- {
- "checkout_product_id": 116991843,
- "product_id": 648884,
- "name": "Notesnook Pro (Monthly)",
- "custom_message": "",
- "quantity": 1,
- "allow_quantity": false,
- "icon_url": "https://paddle.s3.amazonaws.com/user/128190/UgQbUiBTuWY7j9PDMnvY_android-chrome-512x512.png",
- "prices": [
- {
- "currency": "USD",
- "unit_price": {
- "net": 4.49,
- "gross": 4.49,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 4.49,
- "gross_after_discount": 4.49,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "line_price": {
- "net": 4.49,
- "gross": 4.49,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 4.49,
- "gross_after_discount": 4.49,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "discounts": [],
- "tax_rate": 0.0
- }
- ],
- "recurring": {
- "period": "month",
- "interval": 1,
- "trial_days": 0,
- "prices": [
- {
- "currency": "USD",
- "unit_price": {
- "net": 4.49,
- "gross": 4.49,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 4.49,
- "gross_after_discount": 4.49,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "line_price": {
- "net": 4.49,
- "gross": 4.49,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 4.49,
- "gross_after_discount": 4.49,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "discounts": [],
- "tax_rate": 0.0
- }
- ]
- },
- "webhook_url": null
- }
- ],
- "available_payment_methods": [],
- "total": [
- {
- "net": 4.49,
- "gross": 4.49,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 4.49,
- "gross_after_discount": 4.49,
- "tax": 0.0,
- "tax_after_discount": 0.0,
- "currency": "USD",
- "is_free": false,
- "includes_tax": false,
- "tax_rate": 0.0
- }
- ],
- "pending_payment": false,
- "completed": false,
- "payment_method_type": null,
- "flagged_for_review": false,
- "ip_geo_country_code": "PK",
- "tax": null,
- "passthrough": "{}",
- "redirect_url": null,
- "tracking": {
- "source_page": "http://localhost:3000/settings",
- "test_name": null,
- "test_variant": null,
- "initial_request": {
- "data": {
- "type": "default",
- "items": [{ "product_id": 648884 }],
- "customer": { "email": "redacted" },
- "settings": { "passthrough": "{}" },
- "tracking": { "referrer": "localhost:3000 / localhost:3000" },
- "parent_url": "http://localhost:3000/settings",
- "geo_country": "PK"
- }
- }
- },
- "created_at": "2021-10-08 12:03:13",
- "paddlejs": {
- "vendor": {
- "currency": "USD",
- "prices": {
- "unit": 4.49,
- "unit_tax": 0.0,
- "total": 4.49,
- "total_tax": 0.0
- },
- "recurring_prices": {
- "unit": 4.49,
- "unit_tax": 0.0,
- "total": 4.49,
- "total_tax": 0.0
- }
- }
- },
- "payment_details": null,
- "environment": "production",
- "messages": []
- }
-}
diff --git a/apps/web/src/common/yearly.json b/apps/web/src/common/yearly.json
deleted file mode 100644
index 2b1ab3ee9..000000000
--- a/apps/web/src/common/yearly.json
+++ /dev/null
@@ -1,188 +0,0 @@
-{
- "data": {
- "public_checkout_id": "112231646-chre94eb195cbde-12dcba6761",
- "type": "default",
- "vendor": { "id": 128190, "name": "Streetwriters (Private) Limited" },
- "display_currency": "USD",
- "charge_currency": "USD",
- "settings": {
- "feature_flags": {
- "show_save_payment_details_UI_without_taking_payment": false,
- "show_save_payment_details_UI_and_take_payment": false,
- "marketing_consent": true,
- "customer_name": true,
- "tax_code": false,
- "tax_code_link": false,
- "coupon": true,
- "coupon_link": true,
- "quantity_enabled": false,
- "quantity_selection": false,
- "hide_compliance_bar": false
- },
- "variant": "multipage",
- "expires": "2021-12-07 23:59:59",
- "marketing_consent_message": "Streetwriters (Private) Limited may send me product updates and offers via email. It is possible to opt-out at any time.",
- "language_code": "en",
- "disable_logout": false,
- "styles": { "primary_colour": null, "theme": "light" },
- "inline_styles": null
- },
- "customer": {
- "id": 11363557,
- "email": "enkaboot@gmail.com",
- "country_code": "PK",
- "postcode": null,
- "remember_me": false,
- "audience_opt_in": false
- },
- "items": [
- {
- "checkout_product_id": 116991610,
- "product_id": 658759,
- "name": "Notesnook Pro (Yearly)",
- "custom_message": "",
- "quantity": 1,
- "allow_quantity": false,
- "icon_url": "https://paddle.s3.amazonaws.com/user/128190/9v0fhULnQUCZzo3DMkJ8_android-chrome-512x512.png",
- "prices": [
- {
- "currency": "USD",
- "unit_price": {
- "net": 49.99,
- "gross": 49.99,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 49.99,
- "gross_after_discount": 49.99,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "line_price": {
- "net": 49.99,
- "gross": 49.99,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 49.99,
- "gross_after_discount": 49.99,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "discounts": [],
- "tax_rate": 0.0
- }
- ],
- "recurring": {
- "period": "year",
- "interval": 1,
- "trial_days": 0,
- "prices": [
- {
- "currency": "USD",
- "unit_price": {
- "net": 49.99,
- "gross": 49.99,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 49.99,
- "gross_after_discount": 49.99,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "line_price": {
- "net": 49.99,
- "gross": 49.99,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 49.99,
- "gross_after_discount": 49.99,
- "tax": 0.0,
- "tax_after_discount": 0.0
- },
- "discounts": [],
- "tax_rate": 0.0
- }
- ]
- },
- "webhook_url": null
- }
- ],
- "available_payment_methods": [
- {
- "type": "SPREEDLY_CARD",
- "weight": 1,
- "options": {
- "api_key": "LIyzQnqLfedZ3oo6aWJFxSiqgfW",
- "spreedly_environment_key": "LIyzQnqLfedZ3oo6aWJFxSiqgfW"
- },
- "seller_friendly_name": "card"
- },
- {
- "type": "PAYPAL",
- "weight": 2,
- "options": [],
- "seller_friendly_name": "paypal"
- }
- ],
- "total": [
- {
- "net": 49.99,
- "gross": 49.99,
- "net_discount": 0.0,
- "gross_discount": 0.0,
- "net_after_discount": 49.99,
- "gross_after_discount": 49.99,
- "tax": 0.0,
- "tax_after_discount": 0.0,
- "currency": "USD",
- "is_free": false,
- "includes_tax": false,
- "tax_rate": 0.0
- }
- ],
- "pending_payment": false,
- "completed": false,
- "payment_method_type": null,
- "flagged_for_review": false,
- "ip_geo_country_code": "PK",
- "tax": null,
- "passthrough": "{}",
- "redirect_url": null,
- "tracking": {
- "source_page": "http://localhost:3000/settings",
- "test_name": null,
- "test_variant": null,
- "initial_request": {
- "data": {
- "type": "default",
- "items": [{ "product_id": 658759 }],
- "customer": { "email": "redacted" },
- "settings": { "passthrough": "{}" },
- "tracking": { "referrer": "localhost:3000 / localhost:3000" },
- "parent_url": "http://localhost:3000/settings",
- "geo_country": "PK"
- }
- }
- },
- "created_at": "2021-10-08 12:00:58",
- "paddlejs": {
- "vendor": {
- "currency": "USD",
- "prices": {
- "unit": 49.99,
- "unit_tax": 0.0,
- "total": 49.99,
- "total_tax": 0.0
- },
- "recurring_prices": {
- "unit": 49.99,
- "unit_tax": 0.0,
- "total": 49.99,
- "total_tax": 0.0
- }
- }
- },
- "payment_details": null,
- "environment": "production",
- "messages": []
- }
-}
diff --git a/apps/web/src/components/dialogs/buydialog.js b/apps/web/src/components/dialogs/buydialog.js
index 3449a03e7..2f0efc55e 100644
--- a/apps/web/src/components/dialogs/buydialog.js
+++ b/apps/web/src/components/dialogs/buydialog.js
@@ -1,9 +1,9 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
import { Text, Flex, Button, Box } from "rebass";
import Dialog from "./dialog";
import * as Icon from "../icons";
import { useStore as useUserStore } from "../../stores/user-store";
-import { getCouponData, upgrade } from "../../common/checkout";
+import { getPlans, inlineCheckout, upgrade } from "../../common/checkout";
import getSymbolFromCurrency from "currency-symbol-map";
import { ANALYTICS_EVENTS, trackEvent } from "../../utils/analytics";
import { navigate } from "../../navigation";
@@ -11,6 +11,9 @@ import Switch from "../switch";
import Modal from "react-modal";
import { useTheme } from "emotion-theming";
import { ReactComponent as Rocket } from "../../assets/rocket.svg";
+import Loader from "../loader";
+import Field from "../field";
+import { useSessionState } from "../../utils/hooks";
const sections = [
{
@@ -251,51 +254,13 @@ const sections = [
},
];
-const plans = [
- {
- title: "Monthly",
- subtitle: "Pay once a month",
- price: 4.99,
- currency: "USD",
- },
- {
- title: "Yearly",
- subtitle: "Pay once a year",
- price: 49.99,
- currency: "USD",
- },
-];
-
function BuyDialog(props) {
- const { couponCode } = props;
- const [coupon, setCoupon] = useState(couponCode);
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState();
- const [prices, setPrices] = useState();
- const [plan, setPlan] = useState(props.plan || "monthly");
- const isLoggedIn = useUserStore((store) => store.isLoggedIn);
const user = useUserStore((store) => store.user);
+ const isLoggedIn = useUserStore((store) => store.isLoggedIn);
+ const [selectedPlan, setSelectedPlan] = useState();
+ const [discount, setDiscount] = useState({ isApplyingCoupon: false });
const theme = useTheme();
- useEffect(() => {
- (async function () {
- try {
- setIsLoading(true);
- setError();
- const data = await getCouponData(coupon, plan);
- setPrices({
- ...data.paddlejs.vendor,
- withoutDiscount: data.total[0],
- });
- } catch (e) {
- console.error(e);
- setError(e.message);
- } finally {
- setIsLoading(false);
- }
- })();
- }, [coupon, plan]);
-
useEffect(() => {
trackEvent(ANALYTICS_EVENTS.purchaseInitiated, "Buy dialog opened.");
}, []);
@@ -346,9 +311,8 @@ function BuyDialog(props) {
>
-
-
- Choose a plan
-
-
- Every day we spend hours improving Notesnook. You are what makes
- that possible.
-
+ {isLoggedIn ? (
+ selectedPlan ? (
+ setSelectedPlan()}
+ onCouponApplied={(coupon) => {
+ setDiscount({ isApplyingCoupon: true });
+ setSelectedPlan((plan) => ({ ...plan, coupon }));
+ }}
+ />
+ ) : (
+ setSelectedPlan(plan)}
+ />
+ )
+ ) : (
+
+ )}
-
- {plans.map((plan) => (
-
-
- {plan.title}
-
- {plan.subtitle}
-
-
-
- {plan.price} {plan.currency}
-
-
- ))}
-
- {/*
-
-
- Notesnook Pro
-
-
- Ready to take the next step in your private note taking journey?
-
-
-
-
- {sections.map((section) => (
-
- {section.pro && (
-
-
-
- Pro
-
-
- )}
-
- {section.title}
-
-
- {section.detail}
-
- {section.features && (
-
- {section.features.map((feature) => (
-
-
- {feature.pro && (
-
-
-
- Pro
-
-
- )}
- {feature.title && (
-
- {feature.title}
-
- )}
-
- ))}
-
- )}
- {section.info && (
-
- {section.info}
-
- )}
-
- ))}
- */}
+ {selectedPlan ? (
+ {
+ const pricingInfo = getPricingInfoFromCheckout(
+ selectedPlan,
+ data
+ );
+ console.log(pricingInfo, selectedPlan.coupon);
+ setDiscount(pricingInfo);
+ }}
+ />
+ ) : (
+
+ )}
);
-
- /*
-
-
- Notesnook Pro
-
- Ready to take the next step on your private note taking journey?
-
-
-
- PRO
-
-
-
- {premiumDetails.map((item) => (
-
-
-
- {item.title}
-
-
- ))}
-
-
-
- {isLoading ? (
-
- ) : error ? (
- <>
-
- {error}
-
-
- >
- ) : (
- <>
-
-
- Monthly
-
- {
- setPlan((s) => (s === "monthly" ? "yearly" : "monthly"));
- }}
- />
-
- Yearly
-
-
- {coupon ? (
-
-
-
- Coupon:
-
-
- {coupon}
-
-
-
-
- {
- const code = window.prompt("Enter new coupon code:");
- setCoupon(code);
- }}
- >
- Change
-
- {
- if (
- window.confirm(
- "Are you sure you want to remove this coupon?"
- )
- ) {
- setCoupon();
- }
- }}
- >
- Remove
-
-
-
- ) : (
- {
- const code = window.prompt("Enter new coupon code:");
- setCoupon(code);
- }}
- >
- Add coupon code
-
- )}
-
- Only
-
-
-
- Cancel anytime. No questions asked.
-
-
- >
- )}
-
- */
- //
- // );
}
export default BuyDialog;
-function MainPricing(props) {
- const { prices, plan } = props;
-
- if (prices.withoutDiscount.net !== prices.prices.total)
- return (
- <>
-
-
- {" "}
-
- {prices.recurring_prices.total === prices.prices.total
- ? ` per ${planToPeriod(plan)}`
- : ` your first ${planToPeriod(plan)}`}
- >
- );
- else
- return (
- <>
-
- {` per ${planToPeriod(plan)}`}
- >
- );
-}
-
function RecurringPricing(props) {
- const { prices, plan } = props;
-
- if (prices.prices.total === prices.recurring_prices.total) return null;
+ const { plan } = props;
+ // if (product.prices.total === prices.recurring_prices.total) return null;
return (
-
- And then{" "}
- {" "}
- every {planToPeriod(plan)} afterwards.
+
+
+
+
+ {formatPeriod(plan.key)}
);
}
@@ -734,6 +404,378 @@ function Price(props) {
);
}
-function planToPeriod(plan) {
- return plan === "monthly" ? "month" : "year";
+function FeaturesList() {
+ return (
+
+ {sections.map((section) => (
+
+ {section.pro && (
+
+
+
+ Pro
+
+
+ )}
+
+ {section.title}
+
+
+ {section.detail}
+
+ {section.features && (
+
+ {section.features.map((feature) => (
+
+
+ {feature.pro && (
+
+
+
+ Pro
+
+
+ )}
+ {feature.title && (
+
+ {feature.title}
+
+ )}
+
+ ))}
+
+ )}
+ {section.info && (
+
+ {section.info}
+
+ )}
+
+ ))}
+
+ );
+}
+
+function Checkout({ user, plan, onCheckoutLoaded }) {
+ const [isLoading, setIsLoading] = useState(true);
+ useEffect(() => {
+ (async () => {
+ setIsLoading(true);
+ await inlineCheckout({
+ user,
+ plan: plan.key,
+ coupon: plan.coupon,
+ country: plan.country,
+ onCheckoutLoaded,
+ });
+ setIsLoading(false);
+ })();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [plan, user]);
+
+ return (
+
+ {isLoading ? (
+
+ ) : null}
+
+
+ );
+}
+
+const CACHED_PLANS = [
+ {
+ key: "monthly",
+ title: "Monthly",
+ subtitle: `Pay once a month.`,
+ },
+ {
+ key: "yearly",
+ title: "Yearly",
+ subtitle: `Pay once a year.`,
+ },
+];
+function PlansList({ selectedPlan, onPlanChanged }) {
+ const [isLoading, setIsLoading] = useState(false);
+ const [plans, setPlans] = useSessionState("PlansList:plans", CACHED_PLANS);
+
+ useEffect(() => {
+ if (plans && plans !== CACHED_PLANS) return;
+ (async function () {
+ try {
+ setIsLoading(true);
+ let plans = await getPlans();
+ plans = plans.map((product) => {
+ return {
+ key: `${product.subscription.interval}ly`,
+ country: product.customer_country,
+ currency: product.currency,
+ price: product.price.net,
+ id: product.product_id,
+ title:
+ product.subscription.interval === "month" ? "Monthly" : "Yearly",
+ subtitle: `Pay once a ${product.subscription.interval}.`,
+ };
+ });
+ setPlans(plans);
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setIsLoading(false);
+ }
+ })();
+ }, []);
+
+ return plans.map((plan) => (
+
+ ));
+}
+
+function ChooseAPlan({ selectedPlan, onPlanChanged }) {
+ return (
+ <>
+
+
+ Choose a plan
+
+
+ Every day we spend hours improving Notesnook. You are what makes that
+ possible.
+
+
+
+
+ >
+ );
+}
+
+function TryForFree() {
+ return (
+ <>
+
+
+ Notesnook Pro
+
+
+ Ready to take the next step in your private note taking journey?
+
+
+ >
+ );
+}
+
+function SelectedPlan({ plan, onPlanChangeRequest, onCouponApplied }) {
+ console.log("SELECTED", plan);
+ useEffect(() => {
+ const couponInput = document.getElementById("coupon");
+ couponInput.value = plan.coupon ? plan.coupon : couponInput.value;
+ }, [plan.coupon]);
+
+ return (
+ <>
+
+
+ Notesnook Pro
+
+
+ {plan.title}
+
+ {
+ if (e.code === "Enter") {
+ const couponInput = document.getElementById("coupon");
+ if (!!plan.coupon) couponInput.value = "";
+ onCouponApplied(couponInput.value);
+ }
+ }}
+ action={{
+ icon: plan.isApplyingCoupon
+ ? Icon.Loading
+ : plan.coupon
+ ? Icon.Cross
+ : Icon.Check,
+ onClick: () => {
+ const couponInput = document.getElementById("coupon");
+ if (!!plan.coupon) couponInput.value = "";
+ onCouponApplied(couponInput.value);
+ },
+ }}
+ />
+
+
+ >
+ );
+}
+
+function CheckoutPricing({
+ currency,
+ period,
+ subtotal,
+ discountedPrice,
+ isRecurringDiscount,
+}) {
+ const fields = [
+ {
+ key: "subtotal",
+ label: "Subtotal",
+ value: formatPrice(currency, subtotal.toFixed(2)),
+ },
+ {
+ key: "discount",
+ label: "Discount",
+ color: "primary",
+ value: formatPrice(
+ currency,
+ discountedPrice.toFixed(2),
+ null,
+ discountedPrice > 0
+ ),
+ },
+ ];
+
+ return (
+ <>
+ {fields.map((field) => (
+
+
+ {field.label}
+
+
+ {field.value}
+
+
+ ))}
+
+
+ Total
+
+
+ {formatPrice(
+ currency,
+ subtotal - discountedPrice,
+ isRecurringDiscount || discountedPrice <= 0 ? period : "null"
+ )}
+
+ {isRecurringDiscount || discountedPrice <= 0
+ ? ""
+ : `then ${formatPrice(currency, subtotal, period)}`}
+
+
+
+ >
+ );
+}
+
+function formatPrice(currency, price, period, negative = false) {
+ return `${negative ? "-" : ""}${getSymbolFromCurrency(
+ currency
+ )}${price}${formatPeriod(period)}`;
+}
+
+function formatPeriod(period) {
+ return period === "monthly" ? "/mo" : period === "yearly" ? "/yr" : "";
+}
+
+function getPricingInfoFromCheckout(plan, eventData) {
+ const { checkout } = eventData;
+ const { prices, recurring_prices, coupon } = checkout;
+ const price = parseFloat(prices.customer.total);
+ const recurringPrice = parseFloat(recurring_prices.customer.total);
+
+ return {
+ price: plan.price,
+ discount: plan.price - price,
+ coupon: coupon.coupon_code,
+ isRecurringDiscount: coupon.coupon_code && price === recurringPrice,
+ isInvalidCoupon: !!plan.coupon !== !!coupon.coupon_code,
+ };
}
diff --git a/apps/web/src/components/field/index.js b/apps/web/src/components/field/index.js
index bff232a0f..bba974be0 100644
--- a/apps/web/src/components/field/index.js
+++ b/apps/web/src/components/field/index.js
@@ -48,6 +48,7 @@ function Field(props) {
placeholder,
validatePassword,
onError,
+ variant,
} = props;
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [rules, setRules] = useState(passwordValidationRules);
@@ -83,6 +84,7 @@ function Field(props) {