From dc3be344ce378803ee23d252f48491888db01ba3 Mon Sep 17 00:00:00 2001 From: devinxl Date: Tue, 14 Nov 2023 16:40:35 +0800 Subject: [PATCH 1/2] feat(dcellar-web-ui): add large amount limit and opt pricing page --- .../components/Calculator.tsx | 131 ++++++------------ .../pricing-calculator/components/Common.tsx | 28 +++- .../pricing-calculator/components/FAQ.tsx | 71 ++++++++-- .../components/PricingCard.tsx | 1 - .../components/SizeMenu.tsx | 1 - .../src/modules/pricing-calculator/index.tsx | 40 +++--- .../src/modules/wallet/TransferIn/index.tsx | 2 + .../src/modules/wallet/TransferOut/index.tsx | 2 + .../wallet/components/LargeAmountTip.tsx | 36 +++++ .../dcellar-web-ui/src/store/slices/global.ts | 2 +- 10 files changed, 192 insertions(+), 122 deletions(-) create mode 100644 apps/dcellar-web-ui/src/modules/wallet/components/LargeAmountTip.tsx diff --git a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Calculator.tsx b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Calculator.tsx index 60de268b..9321a316 100644 --- a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Calculator.tsx +++ b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Calculator.tsx @@ -4,16 +4,7 @@ import { CRYPTOCURRENCY_DISPLAY_PRECISION, DECIMAL_NUMBER } from '@/modules/wall import { TStoreFeeParams } from '@/store/slices/global'; import { getQuotaNetflowRate, getStoreNetflowRate } from '@/utils/payment'; import { getUTC0Month } from '@/utils/time'; -import { - Box, - Divider, - Flex, - Link, - Loading, - Text, - useDisclosure, - useMediaQuery, -} from '@totejs/uikit'; +import { Box, Divider, Flex, Loading, Text, useDisclosure, useMediaQuery } from '@totejs/uikit'; import React, { useMemo, useState } from 'react'; import { FeeItem } from './FeeItem'; import { SizeMenu } from './SizeMenu'; @@ -25,11 +16,13 @@ import { PriceResponsiveContainer } from '..'; import { smMedia } from '@/modules/responsive'; import { currencyFormatter } from '@/utils/formatter'; import { BN } from '@/utils/math'; +import { JumpLink } from './Common'; type CalculatorProps = { storeParams: TStoreFeeParams; bnbPrice: string; - gasFee: string; + onOpenKey: (key: number) => void; + // gasFee: string; }; const formatInput = (value: string) => { @@ -45,7 +38,7 @@ export const displayUsd = (fee: string, bnbPrice: string) => { .toString(DECIMAL_NUMBER), ); }; -export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) => { +export const Calculator = ({ storeParams, bnbPrice, onOpenKey }: CalculatorProps) => { const [isMobile] = useMediaQuery('(max-width: 767px)'); const TOKEN_SYMBOL = 'BNB'; const { isOpen, onClose, onToggle } = useDisclosure(); @@ -61,7 +54,7 @@ export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) = size: '', unit: 'GB', }); - const [gasTimes, setGasTimes] = useState(''); + // const [gasTimes, setGasTimes] = useState(''); const [storageTime, setStorageTime] = useState(TimeOptions[0]); const [customStorageTime, setCustomStorageTime] = useState(TimeOptions[2]); const sizes = Object.keys(Sizes); @@ -114,12 +107,12 @@ export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) = .toString(); }, [quotaSize, storeParams]); - const totalGasFee = useMemo(() => { - return BN(gasTimes || 0) - .times(gasFee) - .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) - .toString(); - }, [gasFee, gasTimes]); + // const totalGasFee = useMemo(() => { + // return BN(gasTimes || 0) + // .times(gasFee) + // .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) + // .toString(); + // }, [gasFee, gasTimes]); const costs = useMemo(() => { let storeTime = storageTime.id === 'custom' @@ -155,17 +148,17 @@ export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) = .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) .toString(); - const timesByDay = BN(gasTimes || 0).dividedBy(30); - const storeDays = BN(storeTime).dividedBy(24 * 60 * 60); - const totalGasCost = BN(timesByDay || 0) - .times(gasFee) - .times(storeDays) - .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) - .toString(); + // const timesByDay = BN(gasTimes || 0).dividedBy(30); + // const storeDays = BN(storeTime).dividedBy(24 * 60 * 60); + // const totalGasCost = BN(timesByDay || 0) + // .times(gasFee) + // .times(storeDays) + // .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) + // .toString(); + // .plus(totalGasCost) const totalCost = BN(totalStorageCost) .plus(totalQuotaCost) - .plus(totalGasCost) .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) .toString(); @@ -178,15 +171,15 @@ export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) = return { totalStorageCost, totalQuotaCost, - totalGasCost, + // totalGasCost, totalCost, averageMonthCost, }; }, [ customStorageTime.unit, customStorageTime.value, - gasFee, - gasTimes, + // gasFee, + // gasTimes, quotaSize.size, quotaSize.unit, storageSize.size, @@ -239,7 +232,6 @@ export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) = }, }} > - {/* */} Check out  - { - const anchor = '#faq'; - let anchorElement = document.getElementById(anchor); - if (anchorElement) { - anchorElement.scrollIntoView({ block: 'start', behavior: 'smooth' }); - } - }} - > + the Storage Fee Formula - + . } @@ -359,15 +341,6 @@ export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) = - - Each bucket has a one-time free quota offered by SP. - + <> + Learn More about{' '} + + Quota + . + } /> - How much monthly download quota do you require? + + How much network traffic will cost for your download and view activities within one + month?{' '} + - - + {/* - - + */} {TimeOptions.map((item, index) => ( - <> + {item.id !== 'custom' && ( - // setStorageTime(item)} - // > - // {item.title} - // )} - + ))} @@ -675,14 +628,14 @@ export const Calculator = ({ storeParams, bnbPrice, gasFee }: CalculatorProps) = fee={costs.totalQuotaCost} bnbPrice={bnbPrice} /> - 1 ? 's' : ''}/month`} fee={costs.totalGasCost} bnbPrice={bnbPrice} - /> + /> */} *1 month=30 day diff --git a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Common.tsx b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Common.tsx index 76f9b29b..20d672a3 100644 --- a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Common.tsx +++ b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/Common.tsx @@ -1,5 +1,5 @@ import { smMedia } from '@/modules/responsive'; -import { TextProps, Text } from '@totejs/uikit'; +import { TextProps, Text, Link, LinkProps } from '@totejs/uikit'; export const H1 = ({ children, ...restProps }: TextProps) => ( ( {children} ); + +type JumpLinkProps = LinkProps & { + id: string; + openKey: number; + onOpenKey: (key: number) => void; +}; +export const JumpLink = ({ id, children, openKey, onOpenKey, ...restProps }: JumpLinkProps) => { + return ( + { + const anchor = id; + const rect = document.getElementById(anchor)?.getBoundingClientRect(); + onOpenKey(openKey); + window.scrollTo({ + top: (rect?.top || 0) + document.documentElement.scrollTop - 65, + behavior: 'smooth', + }); + }} + {...restProps} + > + {children} + + ); +}; diff --git a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/FAQ.tsx b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/FAQ.tsx index fa685280..c72270f8 100644 --- a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/FAQ.tsx +++ b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/FAQ.tsx @@ -1,5 +1,6 @@ import { Box, + Flex, QAccordion, QAccordionButton, QAccordionIcon, @@ -48,8 +49,8 @@ const BillingFormula = () => { id: 1, name: 'Storage Fee', value: ( - <> - + + Fee = sum(ChargedSize) * (PrimaryStorePrice + SecondaryStorePrice*SecondarySPNumber) * (1+Validator Tax Rate) * ReserveTime @@ -59,7 +60,11 @@ const BillingFormula = () => { Validator Tax Rate = 1% - + + ChargeSize ≥ Total Storage Size (For data object smaller than 128K, it will be charged + as 128K) + + ), }, { @@ -92,13 +97,16 @@ const BillingFormula = () => { > ); }; -export const FAQ = () => { +type FAQProps = { openKeys: number[]; toggleOpenKeys: (key: number) => void }; + +export const FAQ = ({ openKeys, toggleOpenKeys }: FAQProps) => { const data = [ { question: Billing Formula, + id: '#billing_formula', answer: ( <> - + In Greenfield, Besides transaction fee, users are required to pay two kinds of storage service fees: storage fee and download quota fee. These storage service fees are charged by Storage Providers (SPs) in a steam payment. Users need to prelock an amount of @@ -110,19 +118,26 @@ export const FAQ = () => { }, { question: 'What is Charged Size?', + id: '#charged_size', answer: ( - - The ChargeSize is calculated from the object's payload size, if the payload size is less - than 128k then ChargeSize is 128k, otherwise ChargeSize is equal to payload size. + + + In general, charge size is slightly larger than the real storage size. + + + ChargeSize is calculated from the object's payload size, if the payload size is less + than 128k then ChargeSize is 128k, otherwise ChargeSize is equal to payload size. + If Data Size < 128K, ChargedSize = 128K; else, ChargedSize = Data Size If object is an empty folder, ChargedSize = 128K - + ), }, { question: 'What is Primary/Secondary Store Price?', + id: '#store_price', answer: ( Every SP can set their own suggested store price and read price via on-chain transactions. @@ -143,6 +158,7 @@ export const FAQ = () => { }, { question: 'What is Validator Tax Rate?', + id: '#tax_rate', answer: ( For each data related operation on Greenfield, validators can get some rewards for @@ -152,8 +168,32 @@ export const FAQ = () => { ), }, + { + question: 'What is Download Quota?', + id: '#download_quota', + answer: ( + + + Each download operation will consume Download Quota, which is related to the data + object's size. + + + For each bucket, you are granted a free, one-time download quota from the storage + provider you have chosen. You can find in the above sector how much free quota each + storage provider gives. + + + You can upgrade your bucket monthly quota to get more download quota. After your free + quota is used out, Greenfield will start to use the download quota you bought. If your + purchased monthly download quota does not use out before the end of the month, your + monthly quota will be expired. + + + ), + }, { question: 'What is Read Price?', + id: '#read_price', answer: ( A storage provider can update its free read quote, suggested primary store price and read @@ -164,6 +204,7 @@ export const FAQ = () => { }, { question: 'What is Reserve Time?', + id: '#reserve_time', answer: ( The storage fee will be charged on Greenfield in a steam payment style. The fees are paid @@ -179,10 +220,16 @@ export const FAQ = () => {

FAQ

- + {data.map((item, index) => ( - - + + toggleOpenKeys(index)} + h={56} + fontSize={16} + fontWeight={600} + px={0} + > {item.question} diff --git a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/PricingCard.tsx b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/PricingCard.tsx index fb1c24aa..6054b9d8 100644 --- a/apps/dcellar-web-ui/src/modules/pricing-calculator/components/PricingCard.tsx +++ b/apps/dcellar-web-ui/src/modules/pricing-calculator/components/PricingCard.tsx @@ -102,7 +102,6 @@ export const PricingCard = ({ storeParams }: PricingCardProps) => { {({ isOpen }) => ( <> ( <> { +export const PriceCalculator = () => { const [sps, setSps] = useState([]); - const [gasFee, setGasFee] = useState(DEFAULT_GAS_FEE); + const [openKeys, setOpenKeys] = useState([]); + const toggleOpenKeys = (key: number) => { + if (openKeys.includes(key)) { + return setOpenKeys(openKeys.filter(item => item !== key)) + } + setOpenKeys([...openKeys, key]); + } + const onOpenKey = (key: number) => { + setOpenKeys(Array.from(new Set([key]))) + } const mainnetStoreFeeParams = useAppSelector(selectMainnetStoreFeeParams); const bnbPrice = useAppSelector(selectBnbPrice); - useAsyncEffect(async () => { - const [gasFees, error] = await getGasFees('mainnet'); - const gasLimit = - gasFees?.msgGasParams.find((item) => item.msgTypeUrl === DEFAULT_TX_TYPE)?.fixedType?.fixedGas - .low || DEFAULT_GAS_LIMIT; - const gasFee = +GAS_PRICE * gasLimit; - setGasFee(gasFee + ''); - }, []); + // const [gasFee, setGasFee] = useState(DEFAULT_GAS_FEE); + // useAsyncEffect(async () => { + // const [gasFees, error] = await getGasFees('mainnet'); + // const gasLimit = + // gasFees?.msgGasParams.find((item) => item.msgTypeUrl === DEFAULT_TX_TYPE)?.fixedType?.fixedGas + // .low || DEFAULT_GAS_LIMIT; + // const gasFee = +GAS_PRICE * gasLimit; + // setGasFee(gasFee + ''); + // }, []); useAsyncEffect(async () => { const sps = await getStorageProviders('mainnet'); const spMeta = await getSpMeta('mainnet'); @@ -109,11 +115,11 @@ export const PriceCalculator = ({pageProps}: any) => { }} > - + - + diff --git a/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx b/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx index ff551de6..b47e9fa6 100644 --- a/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx +++ b/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx @@ -26,6 +26,7 @@ import { InternalRoutePaths } from '@/utils/constant'; import { removeTrailingSlash } from '@/utils/string'; import { broadcastFault } from '@/facade/error'; import { Faucet } from '../components/Faucet'; +import { LargeAmountTip } from '../components/LargeAmountTip'; interface TransferInProps {} @@ -228,6 +229,7 @@ export const TransferIn = memo(function TransferIn() { gaClickSubmitName="dc.wallet.transferin.transferin_btn.click" gaClickSwitchName="dc.wallet.transferin.switch_network.click" /> + (function TransferOut() { gaClickSubmitName="dc.wallet.transferout.transferout_btn.click" gaClickSwitchName="dc.wallet.transferout.switch_network.click" /> + { + const { transType } = useAppSelector((root) => root.wallet); + const curInfo = WalletOperationInfos[transType]; + const { chain } = useNetwork(); + const isRight = useMemo(() => { + return isRightChain(chain?.id, curInfo?.chainId); + }, [chain?.id, curInfo?.chainId]); + + if (formError || !['transfer_in', 'transfer_out'].includes(transType) || Number(amount) < LARGE_TRANSFER_AMOUNT || !isRight) { + return null; + } + + return ( + + + + {LARGE_TRANSFER_WAIT_TIME}-hour wait for cross chain transfer of {LARGE_TRANSFER_AMOUNT}+ BNB. + + + ); +}; diff --git a/apps/dcellar-web-ui/src/store/slices/global.ts b/apps/dcellar-web-ui/src/store/slices/global.ts index e2a9700f..5e3b4a95 100644 --- a/apps/dcellar-web-ui/src/store/slices/global.ts +++ b/apps/dcellar-web-ui/src/store/slices/global.ts @@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { BnbPriceInfo, getBnbPrice, getDefaultBnbInfo } from '@/facade/common'; import { AppDispatch, AppState, GetState } from '@/store'; import { QueryMsgGasParamsResponse } from '@bnb-chain/greenfield-cosmos-types/cosmos/gashub/v1beta1/query'; -import { find, isEmpty, keyBy } from 'lodash-es'; +import { find, keyBy } from 'lodash-es'; import { setupListObjects, updateObjectStatus } from '@/store/slices/object'; import { getSpOffChainData } from '@/store/slices/persist'; import { VisibilityType } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; From 57816b8e1004ddd0a28e799e144a7126e831e27e Mon Sep 17 00:00:00 2001 From: devinxl Date: Tue, 14 Nov 2023 17:33:56 +0800 Subject: [PATCH 2/2] chore(dcellar-web-ui): update LARGET_TRANSFER_AMOUNT to 1000 --- .../src/modules/wallet/components/LargeAmountTip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dcellar-web-ui/src/modules/wallet/components/LargeAmountTip.tsx b/apps/dcellar-web-ui/src/modules/wallet/components/LargeAmountTip.tsx index e6c81cee..f5bc9138 100644 --- a/apps/dcellar-web-ui/src/modules/wallet/components/LargeAmountTip.tsx +++ b/apps/dcellar-web-ui/src/modules/wallet/components/LargeAmountTip.tsx @@ -10,7 +10,7 @@ type Props = { amount: string; formError: boolean; } -const LARGE_TRANSFER_AMOUNT = 0.5; +const LARGE_TRANSFER_AMOUNT = 1000; const LARGE_TRANSFER_WAIT_TIME = 12; export const LargeAmountTip = ({ amount, formError}: Props) => {