diff --git a/components/ClaimPeriodCountdown.tsx b/components/ClaimPeriodCountdown.tsx new file mode 100644 index 0000000..b861ad5 --- /dev/null +++ b/components/ClaimPeriodCountdown.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import Countdown from 'react-countdown'; + +export interface ClaimPeriodCountdownProps { + claimPeriodStart: number; + claimPeriodEnd: number; +} + +interface RendererProps { + days: number; + hours: number; + minutes: number; + seconds: number; + completed: boolean; +} + +const ClaimPeriodCountdown = ({ claimPeriodStart, claimPeriodEnd }: ClaimPeriodCountdownProps) => { + const ClaimPeriodEnded = () => Claim Period has ended; + + const buildRenderer = + (beforeStart: boolean) => + // eslint-disable-next-line react/display-name + ({ days, hours, minutes, seconds, completed }: RendererProps) => { + if (completed) { + if (beforeStart) { + return Claim Period has started; + } + + return ; + } + + const numberText = (number: number, singular: string, plural: string = singular + 's') => + number > 0 ? number + ' ' + (number === 1 ? singular : plural) : ''; + + const daysText = numberText(days, 'day'); + const hoursText = numberText(hours, 'hour'); + const minutesText = numberText(minutes, 'minute'); + const secondsText = numberText(seconds, 'second'); + + const allText = [daysText, hoursText, minutesText, secondsText].filter((text) => text).join(', '); + + return {allText}; + }; + + if (claimPeriodStart > Date.now()) { + return ( + + Claim Period starts in + + ); + } + + if (claimPeriodEnd > Date.now()) { + return ( + + Claim Period ends in + + ); + } + + return ; +}; +export default ClaimPeriodCountdown; diff --git a/hooks/parcelNFT.ts b/hooks/parcelNFT.ts index 1973a3c..a12549f 100644 --- a/hooks/parcelNFT.ts +++ b/hooks/parcelNFT.ts @@ -1,6 +1,7 @@ import { ParcelNFT__factory } from '@citydao/parcel-contracts/dist/types/contracts/factories/ParcelNFT__factory'; import { ParcelNFT } from '@citydao/parcel-contracts/dist/types/contracts/ParcelNFT'; import { ContractAddress } from '@citydao/parcel-contracts/src/constants/accounts'; +import { BigNumber } from 'ethers'; import { useEffect, useState } from 'react'; import { addresses } from '../data/whiteListedAddresses'; import { useContractLoader } from './contractHooks'; @@ -16,6 +17,8 @@ export interface ParcelNFTDetails { totalSupply: number; walletAlreadyClaimed: number; allowance: number; + claimPeriodStart: number; + claimPeriodEnd: number; } export const useParcelNFT = (contractAddress: ContractAddress): ParcelNFTHook => { @@ -24,22 +27,36 @@ export const useParcelNFT = (contractAddress: ContractAddress): ParcelNFTHook => contract: parcelNFT, values, refetch: refetchValues, - } = useContractLoader(new ParcelNFT__factory(), contractAddress, ['totalSupply']); + } = useContractLoader(new ParcelNFT__factory(), contractAddress, ['totalSupply', 'claimPeriod']); const [walletAlreadyClaimed, setWalletAlreadyClaimed] = useState(0); - const parcelNFTDetails: ParcelNFTDetails | null = - parcelNFT && values && account - ? { - parcelNFT: parcelNFT, - walletAlreadyClaimed, - totalSupply: values.totalSupply.toNumber(), - allowance: addresses[account.toLowerCase()], - } - : null; + const buildParcelNFTDetails = (): ParcelNFTDetails | null => { + if (!(parcelNFT && values && account)) { + return null; + } + + const { totalSupply, claimPeriod } = values; + + const totalSupplyNumber = 'toNumber' in totalSupply ? totalSupply.toNumber() : 0; + const [claimPeriodStart, claimPeriodEnd] = + claimPeriod instanceof Array ? claimPeriod : [BigNumber.from(0), BigNumber.from(0)]; + + return { + parcelNFT: parcelNFT, + walletAlreadyClaimed, + totalSupply: totalSupplyNumber, + claimPeriodStart: claimPeriodStart.toNumber() * 1000, + claimPeriodEnd: claimPeriodEnd.toNumber() * 1000, + allowance: addresses[account.toLowerCase()], + }; + }; + + const parcelNFTDetails: ParcelNFTDetails | null = buildParcelNFTDetails(); useEffect(() => { // noinspection JSIgnoredPromiseFromCall loadFields(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [account, parcelNFT]); const loadFields = async () => { diff --git a/package.json b/package.json index 9ffd45f..ab14542 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "merkletreejs": "^0.2.31", "next": "12.0.4", "react": "17.0.2", + "react-countdown": "^2.3.2", "react-dom": "17.0.2", "react-modal": "^3.15.1", "react-tooltip": "^4.2.21", @@ -44,5 +45,8 @@ "pretty-quick": "^3.1.3", "styled-components": "^5.3.5", "typescript": "4.5.2" + }, + "resolutions": { + "eth-sig-util/**/ethereumjs-abi": "^0.6.8" } } diff --git a/pages/index.tsx b/pages/index.tsx index b6147db..d6bd9e1 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -7,6 +7,7 @@ import { useEffect, useMemo, useState } from 'react'; import ReactTooltip from 'react-tooltip'; import ClaimButton from '../components/ClaimButton'; import { ClaimModal } from '../components/ClaimModal'; +import ClaimPeriodCountdown from '../components/ClaimPeriodCountdown'; import { ClaimSuccessModal } from '../components/ClaimSuccessModal'; import { ConnectButton } from '../components/ConnectButton'; import { MintedNftsView } from '../components/MintedNftsView'; @@ -28,18 +29,15 @@ const Home: NextPage = () => { const { handleOpenClaimModal, handleCloseClaimModal, handleOpenClaimSuccessModal } = useModal(); const [currentView, setCurrentView] = useState(VIEWS.INITIAL_VIEW); - const { - account: address, - connect, - disconnect, - chainId, - } = useWallet(); + const { account: address, connect, disconnect, chainId } = useWallet(); const { parcelNFTDetails, refetch } = useParcelNFT(PARCEL0_NFT_CONTRACT_ADDRESSES[chainId ?? 0]); const allowance = parcelNFTDetails?.allowance || 0; const walletAlreadyClaimed = parcelNFTDetails?.walletAlreadyClaimed || 0; const totalSupply = parcelNFTDetails?.totalSupply || 0; + const claimPeriodStart = parcelNFTDetails?.claimPeriodStart || 0; + const claimPeriodEnd = parcelNFTDetails?.claimPeriodEnd || 0; const onWalletDisconnect = async () => { await disconnect(); @@ -84,11 +82,10 @@ const Home: NextPage = () => { } }; - //TODO trkaplan check what happens when you visit with a browser that does not have metamask - useEffect(() => { // noinspection JSIgnoredPromiseFromCall checkEligibility(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [address, parcelNFTDetails]); const checkEligibility = async () => { @@ -127,8 +124,7 @@ const Home: NextPage = () => {
70 HAIL BASIN RD, POWELL, WYOMING
- Claim ends in 45 Days 00 Hours{' '} - {/* TODO trkaplan use countdown component */} +
{address && ( <> diff --git a/yarn.lock b/yarn.lock index 420c33b..0164603 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2794,7 +2794,7 @@ ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" -ethereumjs-abi@0.6.8: +ethereumjs-abi@^0.6.8, "ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git": version "0.6.8" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" integrity sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA== @@ -4793,6 +4793,13 @@ raw-body@2.4.1: iconv-lite "0.4.24" unpipe "1.0.0" +react-countdown@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/react-countdown/-/react-countdown-2.3.2.tgz#4cc27f28f2dcd47237ee66e4b9f6d2a21fc0b0ad" + integrity sha512-Q4SADotHtgOxNWhDdvgupmKVL0pMB9DvoFcxv5AzjsxVhzOVxnttMbAywgqeOdruwEAmnPhOhNv/awAgkwru2w== + dependencies: + prop-types "^15.7.2" + react-dom@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"