From 1784829d1217cd1d7b408ca8d63d154a533f8118 Mon Sep 17 00:00:00 2001 From: Kevin Mulcrone Date: Mon, 14 Nov 2022 21:43:09 -0700 Subject: [PATCH] feat: update pricing page --- apps/frontend/src/components/AlertBar.tsx | 73 +++---- apps/frontend/src/components/PricingChart.tsx | 191 ++++++++++++++++++ apps/frontend/src/components/index.ts | 1 + apps/frontend/src/components/layout.tsx | 2 +- apps/frontend/src/pages/Purchase/index.tsx | 21 +- apps/frontend/src/pages/Vault/VaultView.tsx | 5 +- apps/frontend/src/utils/license.ts | 11 +- 7 files changed, 251 insertions(+), 53 deletions(-) create mode 100644 apps/frontend/src/components/PricingChart.tsx diff --git a/apps/frontend/src/components/AlertBar.tsx b/apps/frontend/src/components/AlertBar.tsx index 5335d0c4..54bf377c 100644 --- a/apps/frontend/src/components/AlertBar.tsx +++ b/apps/frontend/src/components/AlertBar.tsx @@ -1,21 +1,19 @@ import React, { useContext, useState, useEffect } from 'react'; -import { blockExplorerTransactionURL } from 'unchained-bitcoin'; import { useHistory } from 'react-router-dom'; -import { ExclamationIcon } from '@heroicons/react/outline'; +import { ExternalLinkIcon } from '@heroicons/react/outline'; import { AccountMapContext, ConfigContext, PlatformContext } from 'src/context'; import { getLicenseBannerMessage, licenseTxId } from 'src/utils/license'; -import { getUnchainedNetworkFromBjslibNetwork } from 'src/utils/files'; import { OnChainConfig, VaultConfig } from '@lily/types'; interface Props { - accountConfig: OnChainConfig; + config: OnChainConfig; } -export const AlertBar = ({ accountConfig }: Props) => { - const { config, nodeConfig, currentBitcoinNetwork } = useContext(ConfigContext); +export const AlertBar = ({ config }: Props) => { + const { nodeConfig, currentBitcoinNetwork } = useContext(ConfigContext); const { platform } = useContext(PlatformContext); const { setCurrentAccountId } = useContext(AccountMapContext); const history = useHistory(); @@ -25,11 +23,11 @@ export const AlertBar = ({ accountConfig }: Props) => { useEffect(() => { async function checkLicenseTxConfirmed() { - if ((accountConfig as VaultConfig).license) { + if ((config as VaultConfig).license) { let licenseTxConfirmed = false; if (nodeConfig) { try { - const txId = licenseTxId((accountConfig as VaultConfig).license); + const txId = licenseTxId((config as VaultConfig).license); if (txId) { licenseTxConfirmed = await platform.isConfirmedTransaction(txId); } @@ -38,7 +36,7 @@ export const AlertBar = ({ accountConfig }: Props) => { console.log('AlertBar: Error retrieving license transaction'); } const { message, promptBuy } = getLicenseBannerMessage( - accountConfig as VaultConfig, + config as VaultConfig, licenseTxConfirmed, nodeConfig ); @@ -51,44 +49,39 @@ export const AlertBar = ({ accountConfig }: Props) => { checkLicenseTxConfirmed(); }, [config, nodeConfig, platform]); - if (buyMessage && !!(accountConfig as VaultConfig).license) { + if (buyMessage && !!(config as VaultConfig).license) { return ( -
+
-
- - -

- {/* TODO: change later */} - Upgrade your account! - {buyMessage} -

-
-
- {showBuyButton ? ( - { - setCurrentAccountId(accountConfig.id); - history.push(`/vault/${accountConfig.id}/purchase`); - }} - className='cursor-pointer flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-yellow-600 bg-white hover:bg-yellow-50' - > - Upgrade - - ) : ( +

+ Thank you for using Lily Wallet! +

+

{buyMessage}

+
+
+ {showBuyButton ? ( + + ) : null} + - View Transaction + What is this? + - )} +
diff --git a/apps/frontend/src/components/PricingChart.tsx b/apps/frontend/src/components/PricingChart.tsx new file mode 100644 index 00000000..d4877101 --- /dev/null +++ b/apps/frontend/src/components/PricingChart.tsx @@ -0,0 +1,191 @@ +import React, { useState } from 'react'; +import { CheckIcon } from '@heroicons/react/outline'; +import { Transition } from '@headlessui/react'; + +import { LicenseTiers, LilyOnchainAccount } from '@lily/types'; + +const tiers = [ + { + name: 'Multisig', + href: '/download', + priceAnnual: 100, + description: 'A premium solution for businesses, inheritance, or high net-worth individuals', + features: [ + 'Eliminate single points of failure', + 'Collaborative custody with other stakeholders', + 'Zero KYC or invasive surveillance', + 'Premium support staff on call to assist' + ], + cta: 'Purchase' + } +]; + +interface Props { + clickRenewLicense: (tier: LicenseTiers, currentAccount: LilyOnchainAccount) => Promise; + currentAccount: LilyOnchainAccount; +} + +export const PricingChart = ({ clickRenewLicense, currentAccount }: Props) => { + const [isLoading, setLoading] = useState(''); + + const onLicenseClick = async (tier: LicenseTiers, account: LilyOnchainAccount) => { + if (!isLoading) { + setLoading(tier); + try { + await clickRenewLicense(tier, account); + setLoading(''); + } catch (e) { + setLoading(''); + } + } + }; + + return ( + <> +
+
+
+
+
+ +

+ Pricing +

+
+ +

+ Upgrade to multisig for more robust security and complex custody solutions. +

+
+
+
+
+
+
+
+
+
+
+
+
+

+ {tiers[0].name} +

+
+ {tiers[0].priceAnnual ? ( +
+ ${tiers[0].priceAnnual} + + /year + +
+ ) : ( +
+ Free +
+ )} +

{tiers[0].description}

+
+
+
    + {tiers[0].features.map((feature) => ( +
  • +
    +
    +

    {feature}

    +
  • + ))} +
+
+ +
+
+
+ +
+
+

+ Frequently asked questions +

+
+
+
+ Why do your charge money for multisig security? +
+
+ We assume that users requiring multisig security are holding large + amounts of bitcoin or have a complex custodial arrangement. +
+
+ We ask users who use multisig to purchase a license to help support + development of Lily Wallet. +
+
+ +
+
+ Do you require KYC/AML information for a license? +
+
+ Purchasing a license doesn't require any KYC/AML information from our + customers. +
+
+
+
+ +
+
+
+
+
+
+
+ + ); +}; diff --git a/apps/frontend/src/components/index.ts b/apps/frontend/src/components/index.ts index 4a54708b..a1d39018 100644 --- a/apps/frontend/src/components/index.ts +++ b/apps/frontend/src/components/index.ts @@ -24,6 +24,7 @@ export * from './MnemonicWordsDisplayer'; export * from './NavLinks'; export * from './NoAccountsEmptyState'; export * from './OutsideClick'; +export * from './PricingChart'; export * from './PricingTable'; export * from './PromptPinModal'; export * from './Price'; diff --git a/apps/frontend/src/components/layout.tsx b/apps/frontend/src/components/layout.tsx index b7a44a98..10f466c8 100644 --- a/apps/frontend/src/components/layout.tsx +++ b/apps/frontend/src/components/layout.tsx @@ -42,7 +42,7 @@ export const PageWrapper = ({ children }: Props) => {
)} -
+
{children}
diff --git a/apps/frontend/src/pages/Purchase/index.tsx b/apps/frontend/src/pages/Purchase/index.tsx index 5c27031f..7f1f1e42 100644 --- a/apps/frontend/src/pages/Purchase/index.tsx +++ b/apps/frontend/src/pages/Purchase/index.tsx @@ -6,6 +6,7 @@ import BigNumber from 'bignumber.js'; import { ShieldCheckIcon } from '@heroicons/react/solid'; import { + PricingChart, PricingTable, PurchaseLicenseSuccess, ErrorModal, @@ -208,7 +209,7 @@ const PurchasePage = ({ return ( <> -
+ {/*
Purchase a license @@ -222,12 +223,20 @@ const PurchasePage = ({ Questions? Click here for support. -
- {step === 0 && ( - - )} +
*/} + { + step === 0 && ( + + ) + // + } {step === 1 && licenseResponse && ( <> +
+ + Checkout + +
{finalPsbt && (
), - header: `License for Lily Wallet (${capitalize(licenseTier(licenseResponse))})`, + header: `License for Lily Wallet`, subtext: getValue(finalPsbt.txOutputs[0].value), extraInfo: [ { diff --git a/apps/frontend/src/pages/Vault/VaultView.tsx b/apps/frontend/src/pages/Vault/VaultView.tsx index e621099f..603b6187 100644 --- a/apps/frontend/src/pages/Vault/VaultView.tsx +++ b/apps/frontend/src/pages/Vault/VaultView.tsx @@ -12,7 +12,7 @@ import { TooltipPayload } from 'recharts'; -import { ChartEmptyState, Modal, Unit } from 'src/components'; +import { ChartEmptyState, Modal, Unit, AlertBar } from 'src/components'; import RecentTransactions from './RecentTransactions'; import { RescanModal } from './RescanModal'; @@ -70,7 +70,7 @@ interface Props { } const VaultView = ({ currentAccount, nodeConfig, toggleRefresh }: Props) => { - const { currentBalance, transactions } = currentAccount; + const { currentBalance, transactions, config } = currentAccount; const [modalIsOpen, setModalIsOpen] = useState(false); const [modalContent, setModalContent] = useState(null); const transactionsCopyForChart = [...transactions]; @@ -188,6 +188,7 @@ const VaultView = ({ currentAccount, nodeConfig, toggleRefresh }: Props) => {
) : null} + { if (!b.status.confirmed && !a.status.confirmed) { diff --git a/apps/frontend/src/utils/license.ts b/apps/frontend/src/utils/license.ts index f69cbcdf..fc0547a7 100644 --- a/apps/frontend/src/utils/license.ts +++ b/apps/frontend/src/utils/license.ts @@ -90,7 +90,7 @@ export const getLicenseBannerMessage = ( if (isFreeTrialLicense(vault.license)) { if (isExpiredLicense(vault.license, nodeConfig)) { return { - message: `The Lily free trial for ${vault.name} has expired. Please buy a license to continue using ${vault.name}.`, + message: `Your 30 day free trial for ${vault.name} has expired. Please purchase a license to help support Lily Wallet.`, promptBuy: true }; } else { @@ -98,7 +98,7 @@ export const getLicenseBannerMessage = ( message: `${vault.name} is using a free trial version of Lily. The trial will expire in ${ licenseExpires(vault.license) - nodeConfig.blocks } blocks (approx. ${licenseExpireAsDate(vault.license, nodeConfig).fromNow()})`, - promptBuy: true + promptBuy: false }; } } else if (!isValidLicenseSignature(vault.license)) { @@ -107,13 +107,16 @@ export const getLicenseBannerMessage = ( promptBuy: true }; } else if (isExpiredLicense(vault.license, nodeConfig)) { - return { message: `${vault.name}'s license has expired!`, promptBuy: true }; + return { + message: `${vault.name}'s license has expired! Please renew your license.`, + promptBuy: true + }; } else if (isAlmostExpiredLicense(vault.license, nodeConfig)) { return { message: `${vault.name}'s license will expire in ${ licenseExpires(vault.license) - nodeConfig.blocks } blocks (approx. ${licenseExpireAsDate(vault.license, nodeConfig).fromNow()})`, - promptBuy: true + promptBuy: false }; } else if (!txConfirmed && licenseTier(vault.license) !== 'free') { return {