From a116ab5dd83ef0fb86721296fb183ad15702ab97 Mon Sep 17 00:00:00 2001 From: Leandro Boscariol Date: Thu, 20 Jan 2022 02:42:09 -0800 Subject: [PATCH] Claim last investment page (#2227) * Refactoring token logo logic * New component InvestmentSummaryRow semi complete * Also filtering free claims to show them on summary page * Dynamically loading claims on investment summary page * Refactored calculatePercentage function into utils * Refactored new utils calculateInvestmentAmounts * Upgradring EnhancedUserClaimData into ClaimWithInvestmentData * Adding vCowAmount, cost and percentage to InvestmentSummaryRow * Renamed paidClaims to selectedClaims as they are a subset only * Refactored free and selected claims * Fixed typo * Further refactoring stuff on InvesmentFlow component Co-authored-by: Leandro --- .../Claim/InvestmentFlow/InvestOption.tsx | 33 ++--- .../Claim/InvestmentFlow/InvestSummaryRow.tsx | 73 +++++++++++ .../pages/Claim/InvestmentFlow/index.tsx | 119 ++++++++---------- src/custom/pages/Claim/styled.ts | 20 ++- src/custom/pages/Claim/types.ts | 7 ++ src/custom/state/claim/hooks/utils.ts | 43 ++++++- 6 files changed, 199 insertions(+), 96 deletions(-) create mode 100644 src/custom/pages/Claim/InvestmentFlow/InvestSummaryRow.tsx diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 0dcc0182f..be85e49bf 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -1,5 +1,4 @@ import { useCallback, useMemo, useState, useEffect } from 'react' -import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import CowProtocolLogo from 'components/CowProtocolLogo' import { InvestTokenGroup, TokenLogo, InvestSummary, InvestInput, InvestAvailableBar } from '../styled' @@ -18,8 +17,7 @@ import { ButtonSize } from 'theme' import Loader from 'components/Loader' import { useErrorModal } from 'hooks/useErrorMessageAndModal' import { tryParseAmount } from 'state/swap/hooks' -import { ONE_HUNDRED_PERCENT, ZERO_PERCENT } from 'constants/misc' -import { PERCENTAGE_PRECISION } from 'constants/index' +import { calculateInvestmentAmounts, calculatePercentage } from 'state/claim/hooks/utils' enum ErrorMsgs { Initial = 'Insufficient balance to cover the selected investment amount. Either modify the investment amount or unselect the investment option to move forward', @@ -61,7 +59,7 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest setTypedValue(value.toExact() || '') setInputError('') - setPercentage(_calculatePercentage(balance, maxCost)) + setPercentage(calculatePercentage(balance, maxCost)) }, [balance, maxCost, noBalance, optionIndex, updateInvestAmount]) // on input field change handler @@ -96,7 +94,7 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest updateInvestAmount({ index: optionIndex, amount: parsedAmount.quotient.toString() }) // update the local state with percentage value - setPercentage(_calculatePercentage(parsedAmount, maxCost)) + setPercentage(calculatePercentage(parsedAmount, maxCost)) }, [balance, maxCost, optionIndex, token, updateInvestAmount] ) @@ -124,14 +122,10 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest } }, [approveCallback, handleCloseError, handleSetError, token?.symbol]) - const vCowAmount = useMemo(() => { - if (!token || !price || !investedAmount) { - return - } - - const investA = CurrencyAmount.fromRawAmount(token, investedAmount) - return price.quote(investA) - }, [investedAmount, price, token]) + const vCowAmount = useMemo( + () => calculateInvestmentAmounts(claim, investedAmount)?.vCowAmount, + [claim, investedAmount] + ) // if its claiming for someone else we will set values to max // if there is not enough balance then we will set an error @@ -256,16 +250,3 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest ) } - -function _calculatePercentage( - numerator: CurrencyAmount, - denominator: CurrencyAmount -): string { - let percentage = denominator.equalTo(ZERO_PERCENT) - ? ZERO_PERCENT - : new Percent(numerator.quotient, denominator.quotient) - if (percentage.greaterThan(ONE_HUNDRED_PERCENT)) { - percentage = ONE_HUNDRED_PERCENT - } - return formatSmart(percentage, PERCENTAGE_PRECISION) || '0' -} diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestSummaryRow.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestSummaryRow.tsx new file mode 100644 index 000000000..a462533ef --- /dev/null +++ b/src/custom/pages/Claim/InvestmentFlow/InvestSummaryRow.tsx @@ -0,0 +1,73 @@ +import { ClaimType } from 'state/claim/hooks' +import { calculatePercentage } from 'state/claim/hooks/utils' +import { TokenLogo } from 'pages/Claim/styled' +import { ClaimWithInvestmentData } from 'pages/Claim/types' +import CowProtocolLogo from 'components/CowProtocolLogo' +import { formatSmart } from 'utils/format' +import { AMOUNT_PRECISION } from 'constants/index' + +export type Props = { claim: ClaimWithInvestmentData } + +export function InvestSummaryRow(props: Props): JSX.Element | null { + const { claim } = props + + const { isFree, type, price, currencyAmount, vCowAmount, cost, investmentCost } = claim + + const symbol = isFree ? '' : (currencyAmount?.currency?.symbol as string) + + const formattedCost = + formatSmart(investmentCost, AMOUNT_PRECISION, { thousandSeparator: true, isLocaleAware: true }) || '0' + const percentage = investmentCost && cost && calculatePercentage(investmentCost, cost) + + return ( + + + {isFree ? ( + {ClaimType[type]} + ) : ( + <> + + + + Buy vCOW + with {symbol} + + + )} + + + + {!isFree && ( + + Investment amount:{' '} + + {formattedCost} {symbol} ({percentage}% of available investing opportunity) + + + )} + + Amount to receive: + {formatSmart(vCowAmount) || '0'} vCOW + + + + + {!isFree && ( + + Price:{' '} + + {formatSmart(price) || '0'} vCoW per {symbol} + + + )} + + Cost: {isFree ? 'Free!' : `${formattedCost} ${symbol}`} + + + Vesting: + {type === ClaimType.Airdrop ? 'No' : '4 years (linear)'} + + + + ) +} diff --git a/src/custom/pages/Claim/InvestmentFlow/index.tsx b/src/custom/pages/Claim/InvestmentFlow/index.tsx index 186b66c14..5c5ce806a 100644 --- a/src/custom/pages/Claim/InvestmentFlow/index.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/index.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo } from 'react' + import { InvestFlow, InvestContent, @@ -8,15 +9,19 @@ import { Steps, ClaimTable, AccountClaimSummary, - TokenLogo, } from 'pages/Claim/styled' +import { InvestSummaryRow } from 'pages/Claim/InvestmentFlow/InvestSummaryRow' + import { ClaimType, useClaimState, useUserEnhancedClaimData, useClaimDispatchers } from 'state/claim/hooks' -import { ClaimCommonTypes, EnhancedUserClaimData } from '../types' import { ClaimStatus } from 'state/claim/actions' -import { useActiveWeb3React } from 'hooks/web3' +import { InvestClaim } from 'state/claim/reducer' +import { calculateInvestmentAmounts } from 'state/claim/hooks/utils' + import { ApprovalState, OptionalApproveCallbackParams } from 'hooks/useApproveCallback' +import { useActiveWeb3React } from 'hooks/web3' + import InvestOption from './InvestOption' -import CowProtocolLogo from 'components/CowProtocolLogo' +import { ClaimCommonTypes, ClaimWithInvestmentData, EnhancedUserClaimData } from '../types' export type InvestOptionProps = { claim: EnhancedUserClaimData @@ -49,15 +54,52 @@ function _claimToTokenApproveData(claimType: ClaimType, tokenApproveData: TokenA } } +function _classifyAndFilterClaimData(claimData: EnhancedUserClaimData[], selected: number[]) { + const paid: EnhancedUserClaimData[] = [] + const free: EnhancedUserClaimData[] = [] + + claimData.forEach((claim) => { + if (claim.isFree) { + free.push(claim) + } else if (selected.includes(claim.index)) { + paid.push(claim) + } + }) + return [free, paid] +} + +function _enhancedUserClaimToClaimWithInvestment( + claim: EnhancedUserClaimData, + investFlowData: InvestClaim[] +): ClaimWithInvestmentData { + const investmentAmount = claim.isFree + ? undefined + : investFlowData.find(({ index }) => index === claim.index)?.investedAmount + + return { ...claim, ...calculateInvestmentAmounts(claim, investmentAmount) } +} + export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenApproveData }: InvestmentFlowProps) { const { account } = useActiveWeb3React() - const { selected, activeClaimAccount, claimStatus, isInvestFlowActive, investFlowStep } = useClaimState() + const { selected, activeClaimAccount, claimStatus, isInvestFlowActive, investFlowStep, investFlowData } = + useClaimState() const { initInvestFlowData } = useClaimDispatchers() const claimData = useUserEnhancedClaimData(activeClaimAccount) - const selectedClaims = useMemo(() => { - return claimData.filter(({ index }) => selected.includes(index)) - }, [claimData, selected]) + // Filtering and splitting claims into free and selected paid claims + // `selectedClaims` are used on step 1 and 2 + // `freeClaims` are used on step 2 + const [freeClaims, selectedClaims] = useMemo( + () => _classifyAndFilterClaimData(claimData, selected), + [claimData, selected] + ) + + // Merge all claims together again, with their investment data for step 2 + const allClaims: ClaimWithInvestmentData[] = useMemo( + () => + freeClaims.concat(selectedClaims).map((claim) => _enhancedUserClaimToClaimWithInvestment(claim, investFlowData)), + [freeClaims, investFlowData, selectedClaims] + ) useEffect(() => { initInvestFlowData() @@ -146,62 +188,9 @@ export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenAppro - - - Airdrop - - - - Amount to receive: - 13,120.50 vCOW - - - - - - Cost: Free! - - - Vesting: - No - - - - - - - {' '} - - - - Buy vCOW - with GNO - - - - - - Investment amount: 1343 GNO (50% of available investing opportunity) - - - Amount to receive: - 13,120.50 vCOW - - - - - - Price: 2666.6666 vCoW per GNO - - - Cost: 0.783375 GNO - - - Vesting: - 4 years (linear) - - - + {allClaims.map((claim) => ( + + ))} @@ -214,7 +203,7 @@ export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenAppro

Can I modify the invested amounts or invest partial amounts later? No. Once you send the transaction, - you cannot increase or reduce the investment. Investment oportunities can only be exercised once. + you cannot increase or reduce the investment. Investment opportunities can only be exercised once.

Important! Please make sure you intend to claim and send vCOW to the mentioned receiving account(s) diff --git a/src/custom/pages/Claim/styled.ts b/src/custom/pages/Claim/styled.ts index 1c462b9e3..7fb480eb7 100644 --- a/src/custom/pages/Claim/styled.ts +++ b/src/custom/pages/Claim/styled.ts @@ -191,12 +191,24 @@ export const TokenLogo = styled.div<{ symbol: string; size: number }>` width: ${({ size }) => `${size}px`}; height: ${({ size }) => `${size}px`}; border-radius: ${({ size }) => `${size}px`}; - background: ${({ symbol, theme }) => - `url(${ - symbol === 'GNO' ? LogoGNO : symbol === 'ETH' ? LogoETH : symbol === 'USDC' ? LogoUSDC : theme.blueShade3 - }) no-repeat center/contain`}; + background: ${({ symbol, theme }) => `url(${_getLogo(symbol) || theme.blueShade3}) no-repeat center/contain`}; ` +function _getLogo(symbol: string) { + switch (symbol.toUpperCase()) { + case 'GNO': + return LogoGNO + case 'USDC': + return LogoUSDC + case 'ETH': + return LogoETH + // TODO: add xDai token logo after merging to develop + // case 'XDAI': return LogoXDAI + default: + return undefined + } +} + export const ClaimSummary = styled.div` display: flex; width: 100%; diff --git a/src/custom/pages/Claim/types.ts b/src/custom/pages/Claim/types.ts index f38a01c22..8b2e7e579 100644 --- a/src/custom/pages/Claim/types.ts +++ b/src/custom/pages/Claim/types.ts @@ -18,3 +18,10 @@ export type EnhancedUserClaimData = UserClaimData & { price?: Price | undefined cost?: CurrencyAmount | undefined } + +export type InvestmentAmounts = { + vCowAmount?: CurrencyAmount + investmentCost?: CurrencyAmount +} + +export type ClaimWithInvestmentData = EnhancedUserClaimData & InvestmentAmounts diff --git a/src/custom/state/claim/hooks/utils.ts b/src/custom/state/claim/hooks/utils.ts index b16cf522e..d7ffb0c7b 100644 --- a/src/custom/state/claim/hooks/utils.ts +++ b/src/custom/state/claim/hooks/utils.ts @@ -1,7 +1,9 @@ -import { CurrencyAmount, Token } from '@uniswap/sdk-core' +import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { SupportedChainId } from 'constants/chains' import { GNO, GpEther, USDC_BY_CHAIN, V_COW } from 'constants/tokens' +import { ONE_HUNDRED_PERCENT, ZERO_PERCENT } from 'constants/misc' +import { PERCENTAGE_PRECISION } from 'constants/index' import { CLAIMS_REPO, @@ -15,6 +17,9 @@ import { VCowPrices, } from 'state/claim/hooks/index' +import { formatSmart } from 'utils/format' +import { EnhancedUserClaimData, InvestmentAmounts } from 'pages/Claim/types' + /** * Helper function to check whether any claim is an investment option * @@ -174,3 +179,39 @@ export function claimTypeToTokenAmount(type: ClaimType, chainId: SupportedChainI return undefined } } + +/** + * Helper function to calculate and return the percentage between 2 CurrencyAmount instances + */ +export function calculatePercentage( + numerator: CurrencyAmount, + denominator: CurrencyAmount +): string { + let percentage = denominator.equalTo(ZERO_PERCENT) + ? ZERO_PERCENT + : new Percent(numerator.quotient, denominator.quotient) + if (percentage.greaterThan(ONE_HUNDRED_PERCENT)) { + percentage = ONE_HUNDRED_PERCENT + } + return formatSmart(percentage, PERCENTAGE_PRECISION) || '0' +} + +/** + * Helper function that calculates vCowAmount (in vCOW) and investedAmount (in investing token) + */ +export function calculateInvestmentAmounts( + claim: Pick, + investedAmount?: string +): InvestmentAmounts { + const { isFree, price, currencyAmount, claimAmount } = claim + + if (isFree || !investedAmount) { + // default to 100% when no investment amount is set + return { vCowAmount: claimAmount, investmentCost: currencyAmount } + } else if (!currencyAmount || !price) { + return {} + } + + const amount = CurrencyAmount.fromRawAmount(currencyAmount.currency, investedAmount) + return { vCowAmount: price.quote(amount), investmentCost: amount } +}