mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-22 22:49:45 +01:00
feat: finalize ne checkout/upgrade dialog ui
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/security#csp-meta-tag -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="script-src 'self' https://analytics.streetwriters.co https://cdn.paddle.com https://cdnjs.cloudflare.com 'unsafe-inline' 'unsafe-eval';"
|
||||
content="script-src 'self' https://checkout.paddle.com https://analytics.streetwriters.co https://cdn.paddle.com https://cdnjs.cloudflare.com 'unsafe-inline' 'unsafe-eval';"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { ANALYTICS_EVENTS, trackEvent } from "../utils/analytics";
|
||||
import DEFAULT_DATA_MONTHLY from "./monthly.json";
|
||||
import DEFAULT_DATA_YEARLY from "./yearly.json";
|
||||
import fetchJsonp from "fetch-jsonp";
|
||||
import Config from "../utils/config";
|
||||
|
||||
const isDev = false; // process.env.NODE_ENV === "development";
|
||||
|
||||
const VENDOR_ID = isDev ? 1506 : 128190;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"yearly"|"monthly"} plan
|
||||
* @returns
|
||||
*/
|
||||
const PRODUCT_ID = (plan) => {
|
||||
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",
|
||||
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 (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;
|
||||
}
|
||||
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 };
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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": "<var vendorname>Streetwriters (Private) Limited</var> 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": []
|
||||
}
|
||||
}
|
||||
@@ -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": "<var vendorname>Streetwriters (Private) Limited</var> 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": []
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
>
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
width={"60%"}
|
||||
// maxHeight={["100%", "80%", "70%"]}
|
||||
height={["100%", "80%", "70%"]}
|
||||
maxWidth={["100%", "80%", "60%"]}
|
||||
maxHeight={["100%", "80%", "80%"]}
|
||||
bg="transparent"
|
||||
alignSelf={"center"}
|
||||
overflowY={props.scrollable ? "auto" : "hidden"}
|
||||
@@ -363,8 +327,10 @@ function BuyDialog(props) {
|
||||
flexDirection="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexShrink={0}
|
||||
sx={{
|
||||
borderRadius: "dialog",
|
||||
borderTopLeftRadius: "dialog",
|
||||
borderBottomLeftRadius: "dialog",
|
||||
overflow: "hidden",
|
||||
bg: "bgTransparent",
|
||||
backdropFilter: "blur(8px)",
|
||||
@@ -373,63 +339,73 @@ function BuyDialog(props) {
|
||||
p={4}
|
||||
py={50}
|
||||
>
|
||||
<Rocket width={200} />
|
||||
<Text variant="heading" textAlign="center" mt={4}>
|
||||
Choose a plan
|
||||
</Text>
|
||||
<Text variant="body" textAlign="center" mt={1}>
|
||||
Every day we spend hours improving Notesnook. You are what makes
|
||||
that possible.
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
flex={1}
|
||||
flexDirection="column"
|
||||
overflowY="auto"
|
||||
sx={{ position: "relative" }}
|
||||
pt={6}
|
||||
bg="background"
|
||||
justifyContent="center"
|
||||
>
|
||||
{plans.map((plan) => (
|
||||
<Flex justifyContent="space-between" alignItems="center" p={6}>
|
||||
<Text variant="heading" fontWeight="normal">
|
||||
{plan.title}
|
||||
<Text variant="subtitle" fontWeight="normal">
|
||||
{plan.subtitle}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text variant="body" fontSize="subheading">
|
||||
{plan.price} {plan.currency}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
{/* <Flex
|
||||
flexDirection="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{
|
||||
borderRadius: "dialog",
|
||||
overflow: "hidden",
|
||||
bg: "bgTransparent",
|
||||
backdropFilter: "blur(8px)",
|
||||
{isLoggedIn ? (
|
||||
selectedPlan ? (
|
||||
<SelectedPlan
|
||||
plan={{ ...selectedPlan, ...discount }}
|
||||
onPlanChangeRequest={() => setSelectedPlan()}
|
||||
onCouponApplied={(coupon) => {
|
||||
setDiscount({ isApplyingCoupon: true });
|
||||
setSelectedPlan((plan) => ({ ...plan, coupon }));
|
||||
}}
|
||||
width={350}
|
||||
p={4}
|
||||
py={50}
|
||||
>
|
||||
<Rocket width={200} />
|
||||
<Text variant="heading" textAlign="center" mt={4}>
|
||||
Notesnook Pro
|
||||
</Text>
|
||||
<Text variant="body" textAlign="center" mt={1}>
|
||||
Ready to take the next step in your private note taking journey?
|
||||
</Text>
|
||||
<Button variant="primary" mt={4}>
|
||||
Try free for 14 days
|
||||
</Button>
|
||||
/>
|
||||
) : (
|
||||
<ChooseAPlan
|
||||
selectedPlan={selectedPlan}
|
||||
onPlanChanged={(plan) => setSelectedPlan(plan)}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<TryForFree />
|
||||
)}
|
||||
</Flex>
|
||||
{selectedPlan ? (
|
||||
<Checkout
|
||||
user={user}
|
||||
plan={selectedPlan}
|
||||
onCheckoutLoaded={(data) => {
|
||||
const pricingInfo = getPricingInfoFromCheckout(
|
||||
selectedPlan,
|
||||
data
|
||||
);
|
||||
console.log(pricingInfo, selectedPlan.coupon);
|
||||
setDiscount(pricingInfo);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FeaturesList />
|
||||
)}
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
export default BuyDialog;
|
||||
|
||||
function RecurringPricing(props) {
|
||||
const { plan } = props;
|
||||
// if (product.prices.total === prices.recurring_prices.total) return null;
|
||||
return (
|
||||
<Text variant="body" fontSize="subBody">
|
||||
<Text as="span" fontSize="subtitle">
|
||||
<Price currency={plan.currency} price={plan.price} />
|
||||
</Text>
|
||||
{formatPeriod(plan.key)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
function Price(props) {
|
||||
const { currency, price } = props;
|
||||
return (
|
||||
<>
|
||||
{getSymbolFromCurrency(currency)}
|
||||
{price}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FeaturesList() {
|
||||
return (
|
||||
<Flex
|
||||
flex={1}
|
||||
flexDirection="column"
|
||||
@@ -499,241 +475,307 @@ function BuyDialog(props) {
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Flex> */}
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
/* <Flex flexDirection="column" flex={1} overflowY="hidden">
|
||||
<Flex bg="primary" p={5} sx={{ position: "relative" }}>
|
||||
<Text variant="heading" fontSize="38px" color="static">
|
||||
Notesnook Pro
|
||||
<Text
|
||||
variant="subBody"
|
||||
color="static"
|
||||
opacity={1}
|
||||
fontWeight="normal"
|
||||
fontSize="title"
|
||||
>
|
||||
Ready to take the next step on your private note taking journey?
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
sx={{ position: "absolute", top: 0, right: 0 }}
|
||||
variant="heading"
|
||||
color="static"
|
||||
opacity={0.2}
|
||||
fontSize={90}
|
||||
>
|
||||
PRO
|
||||
</Text>
|
||||
</Flex>
|
||||
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 (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
px={5}
|
||||
pb={2}
|
||||
bg="background"
|
||||
width="500px"
|
||||
padding={40}
|
||||
overflowY="auto"
|
||||
sx={{ position: "relative" }}
|
||||
alignItems={"center"}
|
||||
>
|
||||
{premiumDetails.map((item) => (
|
||||
<Flex mt={2}>
|
||||
<Icon.Checkmark color="primary" size={16} />
|
||||
<Text variant="body" fontSize="title" ml={1}>
|
||||
{item.title}
|
||||
{isLoading ? (
|
||||
<Loader
|
||||
title={
|
||||
plan.isApplyingCoupon
|
||||
? "Applying coupon code. Please wait..."
|
||||
: "Loading checkout. Please wait..."
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<Box
|
||||
flex={1}
|
||||
className="checkout-container"
|
||||
display={isLoading ? "none" : "block"}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
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) => (
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
variant="tool"
|
||||
display="flex"
|
||||
textAlign="start"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
flex={1}
|
||||
mt={1}
|
||||
sx={{
|
||||
bg: selectedPlan?.key === plan.key ? "border" : "transparent",
|
||||
border:
|
||||
selectedPlan?.key === plan.key ? "1px solid var(--primary)" : "none",
|
||||
}}
|
||||
onClick={() => onPlanChanged(plan)}
|
||||
>
|
||||
<Text variant="subtitle" fontWeight="normal">
|
||||
{plan.title}
|
||||
<Text variant="body" fontWeight="normal" color="fontTertiary">
|
||||
{plan.subtitle}
|
||||
</Text>
|
||||
</Text>
|
||||
{isLoading ? <Icon.Loading /> : <RecurringPricing plan={plan} />}
|
||||
</Button>
|
||||
));
|
||||
}
|
||||
|
||||
function ChooseAPlan({ selectedPlan, onPlanChanged }) {
|
||||
return (
|
||||
<>
|
||||
<Rocket width={200} />
|
||||
<Text variant="heading" textAlign="center" mt={4}>
|
||||
Choose a plan
|
||||
</Text>
|
||||
<Text variant="body" textAlign="center" mt={1}>
|
||||
Every day we spend hours improving Notesnook. You are what makes that
|
||||
possible.
|
||||
</Text>
|
||||
<Flex flexDirection="column" alignSelf="stretch" mt={2}>
|
||||
<PlansList selectedPlan={selectedPlan} onPlanChanged={onPlanChanged} />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TryForFree() {
|
||||
return (
|
||||
<>
|
||||
<Rocket width={200} />
|
||||
<Text variant="heading" textAlign="center" mt={4}>
|
||||
Notesnook Pro
|
||||
</Text>
|
||||
<Text variant="body" textAlign="center" mt={1}>
|
||||
Ready to take the next step in your private note taking journey?
|
||||
</Text>
|
||||
<Button variant="primary" mt={4}>
|
||||
Try free for 14 days
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Rocket width={200} />
|
||||
<Text variant="heading" textAlign="center" mt={4}>
|
||||
Notesnook Pro
|
||||
</Text>
|
||||
<Text variant="body" fontSize="subheading" textAlign="center" mt={1}>
|
||||
{plan.title}
|
||||
</Text>
|
||||
<Field
|
||||
variant={plan.isInvalidCoupon ? "error" : "input"}
|
||||
sx={{ alignSelf: "stretch", my: 2 }}
|
||||
styles={{ input: { fontSize: "body" } }}
|
||||
id="coupon"
|
||||
name="coupon"
|
||||
placeholder="Coupon code"
|
||||
autoFocus={plan.isInvalidCoupon}
|
||||
disabled={!!plan.coupon}
|
||||
onKeyUp={(e) => {
|
||||
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);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<CheckoutPricing
|
||||
currency={plan.currency}
|
||||
period={plan.key}
|
||||
subtotal={plan.price}
|
||||
discountedPrice={plan.discount || 0.0}
|
||||
isRecurringDiscount={plan.isRecurringDiscount}
|
||||
/>
|
||||
<Button variant="secondary" mt={4} px={4} onClick={onPlanChangeRequest}>
|
||||
Change plan
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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) => (
|
||||
<Flex
|
||||
key={field.key}
|
||||
justifyContent="space-between"
|
||||
alignSelf="stretch"
|
||||
mt={1}
|
||||
>
|
||||
<Text variant="body" fontSize="subtitle">
|
||||
{field.label}
|
||||
</Text>
|
||||
<Text
|
||||
variant="body"
|
||||
fontSize="subtitle"
|
||||
color={field.color || "text"}
|
||||
>
|
||||
{field.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
bg={error ? "errorBg" : "shade"}
|
||||
p={5}
|
||||
pt={2}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Icon.Loading size={32} />
|
||||
) : error ? (
|
||||
<>
|
||||
<Text variant="title" color="error" fontWeight="normal">
|
||||
{error}
|
||||
</Text>
|
||||
<Button
|
||||
variant="primary"
|
||||
bg="error"
|
||||
color="static"
|
||||
mt={2}
|
||||
onClick={() => setCoupon()}
|
||||
>
|
||||
Try again
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Text variant="body" mr={1}>
|
||||
Monthly
|
||||
</Text>
|
||||
<Switch
|
||||
checked={plan !== "monthly"}
|
||||
onClick={() => {
|
||||
setPlan((s) => (s === "monthly" ? "yearly" : "monthly"));
|
||||
}}
|
||||
/>
|
||||
<Text variant="body" ml={1}>
|
||||
Yearly
|
||||
</Text>
|
||||
</Flex>
|
||||
{coupon ? (
|
||||
<Flex mb={1} alignItems="center" justifyContent="space-between">
|
||||
<Flex alignItems="center" justifyContent="center">
|
||||
<Text variant="subBody" mr={1}>
|
||||
Coupon:
|
||||
</Text>
|
||||
<Text
|
||||
variant="subBody"
|
||||
fontSize={10}
|
||||
bg="primary"
|
||||
color="static"
|
||||
px={1}
|
||||
py="3px"
|
||||
sx={{ borderRadius: "default" }}
|
||||
>
|
||||
{coupon}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex>
|
||||
<Text
|
||||
sx={{ cursor: "pointer", ":hover": { opacity: 0.8 } }}
|
||||
variant="subBody"
|
||||
color="primary"
|
||||
mr={1}
|
||||
onClick={() => {
|
||||
const code = window.prompt("Enter new coupon code:");
|
||||
setCoupon(code);
|
||||
}}
|
||||
>
|
||||
Change
|
||||
</Text>
|
||||
<Text
|
||||
sx={{ cursor: "pointer", ":hover": { opacity: 0.8 } }}
|
||||
variant="subBody"
|
||||
color="error"
|
||||
onClick={() => {
|
||||
if (
|
||||
window.confirm(
|
||||
"Are you sure you want to remove this coupon?"
|
||||
)
|
||||
) {
|
||||
setCoupon();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<Text
|
||||
sx={{ cursor: "pointer", ":hover": { opacity: 0.8 } }}
|
||||
variant="subBody"
|
||||
color="primary"
|
||||
mr={1}
|
||||
onClick={() => {
|
||||
const code = window.prompt("Enter new coupon code:");
|
||||
setCoupon(code);
|
||||
}}
|
||||
>
|
||||
Add coupon code
|
||||
<Flex justifyContent="space-between" alignSelf="stretch" mt={1}>
|
||||
<Text variant="body" fontSize="heading">
|
||||
Total
|
||||
</Text>
|
||||
<Text variant="body" fontSize="heading" color={"text"} textAlign="end">
|
||||
{formatPrice(
|
||||
currency,
|
||||
subtotal - discountedPrice,
|
||||
isRecurringDiscount || discountedPrice <= 0 ? period : "null"
|
||||
)}
|
||||
<Text variant="heading" fontSize={24} color="primary">
|
||||
Only <MainPricing prices={prices} plan={plan} />
|
||||
<Text fontSize="body">
|
||||
{isRecurringDiscount || discountedPrice <= 0
|
||||
? ""
|
||||
: `then ${formatPrice(currency, subtotal, period)}`}
|
||||
</Text>
|
||||
<RecurringPricing prices={prices} plan={plan} />
|
||||
<Text display="flex" variant="subBody" color="text" mt={1} mb={2}>
|
||||
Cancel anytime. No questions asked.
|
||||
</Text>
|
||||
<Button
|
||||
fontSize="title"
|
||||
fontWeight="bold"
|
||||
onClick={async () => {
|
||||
if (isLoggedIn) {
|
||||
await upgrade(user, coupon, plan);
|
||||
} else {
|
||||
navigate(`/login`, { redirect: `/#/buy/${coupon}` });
|
||||
}
|
||||
props.onCancel();
|
||||
}}
|
||||
>
|
||||
Subscribe to Notesnook Pro
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex> */
|
||||
// </Dialog>
|
||||
// );
|
||||
}
|
||||
export default BuyDialog;
|
||||
|
||||
function MainPricing(props) {
|
||||
const { prices, plan } = props;
|
||||
|
||||
if (prices.withoutDiscount.net !== prices.prices.total)
|
||||
return (
|
||||
<>
|
||||
<del>
|
||||
<Price
|
||||
currency={prices.withoutDiscount.currency}
|
||||
price={prices.withoutDiscount.net}
|
||||
/>
|
||||
</del>{" "}
|
||||
<Price currency={prices.currency} price={prices.prices.total} />
|
||||
{prices.recurring_prices.total === prices.prices.total
|
||||
? ` per ${planToPeriod(plan)}`
|
||||
: ` your first ${planToPeriod(plan)}`}
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
<Price currency={prices.currency} price={prices.prices.total} />
|
||||
{` per ${planToPeriod(plan)}`}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function RecurringPricing(props) {
|
||||
const { prices, plan } = props;
|
||||
|
||||
if (prices.prices.total === prices.recurring_prices.total) return null;
|
||||
return (
|
||||
<Text
|
||||
display="flex"
|
||||
variant="body"
|
||||
mt={1}
|
||||
fontWeight="bold"
|
||||
color="primary"
|
||||
>
|
||||
And then{" "}
|
||||
<Price currency={prices.currency} price={prices.recurring_prices.total} />{" "}
|
||||
every {planToPeriod(plan)} afterwards.
|
||||
</Text>
|
||||
);
|
||||
function formatPrice(currency, price, period, negative = false) {
|
||||
return `${negative ? "-" : ""}${getSymbolFromCurrency(
|
||||
currency
|
||||
)}${price}${formatPeriod(period)}`;
|
||||
}
|
||||
|
||||
function Price(props) {
|
||||
const { currency, price } = props;
|
||||
return (
|
||||
<>
|
||||
{getSymbolFromCurrency(currency)}
|
||||
{price}
|
||||
</>
|
||||
);
|
||||
function formatPeriod(period) {
|
||||
return period === "monthly" ? "/mo" : period === "yearly" ? "/yr" : "";
|
||||
}
|
||||
|
||||
function planToPeriod(plan) {
|
||||
return plan === "monthly" ? "month" : "year";
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
<Flex mt={1} sx={{ position: "relative" }}>
|
||||
<Input
|
||||
data-test-id={props["data-test-id"]}
|
||||
variant={variant}
|
||||
defaultValue={defaultValue}
|
||||
ref={inputRef}
|
||||
autoFocus={autoFocus}
|
||||
|
||||
@@ -7,6 +7,7 @@ class DarkColorSchemeFactory {
|
||||
primary: accent,
|
||||
placeholder: hexToRGB("#ffffff", 0.6),
|
||||
background: "#1f1f1f",
|
||||
bgTransparent: "#1f1f1f99",
|
||||
accent: "#000",
|
||||
bgSecondary: "#2b2b2b",
|
||||
bgSecondaryText: "#A1A1A1",
|
||||
|
||||
@@ -22,7 +22,7 @@ class Default {
|
||||
color: "text",
|
||||
outline: "none",
|
||||
":focus": {
|
||||
boxShadow: "0px 0px 0px 2px var(--primary) inset",
|
||||
boxShadow: "0px 0px 0px 1.5px var(--primary) inset",
|
||||
},
|
||||
":hover:not(:focus)": {
|
||||
boxShadow: "0px 0px 0px 1px var(--dimPrimary) inset",
|
||||
@@ -51,13 +51,13 @@ class Error {
|
||||
constructor() {
|
||||
return {
|
||||
variant: "forms.input",
|
||||
borderColor: "red",
|
||||
":focus": {
|
||||
boxShadow: "0px 0px 0px 1px var(--error) inset",
|
||||
outline: "none",
|
||||
borderColor: "red",
|
||||
":focus": {
|
||||
boxShadow: "0px 0px 0px 1.5px var(--error) inset",
|
||||
},
|
||||
":hover": {
|
||||
borderColor: "darkred",
|
||||
":hover:not(:focus)": {
|
||||
boxShadow: "0px 0px 0px 1px var(--error) inset",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user