From af68256bf096242bcf87fdb01944b5fb04c28c61 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 16 Jan 2022 18:35:16 +0000 Subject: [PATCH 1/6] [Claim - ClaimTable - Pending] - Show loader and minor style change (#2162) * ClaimsTable: show loader on pending/block checkbox * larger vCOW amt font size * input padding --- src/custom/pages/Claim/ClaimsTable.tsx | 20 +++++++++++++------- src/custom/pages/Claim/styled.ts | 3 ++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/custom/pages/Claim/ClaimsTable.tsx b/src/custom/pages/Claim/ClaimsTable.tsx index 679b0e571..b8e0bf359 100644 --- a/src/custom/pages/Claim/ClaimsTable.tsx +++ b/src/custom/pages/Claim/ClaimsTable.tsx @@ -7,6 +7,8 @@ import { ClaimStatus } from 'state/claim/actions' import { formatSmart } from 'utils/format' import { EnhancedUserClaimData } from './types' import { useAllClaimingTransactionIndices } from 'state/enhancedTransactions/hooks' +import { CustomLightSpinner } from 'theme' +import Circle from 'assets/images/blue-loader.svg' type ClaimsTableProps = { handleSelectAll: (event: React.ChangeEvent) => void @@ -54,13 +56,17 @@ const ClaimsTableRow = ({ {' '} diff --git a/src/custom/pages/Claim/styled.ts b/src/custom/pages/Claim/styled.ts index 14a27bfd4..df5a260ba 100644 --- a/src/custom/pages/Claim/styled.ts +++ b/src/custom/pages/Claim/styled.ts @@ -474,7 +474,7 @@ export const ClaimTotal = styled.div` > p { margin: 0; - font-size: 24px; + font-size: 30px; font-weight: bold; } ` @@ -547,6 +547,7 @@ export const EligibleBanner = styled.div` export const InputField = styled.div` padding: 18px; + padding-left: 36px; border-radius: var(--border-radius); border: ${({ theme }) => theme.currencyInput?.border}; color: ${({ theme }) => theme.text1}; From 00195a958b44d5744cd68dc793a703ffee131d49 Mon Sep 17 00:00:00 2001 From: maria Date: Mon, 17 Jan 2022 16:21:17 +0300 Subject: [PATCH 2/6] new progressbar component of claim branch && more values available (#2060) # Summary Fixes #2008 New Progress Bar component was created, values can be set by clicking the numbers at the top of the bar or via dragging the bar or clicking at any bar point. ![image](https://user-images.githubusercontent.com/25584298/148546035-98851851-a95b-41a3-a611-4a2b5a31d461.png) # To Test 1. Open the page `Profile` The bar is at the bottom of the form --- src/custom/components/ProgressBar/index.tsx | 62 ++++++++++++++++ src/custom/components/ProgressBar/styled.tsx | 77 ++++++++++++++++++++ src/custom/pages/Profile/index.tsx | 4 + 3 files changed, 143 insertions(+) create mode 100644 src/custom/components/ProgressBar/index.tsx create mode 100644 src/custom/components/ProgressBar/styled.tsx diff --git a/src/custom/components/ProgressBar/index.tsx b/src/custom/components/ProgressBar/index.tsx new file mode 100644 index 000000000..75d62f9da --- /dev/null +++ b/src/custom/components/ProgressBar/index.tsx @@ -0,0 +1,62 @@ +import { ProgressBarWrap, ProgressContainer, Progress, Label, FlexWrap, HiddenRange, ProgressVal } from './styled' + +interface ProgressBarProps { + percentage: number // between 0 - 100 + onPercentageClick: (percentage: number) => void +} + +export function ProgressBar({ percentage, onPercentageClick }: ProgressBarProps) { + const statPercentages = [ + { + value: 0, + label: '0%', + }, + { + value: 25, + label: '25%', + }, + { + value: 50, + label: '50%', + }, + { + value: 75, + label: '75%', + }, + { + value: 100, + label: '100%', + }, + ] + const minVal = statPercentages[0].value + const maxVal = statPercentages[statPercentages.length - 1].value + + if (percentage > 100) { + percentage = 100 + } else if (percentage < 0) { + percentage = 0 + } + + return ( + + + {statPercentages.map((item, index) => ( + + ))} + + onPercentageClick(parseFloat(e.target.value))} + min={minVal} + max={maxVal} + value={percentage} + type="range" + /> + + {percentage}% + + + + ) +} diff --git a/src/custom/components/ProgressBar/styled.tsx b/src/custom/components/ProgressBar/styled.tsx new file mode 100644 index 000000000..b0869fcfa --- /dev/null +++ b/src/custom/components/ProgressBar/styled.tsx @@ -0,0 +1,77 @@ +import styled from 'styled-components/macro' +import * as CSS from 'csstype' +import { FlexWrap as FlexWrapMod } from 'pages/Profile/styled' +import { transparentize } from 'polished' + +export const FlexWrap = styled(FlexWrapMod)` + max-width: 100%; + align-items: flex-end; +` + +export const ProgressBarWrap = styled(FlexWrapMod)` + max-width: 500px; //optional + padding-top: 40px; + position: relative; +` + +export const ProgressContainer = styled.div` + background-color: ${({ theme }) => transparentize(0.61, theme.text1)}; + height: 24px; + width: 100% !important; + position: relative; + overflow: hidden; + border-radius: 10px; +` + +export const HiddenRange = styled.input` + width: 100%; + background-color: transparent; + z-index: 3; + position: relative; + opacity: 0; +` + +export const Progress = styled.div>` + background-color: ${({ theme }) => theme.primary1}; + width: 100%; + max-width: ${(props) => props.percentage}%; + position: absolute; + top: 0; + left: 0; + bottom: 0; + transition: max-width 0.2s; +` + +export const ProgressVal = styled.span` + display: inline-block; + color: ${({ theme }) => theme.text1}; + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + font-weight: bold; + font-size: 16px; +` + +export const Label = styled.a>` + cursor: pointer; + position: absolute; + font-size: 12px; + color: ${({ theme }) => theme.text1}; + top: 10px; + left: ${(props) => props.position}%; + transform: translateX(-50%); + font-weight: bold; + text-decoration: underline; + + &:first-child { + transform: none; + } + &:nth-last-child(2) { + transform: translateX(-100%); + } + + &:hover { + text-decoration: none; + } +` diff --git a/src/custom/pages/Profile/index.tsx b/src/custom/pages/Profile/index.tsx index 8a4faa4ec..65428893a 100644 --- a/src/custom/pages/Profile/index.tsx +++ b/src/custom/pages/Profile/index.tsx @@ -33,6 +33,8 @@ import AffiliateStatusCheck from 'components/AffiliateStatusCheck' import { useHasOrders } from 'api/gnosisProtocol/hooks' import CowProtocolLogo from 'components/CowProtocolLogo' import { Title } from 'components/Page' +import { ProgressBar } from 'components/ProgressBar' +import { useState } from 'react' import { useTokenBalance } from 'state/wallet/hooks' import { V_COW } from 'constants/tokens' import { AMOUNT_PRECISION } from 'constants/index' @@ -44,6 +46,7 @@ export default function Profile() { const lastUpdated = useTimeAgo(profileData?.lastUpdated) const isTradesTooltipVisible = account && chainId == 1 && !!profileData?.totalTrades const hasOrders = useHasOrders(account) + const [percentage, setPercentage] = useState(0) const vCowBalance = useTokenBalance(account || undefined, chainId ? V_COW[chainId] : undefined) @@ -217,6 +220,7 @@ export default function Profile() { {!account && console.log('TODO')} />} + ) From 2f2f1cb9410db694c07c0a517755ef952dd2da52 Mon Sep 17 00:00:00 2001 From: Michel <31534717+biocom@users.noreply.github.com> Date: Mon, 17 Jan 2022 17:42:29 +0100 Subject: [PATCH 3/6] Wording change and CSS cleanup. (#2170) * Wording change and CSS cleanup. * Style update + hide approve button for ETH. (#2171) * Style update + hide approve button for ETH. * Style updates + organise invest options (#2172) --- src/custom/pages/Claim/ClaimsTable.tsx | 4 +- .../Claim/InvestmentFlow/InvestOption.tsx | 23 ++++++---- src/custom/pages/Claim/styled.ts | 46 +++++++++---------- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/custom/pages/Claim/ClaimsTable.tsx b/src/custom/pages/Claim/ClaimsTable.tsx index b8e0bf359..b5c7f50c9 100644 --- a/src/custom/pages/Claim/ClaimsTable.tsx +++ b/src/custom/pages/Claim/ClaimsTable.tsx @@ -74,8 +74,8 @@ const ClaimsTableRow = ({ - Buy vCOW - {isFree ? ClaimType[type] : `with ${currencyAmount?.currency?.symbol}`} + {isFree ? ClaimType[type] : 'Buy vCOW'} + {!isFree && with {currencyAmount?.currency?.symbol}} {formatSmart(claimAmount) || 0} vCOW diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 1064d2985..2fe494152 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -95,11 +95,11 @@ export default function InvestOption({ approveData, updateInvestAmount, claim }: return (
+

Buy vCOW with {currencyAmount?.currency?.symbol}

-

Buy vCOW with {currencyAmount?.currency?.symbol}

@@ -110,6 +110,14 @@ export default function InvestOption({ approveData, updateInvestAmount, claim }: {formatSmart(price)} vCoW per {currencyAmount?.currency?.symbol} + + + Max. investment available{' '} + + {formatSmart(maxCost) || '0'} {currencyAmount?.currency?.symbol} + + + Token approval {approveData ? ( @@ -131,7 +139,7 @@ export default function InvestOption({ approveData, updateInvestAmount, claim }: )} {/* Approve button - @biocom styles for this found in ./styled > InputSummary > ${ButtonPrimary}*/} - {approveState !== ApprovalState.APPROVED && ( + {approveData && approveState !== ApprovalState.APPROVED && ( {approving || approveState === ApprovalState.PENDING ? ( - ) : ( + ) : approveData ? ( Approve {currencyAmount?.currency?.symbol} - )} + ) : null} )} - - Max. investment available{' '} - - {formatSmart(maxCost) || '0'} {currencyAmount?.currency?.symbol} - - + Available investment used diff --git a/src/custom/pages/Claim/styled.ts b/src/custom/pages/Claim/styled.ts index df5a260ba..eaeca8ebc 100644 --- a/src/custom/pages/Claim/styled.ts +++ b/src/custom/pages/Claim/styled.ts @@ -5,13 +5,6 @@ import { ButtonPrimary, ButtonSecondary } from 'components/Button' import { transparentize, darken } from 'polished' export const PageWrapper = styled.div` - --color-tl: #141722; - --color-tr: #3b4052; - --color-grey: rgb(151, 151, 151); - --color-orange: rgb(237, 104, 52); - --color-container-bg: rgb(255 255 255 / 6%); - --color-container-bg2: rgb(255 255 255 / 12%); - --color-container-bg3: rgb(255 255 255 / 25%); --border-radius: 56px; --border-radius-small: 16px; display: flex; @@ -47,7 +40,6 @@ export const PageWrapper = styled.div` cursor: pointer; border: 1px solid var(--bc, var(--border)); background: var(--b, var(--background)); - transition: background .2s, border-color .2s, box-shadow .2s; &:after { content: ''; @@ -55,18 +47,16 @@ export const PageWrapper = styled.div` left: 0; top: 0; position: absolute; - transition: transform var(.2s) var(--d-t-e, ease), opacity var(.2s); } &:checked { --b: var(--active); --bc: var(--active); - --d-t-e: cubic-bezier(.2, .85, .32, 1.2); } &:disabled { cursor: not-allowed; - opacity: .7; + opacity: .5; &:checked { } @@ -167,10 +157,6 @@ export const PageWrapper = styled.div` opacity: 0; transform: scale(var(--s, .7)); } - - &:checked { - --s: .5; - } } } @@ -310,7 +296,7 @@ export const ClaimTable = styled.div` border-collapse: collapse; min-width: 100%; font-size: 16px; - grid-template-columns: min-content auto max-content auto; + grid-template-columns: min-content auto auto auto; } thead, @@ -335,7 +321,7 @@ export const ClaimTable = styled.div` background: transparent; text-align: left; font-weight: normal; - font-size: 13px; + font-size: 15px; color: ${({ theme }) => theme.text1}; position: relative; } @@ -448,7 +434,7 @@ export const ClaimAccount = styled.div` width: 46px; border-radius: 46px; object-fit: contain; - background-color: var(--color-grey); + background-color: grey; } > div > p { @@ -680,6 +666,12 @@ export const ClaimBreakdown = styled.div` display: flex; width: 100%; flex-flow: column wrap; + + > h2 { + font-size: 28px; + font-weight: 500; + text-align: center; + } ` export const FooterNavButtons = styled.div` @@ -738,6 +730,12 @@ export const Demo = styled(ClaimTable)` export const InvestFlow = styled.div` display: flex; flex-flow: column wrap; + + h1 { + font-size: 28px; + font-weight: 500; + text-align: center; + } ` export const InvestContent = styled.div` @@ -753,7 +751,7 @@ export const StepIndicator = styled.div` export const Steps = styled.div<{ step: number | 0 }>` list-style-type: decimal; margin: 0 0 12px; - background: var(--color-container-bg); + background: grey; padding: 12px; border-radius: 12px; display: grid; @@ -868,7 +866,7 @@ export const InvestAvailableBar = styled.div<{ percentage?: number }>` position: relative; height: 10px; border-radius: 24px; - background: var(--color-container-bg2); + background: grey; margin: 8px 0; &::before { @@ -893,20 +891,20 @@ export const InvestSummary = styled.div` display: grid; grid-template-columns: auto auto; font-size: 15px; - gap: 12px; + gap: 16px 36px; > span { display: flex; flex-flow: column wrap; margin: 0 0 12px; color: ${({ theme }) => transparentize(0.1, theme.text1)}; + gap: 3px; } > span > ${ButtonPrimary} { - margin: 12px 0; + margin: 12px 0 12px -9px; padding: 6px; font-size: 16px; - max-width: 154px; } > span > i { @@ -937,7 +935,7 @@ export const InvestTokenSubtotal = styled.div` display: flex; padding: 56px; margin: 0 0 24px; - background: var(--color-container-bg3); + background: grey; color: ${({ theme }) => theme.text1}; border-radius: var(--border-radius); font-size: 21px; From 0b6b4b7d01c2368569424cce5414a8ffe2658221 Mon Sep 17 00:00:00 2001 From: Leandro Boscariol Date: Mon, 17 Jan 2022 12:03:02 -0800 Subject: [PATCH 4/6] Fix native investment (#2123) # Summary Fixing bug with Native investment Example of a successful claim using ETH https://rinkeby.etherscan.io/tx/0x63b9138efd234380362babc8bb2e6566750a2e9ea83ce10fe9b4862c6ad31f0f The issue was that `parseUnits` adds X zeroes to the value passed in, but what I wanted is to remove them. Switched back to JSBI.divide by 10**18 and now it gives us the proper amount :) # Test 1. On rinkeby, connect your account and go to the claim page 2. Pick one account from the list below and check for the balance 3. If it has not been claimed yet, you should be able to claim it now :) 4. Trigger the claim - make sure you have the required amount of ETH + gas costs * Your ETH claim should go through without issues **Notes** this change might break the investor flow. I can revert that, it's on only to make sure the claiming is working # Accounts I filtered this list based on accounts that ONLY have ETH claims to make sure you don't need anything else (GNO/USDC) - 0xb5434A86d09144da8854D1c40e3Cc54d78738695 - 0x9B827cc7Af79d71A2ca0f75343754935BfaF92D3 - 0x5d42D4Cd8B9D6141B1084E8eAD92e8F76b08705E - 0x3320b6c454d0361dd3dF4912e8a06F3C945b8C60 - 0xCe0E37Bf2fC4B5c14D881f36712efB5Dcee203E8 - 0x030905cc078F3c71434fea1175615Bcc2c91FEf8 - 0xb0fE1FcEC3fE9f16c984b7A5668DF89bc7592B9D - 0x6aBBaD8bd6Aced3649d52DFd32d2Ee04f3BE4aFB - 0xF925FDd0de74d75Dd9C71095865c1511863092a9 - 0x26bC2B4D5DA4dc437F581AEFf490E2DdDa0aCFAa - 0xA78266E26a6087566b17C64dE1Ed1D9533E1B36b - 0x3877f08a4b722F48345B0e771c579fF335B533DA - 0x1F1430C14595442c683cF3d3033a726ADecd6179 - 0xD5a0a16AA1ed668c45b29E905bafcb76C21a90BA - 0xE6EA6d415e7Cb32bee6Db8Ad03e3749FEF23c9cd - 0x3011862E85dDf0851f651E7d04e61e97E20a65CF - 0x7f43A4C9E787Ba6A270e67b0D1bE77263cc47a08 - 0xCD2802122F04265791A3E4568A3f24BD1Df821a2 - 0x050f5403EB197DdE4BFE07da6e4aDE19A22db00B - 0xe05206Ba64e59bF94c3dc3E1775Ec3FE04c33D08 - 0xC8249FB3b37fF0788462F1d20263ae373fbe68BB - 0x01A72692ffE0A80029DE7982227A186F2787046f - 0xDb3a676b8Eb7dBBC43b68C7Fb149f565343e850C --- src/custom/state/claim/hooks/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/custom/state/claim/hooks/index.ts b/src/custom/state/claim/hooks/index.ts index c1a3475c3..ef4b0623f 100644 --- a/src/custom/state/claim/hooks/index.ts +++ b/src/custom/state/claim/hooks/index.ts @@ -77,6 +77,9 @@ export const USDC_PRICE = '150000' // '0.15' USDC (6 decimals) per vCOW, in atom const TWO_WEEKS = ms`2 weeks` const SIX_WEEKS = ms`6 weeks` +// For native token price calculation +const DENOMINATOR = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18)) + export enum ClaimType { Airdrop, // free, no vesting, can be available on both mainnet and gchain GnoOption, // paid, with vesting, must use GNO, can be available on both mainnet and gchain @@ -434,7 +437,7 @@ export function useClaimCallback(account: string | null | undefined): { const vCowAmount = CurrencyAmount.fromRawAmount(vCowToken, totalClaimedAmount) - return vCowContract.estimateGas['claimMany'](...args).then((estimatedGas) => { + return vCowContract.estimateGas.claimMany(...args).then((estimatedGas) => { // Last item in the array contains the call overrides const extendedArgs = _extendFinalArg(args, { from: connectedAccount, // add the `from` as the connected account @@ -541,6 +544,7 @@ function _getClaimManyArgs({ }) const value = totalValue.toString() === '0' ? undefined : totalValue.toString() + const args: GetClaimManyArgsResult['args'] = indices.length > 0 ? [indices, claimTypes, claimants, claimableAmounts, claimedAmounts, merkleProofs, sendEth, { value }] @@ -619,9 +623,11 @@ function _getClaimValue(claim: UserClaimData, vCowAmount: string, chainId: Suppo const price = NATIVE_TOKEN_PRICE[chainId] - const claimValueInAtoms = JSBI.multiply(JSBI.BigInt(vCowAmount), JSBI.BigInt(price)) - - return parseUnits(claimValueInAtoms.toString(), 18).toString() + // Why InAtomsSquared? because we are multiplying vCowAmount (which is in atoms == * 10**18) + // by the price (which is also in atoms == * 10**18) + const claimValueInAtomsSquared = JSBI.multiply(JSBI.BigInt(vCowAmount), JSBI.BigInt(price)) + // Then it's divided by 10**18 to return the value in the native currency atoms + return JSBI.divide(claimValueInAtomsSquared, DENOMINATOR).toString() } type LastAddress = string From 706011ec25264810bdac7b2ff30d899ab8c025c1 Mon Sep 17 00:00:00 2001 From: Ramiro Vazquez Date: Mon, 17 Jan 2022 18:02:16 -0300 Subject: [PATCH 5/6] remove progress bar from Profile page (#2177) --- src/custom/pages/Profile/index.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/custom/pages/Profile/index.tsx b/src/custom/pages/Profile/index.tsx index 65428893a..8a4faa4ec 100644 --- a/src/custom/pages/Profile/index.tsx +++ b/src/custom/pages/Profile/index.tsx @@ -33,8 +33,6 @@ import AffiliateStatusCheck from 'components/AffiliateStatusCheck' import { useHasOrders } from 'api/gnosisProtocol/hooks' import CowProtocolLogo from 'components/CowProtocolLogo' import { Title } from 'components/Page' -import { ProgressBar } from 'components/ProgressBar' -import { useState } from 'react' import { useTokenBalance } from 'state/wallet/hooks' import { V_COW } from 'constants/tokens' import { AMOUNT_PRECISION } from 'constants/index' @@ -46,7 +44,6 @@ export default function Profile() { const lastUpdated = useTimeAgo(profileData?.lastUpdated) const isTradesTooltipVisible = account && chainId == 1 && !!profileData?.totalTrades const hasOrders = useHasOrders(account) - const [percentage, setPercentage] = useState(0) const vCowBalance = useTokenBalance(account || undefined, chainId ? V_COW[chainId] : undefined) @@ -220,7 +217,6 @@ export default function Profile() { {!account && console.log('TODO')} />} - ) From 325efc170869cc9f21445cb9d11492a8664d94f6 Mon Sep 17 00:00:00 2001 From: Leandro Boscariol Date: Mon, 17 Jan 2022 14:18:49 -0800 Subject: [PATCH 6/6] 2120/claim countdowns (#2158) * New component copied from uniswap's src/pages/Earn/Countdown * Exporting contract deployment time * Integrating new Countdown component into claims table * Updated sentence slightly to match what was in place before * Making the return type cleaner * Return a message instead of null when countdown expires Co-authored-by: Leandro --- src/custom/pages/Claim/ClaimsTable.tsx | 21 +++++++- src/custom/pages/Claim/Countdown.tsx | 67 ++++++++++++++++++++++++++ src/custom/state/claim/hooks/index.ts | 2 +- 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 src/custom/pages/Claim/Countdown.tsx diff --git a/src/custom/pages/Claim/ClaimsTable.tsx b/src/custom/pages/Claim/ClaimsTable.tsx index b5c7f50c9..f1a748fed 100644 --- a/src/custom/pages/Claim/ClaimsTable.tsx +++ b/src/custom/pages/Claim/ClaimsTable.tsx @@ -1,5 +1,11 @@ +import { + ClaimType, + useAirdropDeadline, + useClaimState, + useDeploymentTimestamp, + useInvestmentDeadline, +} from 'state/claim/hooks' import styled from 'styled-components/macro' -import { ClaimType, useClaimState } from 'state/claim/hooks' import { ClaimTable, ClaimBreakdown, TokenLogo } from 'pages/Claim/styled' import CowProtocolLogo from 'components/CowProtocolLogo' import { ClaimStatus } from 'state/claim/actions' @@ -9,6 +15,7 @@ import { EnhancedUserClaimData } from './types' import { useAllClaimingTransactionIndices } from 'state/enhancedTransactions/hooks' import { CustomLightSpinner } from 'theme' import Circle from 'assets/images/blue-loader.svg' +import { Countdown } from 'pages/Claim/Countdown' type ClaimsTableProps = { handleSelectAll: (event: React.ChangeEvent) => void @@ -22,6 +29,8 @@ type ClaimsTableProps = { type ClaimsTableRowProps = EnhancedUserClaimData & Pick & { selected: number[] + start: number | null + end: number | null isPendingClaim: boolean } @@ -50,6 +59,8 @@ const ClaimsTableRow = ({ cost, handleSelect, selected, + start, + end, }: ClaimsTableRowProps) => { return ( @@ -99,7 +110,7 @@ const ClaimsTableRow = ({ Vesting: {type === ClaimType.Airdrop ? 'No' : '4 years (linear)'} - Ends in: 28 days, 10h, 50m + Ends in: {start && end && } @@ -119,6 +130,10 @@ export default function ClaimsTable({ const hideTable = isAirdropOnly || !hasClaims || !activeClaimAccount || claimStatus !== ClaimStatus.DEFAULT || isInvestFlowActive + const start = useDeploymentTimestamp() + const investmentEnd = useInvestmentDeadline() + const airdropEnd = useAirdropDeadline() + if (hideTable) return null return ( @@ -146,6 +161,8 @@ export default function ClaimsTable({ isPendingClaim={pendingClaimsSet.has(claim.index)} selected={selected} handleSelect={handleSelect} + start={start} + end={claim.isFree ? airdropEnd : investmentEnd} /> ))} diff --git a/src/custom/pages/Claim/Countdown.tsx b/src/custom/pages/Claim/Countdown.tsx new file mode 100644 index 000000000..74b15ca16 --- /dev/null +++ b/src/custom/pages/Claim/Countdown.tsx @@ -0,0 +1,67 @@ +// Sort of a mod of but not quite from src/pages/Earn/Countdown.tsx +import { useEffect, useState } from 'react' + +const MINUTE = 60 +const HOUR = MINUTE * 60 +const DAY = HOUR * 24 + +export type Props = { + start: number + end: number +} + +/** + * Copied over from src/pages/Earn/Countdown.tsx and heavily modified it + * + * If current time is past end time, returns null + * + * @param start start time in ms + * @param end end time in ms + */ +export function Countdown({ start, end }: Props) { + // get current time, store as seconds because 🤷 + const [time, setTime] = useState(() => Math.floor(Date.now() / 1000)) + + useEffect((): (() => void) | void => { + // we only need to tick if not ended yet + if (time <= end / 1000) { + const timeout = setTimeout(() => setTime(Math.floor(Date.now() / 1000)), 1000) + return () => { + clearTimeout(timeout) + } + } + }, [time, end]) + + const timeUntilGenesis = start / 1000 - time + const timeUntilEnd = end / 1000 - time + + let timeRemaining: number + if (timeUntilGenesis >= 0) { + timeRemaining = timeUntilGenesis + } else { + const ongoing = timeUntilEnd >= 0 + if (ongoing) { + timeRemaining = timeUntilEnd + } else { + timeRemaining = Infinity + } + } + + const days = (timeRemaining - (timeRemaining % DAY)) / DAY + timeRemaining -= days * DAY + const hours = (timeRemaining - (timeRemaining % HOUR)) / HOUR + timeRemaining -= hours * HOUR + const minutes = (timeRemaining - (timeRemaining % MINUTE)) / MINUTE + timeRemaining -= minutes * MINUTE + const seconds = timeRemaining + + return ( + <> + {Number.isFinite(timeRemaining) + ? `${days} days, ${hours.toString().padStart(2, '0')}h, ${minutes.toString().padStart(2, '0')}m, ${seconds + .toString() + .padStart(2, '0')}s` + : 'No longer claimable'} + + ) +} diff --git a/src/custom/state/claim/hooks/index.ts b/src/custom/state/claim/hooks/index.ts index ef4b0623f..f9e5cd55e 100644 --- a/src/custom/state/claim/hooks/index.ts +++ b/src/custom/state/claim/hooks/index.ts @@ -282,7 +282,7 @@ const createMockTx = (data: number[]) => ({ * * Returns null if in there's no network or vCowContract doesn't exist */ -function useDeploymentTimestamp(): number | null { +export function useDeploymentTimestamp(): number | null { const { chainId } = useActiveWeb3React() const vCowContract = useVCowContract() const [timestamp, setTimestamp] = useState(null)