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

Commit

Permalink
[CLAIM] Pending claim UI (#2049)
Browse files Browse the repository at this point in the history
* add data prop to addTransaction

* mock testing function and impl

* add claim to transaction state

- add hooks as syntactic sugar

* type claimData

* getIndexes additional RepoClaims type

* claim hooks mock tx edit and helper fn

* claim page - add UI responsive to pending

* styles

* price map fix

* claim page - add UI responsive to pending

* styles

* Use pending claims in unclaimed amount calculation

* revert name and simplify

* 0 if no claiming

* set mock to an acct with claims (testing)

* mod useUserHasSubmittedClaim

* path fix

Co-authored-by: nenadV91 <[email protected]>
  • Loading branch information
W3stside and nenadV91 authored Jan 13, 2022
1 parent fee0156 commit 8a83dd9
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/components/claim/ClaimModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useActiveWeb3React } from '../../hooks/web3'
import { useModalOpen, useToggleSelfClaimModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { useClaimCallback, useUserClaimData, useUserUnclaimedAmount } from 'state/claim/hooks'
import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { CloseIcon, CustomLightSpinner, ExternalLink, TYPE, UniTokenAnimated } from '../../theme'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { ButtonPrimary } from '../Button'
Expand Down
2 changes: 1 addition & 1 deletion src/custom/pages/Claim/ClaimSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function ClaimSummary({ hasClaims, unclaimedAmount }: ClaimSummar
<div>
<ClaimTotal>
<b>Total available to claim</b>
<p>{formatSmart(unclaimedAmount)} vCOW</p>
<p>{formatSmart(unclaimedAmount) ?? 0} vCOW</p>
</ClaimTotal>
</div>
)}
Expand Down
45 changes: 30 additions & 15 deletions src/custom/pages/Claim/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
StepIndicator,
Steps,
TokenLogo,
ClaimRow,
} from 'pages/Claim/styled'
import EligibleBanner from './EligibleBanner'
import {
Expand All @@ -56,6 +57,7 @@ import ClaimAddress from './ClaimAddress'
import CanUserClaimMessage from './CanUserClaimMessage'
import { useClaimDispatchers, useClaimState } from 'state/claim/hooks'
import { ClaimStatus } from 'state/claim/actions'
import { useAllClaimingTransactionIndices } from 'state/enhancedTransactions/hooks'

export default function Claim() {
const { account, chainId } = useActiveWeb3React()
Expand Down Expand Up @@ -117,6 +119,9 @@ export default function Claim() {
const hasClaims = useMemo(() => userClaimData.length > 0, [userClaimData])
const isAirdropOnly = useMemo(() => !hasPaidClaim(userClaimData), [userClaimData])

// get current pending claims set in activities
const indicesSet = useAllClaimingTransactionIndices()

// claim type to currency and price map
const typeToCurrencyMap = useMemo(() => getTypeToCurrencyMap(chainId), [chainId])
const typeToPriceMap = useMemo(() => getTypeToPriceMap(), [])
Expand Down Expand Up @@ -332,33 +337,43 @@ export default function Claim() {
{sortedClaimData.map(({ index, type, amount }) => {
const isFree = isFreeClaim(type)
const currency = typeToCurrencyMap[type] || ''
const vCowPrice = typeToPriceMap[type]
const vCowPrice = typeToPriceMap.get(type)
const parsedAmount = parseClaimAmount(amount, chainId)
const cost = vCowPrice * Number(parsedAmount?.toSignificant(6))
const cost = vCowPrice && vCowPrice * Number(parsedAmount?.toSignificant(6))
const isPendingClaim = indicesSet.has(index)

return (
<tr key={index}>
<ClaimRow
key={index}
isPending={isPendingClaim}
onClick={isPendingClaim ? () => console.log('Claim::Opening Orders panel') : undefined}
>
<td>
{' '}
<label className="checkAll">
<input
onChange={(event) => handleSelect(event, index)}
type="checkbox"
name="check"
checked={isFree || selected.includes(index)}
disabled={isFree}
/>
</label>
{/* User has on going pending claiming transactions? Show the loader */}
{isPendingClaim ? (
<CustomLightSpinner src={Circle} title="Claiming in progress..." alt="loader" size="20px" />
) : (
<label className="checkAll">
<input
onChange={(event) => handleSelect(event, index)}
type="checkbox"
name="check"
checked={isFree || selected.includes(index)}
disabled={isFree}
/>
</label>
)}
</td>
<td>{isFree ? type : `Buy vCOW with ${currency}`}</td>
<td>{isFree ? ClaimType[type] : `Buy vCOW with ${currency}`}</td>
<td width="150px">
<CowProtocolLogo size={16} /> {parsedAmount?.toFixed(0, { groupSeparator: ',' })} vCOW
</td>
<td>{isFree ? '-' : `${vCowPrice} vCoW per ${currency}`}</td>
<td>{isFree || !vCowPrice ? '-' : `${vCowPrice} vCoW per ${currency}`}</td>
<td>{isFree ? <span className="green">Free!</span> : `${cost} ${currency}`}</td>
<td>{type === ClaimType.Airdrop ? 'No' : '4 years (linear)'}</td>
<td>28 days, 10h, 50m</td>
</tr>
</ClaimRow>
)
})}
</tbody>
Expand Down
24 changes: 23 additions & 1 deletion src/custom/pages/Claim/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ export const ClaimTable = styled.div`
}
th {
&:first-child {
display: flex;
align-items: center;
}
position: sticky;
top: 0;
background: transparent;
Expand All @@ -194,18 +199,35 @@ export const ClaimTable = styled.div`
}
td {
display: flex;
align-items: center;
padding-top: 10px;
padding-bottom: 10px;
color: white;
word-break: break-word;
background: var(--color-container-bg);
}
tr > td {
background: var(--color-container-bg);
margin: 0 0 12px;
}
`

export const ClaimRow = styled.tr<{ isPending?: boolean }>`
> td {
background-color: ${({ isPending }) => (isPending ? '#221954' : 'rgb(255 255 255 / 6%)')};
cursor: ${({ isPending }) => (isPending ? 'pointer' : 'initial')};
&:first-child {
border-radius: 8px 0 0 8px;
}
&:last-child {
border-radius: 0 8px 8px 0;
}
}
`

export const ClaimAccount = styled.div`
display: flex;
flex-flow: row nowrap;
Expand Down
61 changes: 57 additions & 4 deletions src/custom/state/claim/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { isAddress } from 'utils'

import { getClaimKey, getClaimsRepoPath, transformRepoClaimsToUserClaims } from 'state/claim/hooks/utils'
import { SupportedChainId } from 'constants/chains'
import { registerOnWindow } from 'utils/misc'
import mockData, { MOCK_INDICES } from './mocks/claimData'
import { getIndexes } from './utils'
import { useAllClaimingTransactionIndices } from 'state/enhancedTransactions/hooks'

export { useUserClaimData } from '@src/state/claim/hooks'

Expand Down Expand Up @@ -68,6 +72,15 @@ export enum ClaimType {
Advisor, // free, with vesting, only on mainnet
}

export type TypeToPriceMapper = Map<ClaimType, number>

// Hardcoded values
export const ClaimTypePriceMap: TypeToPriceMapper = new Map([
[ClaimType.GnoOption, 16.66],
[ClaimType.Investor, 26.66],
[ClaimType.UserOption, 36.66],
])

type RepoClaimType = keyof typeof ClaimType

export const FREE_CLAIM_TYPES: ClaimType[] = [ClaimType.Airdrop, ClaimType.Team, ClaimType.Advisor]
Expand Down Expand Up @@ -182,13 +195,18 @@ export function useUserHasAvailableClaim(account: Account): boolean {
export function useUserUnclaimedAmount(account: string | null | undefined): CurrencyAmount<Token> | undefined {
const { chainId } = useActiveWeb3React()
const claims = useUserAvailableClaims(account)
const pendingIndices = useAllClaimingTransactionIndices()

const vCow = chainId ? V_COW[chainId] : undefined
if (!vCow) return undefined
if (!claims || claims.length === 0) {
return CurrencyAmount.fromRawAmount(vCow, JSBI.BigInt(0))
}

const totalAmount = claims.reduce((acc, claim) => {
// don't add pending
if (pendingIndices.has(claim.index)) return acc

return JSBI.add(acc, JSBI.BigInt(claim.amount))
}, JSBI.BigInt('0'))

Expand Down Expand Up @@ -235,6 +253,14 @@ export function useUserClaims(account: Account): UserClaims | null {
return claimKey ? claimInfo[claimKey] : null
}

// TODO: remove
const createMockTx = (data: number[]) => ({
hash: '0x' + Math.round(Math.random() * 10).toString() + 'AxAFjAhG89G89AfnLK3CCxAfnLKQffQ782G89AfnLK3CCxxx123FF',
summary: `Claimed ${Math.random() * 3337} vCOW`,
claim: { recipient: '0x97EC4fcD5F78cA6f6E4E1EAC6c0Ec8421bA518B7' },
data, // add the claim indices to state
})

/**
* Fetches from contract the deployment timestamp in ms
*
Expand Down Expand Up @@ -330,6 +356,20 @@ export function useClaimCallback(account: string | null | undefined): {
const addTransaction = useTransactionAdder()
const vCowToken = chainId ? V_COW[chainId] : undefined

// TODO: remove
registerOnWindow({
addMockClaimTransactions: (data?: number[]) => {
let finalData: number[] | undefined = data

if (!finalData) {
const mockDataIndices = connectedAccount ? getIndexes(mockData[connectedAccount] || []) : []
finalData = mockDataIndices?.length > 0 ? mockDataIndices : MOCK_INDICES
}

return addTransaction(createMockTx(finalData))
},
})

const claimCallback = useCallback(
async function (claimInput: ClaimInput[]) {
if (
Expand All @@ -356,17 +396,17 @@ export function useClaimCallback(account: string | null | undefined): {

return vCowContract.estimateGas['claimMany'](...args).then((estimatedGas) => {
// Last item in the array contains the call overrides
args[args.length - 1] = {
...args[args.length - 1], // add back whatever is already there
const extendedArgs = _extendFinalArg(args, {
from: connectedAccount, // add the `from` as the connected account
gasLimit: calculateGasMargin(chainId, estimatedGas), // add the estimated gas limit
}
})

return vCowContract.claimMany(...args).then((response: TransactionResponse) => {
return vCowContract.claimMany(...extendedArgs).then((response: TransactionResponse) => {
addTransaction({
hash: response.hash,
summary: `Claimed ${formatSmart(vCowAmount)} vCOW`,
claim: { recipient: account },
data: args[0], // add the claim indices to state
})
return response.hash
})
Expand Down Expand Up @@ -652,3 +692,16 @@ export function useClaimDispatchers() {
export function useClaimState() {
return useSelector((state: AppState) => state.claim)
}

/**
* Extend the Payable optional param
*/
function _extendFinalArg(args: ClaimManyFnArgs, extendedArg: Record<any, any>) {
const lastArg = args.pop()
args.push({
...lastArg, // add back whatever is already there
...extendedArg,
})

return args
}
6 changes: 5 additions & 1 deletion src/custom/state/claim/hooks/mocks/claimData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const mockData: any = {
import { RepoClaimData } from '..'

const mockData: Record<string, RepoClaimData[]> = {
// airdrops + investments
'0xf17aFe5237D982868B8A97424dD79a4A50c36412': [
{
Expand Down Expand Up @@ -145,5 +147,7 @@ const mockData: any = {
// no claims
'0x7C842Bf74359911aEe49bA021014B05265f951c6': [],
}
const mockKeys = Object.keys(mockData)
export const MOCK_INDICES = mockData[mockKeys[0]].map(({ index }) => index)

export default mockData
17 changes: 4 additions & 13 deletions src/custom/state/claim/hooks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { V_COW } from 'constants/tokens'
import {
CLAIMS_REPO,
ClaimType,
ClaimTypePriceMap,
FREE_CLAIM_TYPES,
PAID_CLAIM_TYPES,
RepoClaims,
TypeToPriceMapper,
UserClaims,
} from 'state/claim/hooks/index'

Expand Down Expand Up @@ -95,24 +97,13 @@ export function getTypeToCurrencyMap(chainId: number | undefined): TypeToCurrenc
return map
}

export type TypeToPriceMapper = {
[key: string]: number
}

/**
* Helper function to get vCow price based on claim type and chainId
*
* @param type
*/
export function getTypeToPriceMap(): TypeToPriceMapper {
// Hardcoded values
const map: TypeToPriceMapper = {
[ClaimType.GnoOption]: 16.66,
[ClaimType.Investor]: 26.66,
[ClaimType.UserOption]: 36.66,
}

return map
return ClaimTypePriceMap
}

/**
Expand All @@ -129,7 +120,7 @@ export function isFreeClaim(type: ClaimType): boolean {
*
* @param type
*/
export function getIndexes(data: UserClaims): number[] {
export function getIndexes(data: RepoClaims | UserClaims): number[] {
return data.map(({ index }) => index)
}

Expand Down
2 changes: 2 additions & 0 deletions src/custom/state/enhancedTransactions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { SerializableTransactionReceipt } from '@src/state/transactions/actions'
import { EnhancedTransactionDetails } from './reducer'

type WithChainId = { chainId: number }
type WithData = { data?: any }

export type AddTransactionParams = WithChainId &
WithData &
Pick<
EnhancedTransactionDetails,
'hash' | 'hashType' | 'from' | 'approval' | 'presign' | 'claim' | 'summary' | 'safeTransaction'
Expand Down
Loading

0 comments on commit 8a83dd9

Please sign in to comment.