Skip to content
This repository was archived by the owner on Jun 24, 2022. It is now read-only.

Claim last investment page #2227

Merged
merged 12 commits into from
Jan 20, 2022
33 changes: 7 additions & 26 deletions src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -256,16 +250,3 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
</InvestTokenGroup>
)
}

function _calculatePercentage<C1 extends Currency, C2 extends Currency>(
numerator: CurrencyAmount<C1>,
denominator: CurrencyAmount<C2>
): 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'
}
73 changes: 73 additions & 0 deletions src/custom/pages/Claim/InvestmentFlow/InvestSummaryRow.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<tr>
<td>
{isFree ? (
<b>{ClaimType[type]}</b>
) : (
<>
<TokenLogo symbol={symbol} size={32} />
<CowProtocolLogo size={32} />
<span>
<b>Buy vCOW</b>
<i>with {symbol}</i>
</span>
</>
)}
</td>

<td>
{!isFree && (
<span>
<b>Investment amount:</b>{' '}
<i>
{formattedCost} {symbol} ({percentage}% of available investing opportunity)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michelbio, should we style differently the non 100% cases?
not investing 100% is something that cannot be undone.

I would even omit the percentage if it's 100%.
This could be done in another PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we can review.

</i>
</span>
)}
<span>
<b>Amount to receive:</b>
<i>{formatSmart(vCowAmount) || '0'} vCOW</i>
</span>
</td>

<td>
{!isFree && (
<span>
<b>Price:</b>{' '}
<i>
{formatSmart(price) || '0'} vCoW per {symbol}
</i>
</span>
)}
<span>
<b>Cost:</b> <i>{isFree ? 'Free!' : `${formattedCost} ${symbol}`}</i>
</span>
<span>
<b>Vesting:</b>
<i>{type === ClaimType.Airdrop ? 'No' : '4 years (linear)'}</i>
</span>
</td>
</tr>
)
}
119 changes: 54 additions & 65 deletions src/custom/pages/Claim/InvestmentFlow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useMemo } from 'react'

import {
InvestFlow,
InvestContent,
Expand All @@ -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
Expand Down Expand Up @@ -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]
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need two useMemos right? the first one could already return allClaims. I say this cause we don't use freeClaims selectedClaims anywhere but in this memo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've split because as explained with comments in the code, one section is used for step 1 and 2 while other section is only for step 2.
I don't want to update the step 2 path unnecessarily while on step 1


useEffect(() => {
initInvestFlowData()
Expand Down Expand Up @@ -146,62 +188,9 @@ export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenAppro
</tr>
</thead>
<tbody>
<tr>
<td>
<b>Airdrop</b>
</td>
<td>
<span>
<b>Amount to receive:</b>
<i>13,120.50 vCOW</i>
</span>
</td>

<td>
<span>
<b>Cost:</b> <i>Free!</i>
</span>
<span>
<b>Vesting:</b>
<i>No</i>
</span>
</td>
</tr>

<tr>
<td>
{' '}
<TokenLogo symbol="GNO" size={32} />
<CowProtocolLogo size={32} />
<span>
<b>Buy vCOW</b>
<i>with GNO</i>
</span>
</td>

<td>
<span>
<b>Investment amount:</b> <i>1343 GNO (50% of available investing opportunity)</i>
</span>
<span>
<b>Amount to receive:</b>
<i>13,120.50 vCOW</i>
</span>
</td>

<td>
<span>
<b>Price:</b> <i>2666.6666 vCoW per GNO</i>
</span>
<span>
<b>Cost:</b> <i>0.783375 GNO</i>
</span>
<span>
<b>Vesting:</b>
<i>4 years (linear)</i>
</span>
</td>
</tr>
{allClaims.map((claim) => (
<InvestSummaryRow claim={claim} key={claim.index} />
))}
</tbody>
</table>
</ClaimTable>
Expand All @@ -214,7 +203,7 @@ export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenAppro
</p>
<p>
<b>Can I modify the invested amounts or invest partial amounts later?</b> 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.
</p>
<p>
<b>Important!</b> Please make sure you intend to claim and send vCOW to the mentioned receiving account(s)
Expand Down
20 changes: 16 additions & 4 deletions src/custom/pages/Claim/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Thanks for doing this.

`

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%;
Expand Down
7 changes: 7 additions & 0 deletions src/custom/pages/Claim/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ export type EnhancedUserClaimData = UserClaimData & {
price?: Price<Currency, Currency> | undefined
cost?: CurrencyAmount<Currency> | undefined
}

export type InvestmentAmounts = {
vCowAmount?: CurrencyAmount<Currency>
investmentCost?: CurrencyAmount<Currency>
}

export type ClaimWithInvestmentData = EnhancedUserClaimData & InvestmentAmounts
Loading