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) {