Add proposal info card (#52)

This commit is contained in:
Maria Rushkova 2021-09-06 18:02:03 +02:00 committed by GitHub
parent ce07cd30fb
commit fea412ec90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 739 additions and 2 deletions

View File

@ -7,7 +7,9 @@
"license": "MIT",
"watch": {
"build": {
"patterns": ["src"],
"patterns": [
"src"
],
"extensions": "ts,tsx",
"runOnChangeOnly": false
}
@ -30,17 +32,21 @@
"lint:prettier": "yarn prettier './{src,test}/**/*.{ts,tsx}'"
},
"dependencies": {
"@status-waku-voting/core": "^0.1.0",
"@status-waku-voting/proposal-hooks": "^0.1.0",
"@status-waku-voting/react-components": "^0.1.0",
"@status-waku-voting/core": "^0.1.0",
"ethers": "^5.4.4",
"humanize-duration": "^3.27.0",
"react": "^17.0.2",
"react-countup": "^5.2.0",
"styled-components": "^5.3.0"
},
"devDependencies": {
"@types/chai": "^4.2.21",
"@types/humanize-duration": "^3.25.1",
"@types/mocha": "^9.0.0",
"@types/react": "^17.0.16",
"@types/react-countup": "^4.3.1",
"@types/styled-components": "^5.1.12",
"@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.29.0",

View File

@ -0,0 +1,14 @@
declare module '*.svg' {
const url: string
export default url
}
declare module '*.jpg' {
const url: string
export default url
}
declare module '*.png' {
const url: string
export default url
}

View File

@ -0,0 +1,3 @@
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00004 11.2326L1.83004 7.04645L0.415039 8.46692L6.00004 14.0735L18 2.0271L16.585 0.606628L6.00004 11.2326Z" fill="#3BA881"/>
</svg>

After

Width:  |  Height:  |  Size: 239 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="19" viewBox="0 0 24 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99989 14.5601L2.43989 9.00005L0.553223 10.8867L7.99989 18.3334L23.9999 2.33339L22.1132 0.44672L7.99989 14.5601Z" fill="#3BA881"/>
</svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1.01935L7.99999 8.04642M7.99999 8.04642L15 15.0735M7.99999 8.04642L15 1.01935M7.99999 8.04642L1 15.0735" stroke="#FF6F80" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 305 B

View File

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.6665 1.66667L10.9998 11M10.9998 11L20.3332 20.3333M10.9998 11L20.3332 1.66667M10.9998 11L1.6665 20.3333" stroke="#FF6F80" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 305 B

View File

@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.49984 3.0665C8.49984 3.28742 8.32075 3.4665 8.09984 3.4665L5.29984 3.4665C4.56346 3.4665 3.9665 4.06346 3.9665 4.79984L3.9665 11.1998C3.9665 11.9362 4.56346 12.5332 5.29984 12.5332H11.6998C12.4362 12.5332 13.0332 11.9362 13.0332 11.1998V8.39984C13.0332 8.17892 13.2123 7.99984 13.4332 7.99984C13.6541 7.99984 13.8332 8.17892 13.8332 8.39984V11.1998C13.8332 12.378 12.878 13.3332 11.6998 13.3332H5.29984C4.12163 13.3332 3.1665 12.378 3.1665 11.1998V4.79984C3.1665 3.62163 4.12163 2.6665 5.29984 2.6665H8.09984C8.32075 2.6665 8.49984 2.84559 8.49984 3.0665ZM12.8562 2.66765C13.4299 2.66465 13.4328 2.66463 13.5317 2.70563C13.6504 2.75456 13.7448 2.84863 13.7942 2.96714C13.8009 2.98344 13.8066 2.99699 13.8113 3.01087C13.853 3.13344 13.8201 3.28131 13.8314 5.57301C13.8314 5.70145 13.7804 5.82462 13.6896 5.91544C13.5988 6.00626 13.4757 6.05728 13.3472 6.05728C13.2188 6.05728 13.0957 6.00626 13.0049 5.91544C12.9141 5.82462 12.863 5.70145 12.863 5.57301V4.32117L7.33231 9.85199C7.28764 9.89825 7.23421 9.93514 7.17514 9.96052C7.11607 9.9859 7.05253 9.99926 6.98824 9.99982C6.92395 10.0004 6.86019 9.98812 6.80068 9.96378C6.74118 9.93943 6.68712 9.90347 6.64165 9.858C6.59619 9.81253 6.56024 9.75846 6.53589 9.69894C6.51155 9.63943 6.4993 9.57566 6.49986 9.51136C6.50041 9.44706 6.51377 9.38351 6.53915 9.32443C6.56452 9.26534 6.60141 9.21191 6.64766 9.16723L12.1784 3.63592H10.9263C10.7979 3.63592 10.6747 3.5849 10.5839 3.49408C10.4931 3.40326 10.4421 3.28009 10.4421 3.15165C10.4421 3.02321 10.4931 2.90004 10.5839 2.80922C10.6747 2.7184 10.7979 2.66738 10.9263 2.66738C11.9071 2.67262 12.4949 2.66954 12.8562 2.66765Z" fill="#4360DF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,3 @@
<svg width="14" height="5" viewBox="0 0 14 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.19619 4.16989C7.1213 4.21097 7.03062 4.21097 6.95573 4.16989L1.06846 0.940522C0.841661 0.816112 0.930014 0.471333 1.1887 0.471333L12.9632 0.471332C13.2219 0.471332 13.3103 0.816112 13.0835 0.940522L7.19619 4.16989Z" fill="#7E98F4"/>
</svg>

After

Width:  |  Height:  |  Size: 346 B

View File

@ -0,0 +1,92 @@
import { Button } from '@status-waku-voting/react-components'
import styled from 'styled-components'
export const Btn = styled(Button)`
padding: 11px 0;
font-weight: 500;
line-height: 22px;
text-align: center;
`
export const VoteBtn = styled(Btn)`
width: 44%;
padding: 11px 0;
font-weight: 500;
line-height: 22px;
text-align: center;
&:disabled {
background: #f3f3f3;
color: #939ba1;
}
@media (max-width: 768px) {
width: 48%;
}
`
export const VoteBtnAgainst = styled(VoteBtn)`
background-color: #ffeded;
color: #c90a0a;
&:not(:disabled):hover {
background: #ffdada;
}
&:not(:disabled):active {
background: #fff5f5;
}
`
export const VoteBtnFor = styled(VoteBtn)`
background-color: #edfff4;
color: #1d920a;
&:not(:disabled):hover {
background: #ccfee0;
}
&:not(:disabled):active {
background: #F3FFF8;
`
export const VoteSendingBtn = styled(Btn)`
margin-top: 24px;
padding: 0;
color: #0f3595;
height: auto;
background: transparent;
&:hover {
color: #5d7be2;
}
&:active {
color: #0f3595;
}
&:disabled {
color: #676868;
}
`
export const FinalBtn = styled(Btn)`
width: 100%;
background: #edf1ff;
color: #0f3595;
&:not(:disabled):hover {
background: #dbeeff;
}
&:not(:disabled):active {
background: #f7f9ff;
}
&:disabled {
background: #f3f3f3;
color: #939ba1;
}
@media (max-width: 600px) {
margin-top: 32px;
}
`

View File

@ -2,11 +2,13 @@ import React from 'react'
import styled from 'styled-components'
import { ProposalHeader } from './ProposalHeader'
import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
import { ProposalList } from './ProposalList'
export function Proposal() {
return (
<ProposalWrapper>
<ProposalHeader theme={blueTheme} />
<ProposalList />
</ProposalWrapper>
)
}

View File

@ -0,0 +1,40 @@
import React from 'react'
import styled from 'styled-components'
import { ProposalInfo } from './ProposalInfo'
import { ProposalVote } from './ProposalVoteCard/ProposalVote'
export function ProposalCard() {
return (
<Card>
<ProposalInfo
heading={'This is a very long, explainative and sophisticated title for a proposal.'}
text={
'This is a longer description of the proposal. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales. Nullam mattis fermentum libero, non volutpat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales. Nullam mattis fermentum libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales. Nullam mattis fermentum libero.'
}
address={'#'}
/>
<ProposalVote vote={2345678} voteWinner={2} />
</Card>
)
}
export const Card = styled.div`
display: flex;
align-items: stretch;
margin-top: 24px;
@media (max-width: 768px) {
flex-direction: column;
margin-top: 0;
padding: 16px 0 32px;
border-bottom: 1px solid #e0e0e0;
}
@media (max-width: 600px) {
padding-bottom: 16px;
}
&:not:first-child {
@media (max-width: 768px) {
border-top: 1px solid #e0e0e0;
}
}
`

View File

@ -0,0 +1,54 @@
import React from 'react'
import styled from 'styled-components'
import { ViewLink } from './ViewLink'
type ProposalInfoProps = {
heading: string
text: string
address: string
}
export function ProposalInfo({ heading, text, address }: ProposalInfoProps) {
return (
<Card>
<CardHeading>{heading}</CardHeading>
<CardText>{text}</CardText>
<ViewLink address={address} />
</Card>
)
}
export const Card = styled.div`
display: flex;
flex-direction: column;
align-items: center;
width: 50%;
padding: 24px;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 6px 0px 0px 6px;
@media (max-width: 768px) {
width: 100%;
margin: 0;
border: none;
box-shadow: none;
padding-bottom: 0;
}
@media (max-width: 600px) {
padding: 0;
}
`
const CardHeading = styled.div`
height: 56px;
font-weight: bold;
font-size: 22px;
line-height: 24px;
margin-bottom: 8px;
`
const CardText = styled.div`
font-size: 13px;
line-height: 18px;
margin-bottom: 16px;
`

View File

@ -0,0 +1,6 @@
import React from 'react'
import { ProposalCard } from './ProposalCard'
export function ProposalList() {
return <ProposalCard />
}

View File

@ -0,0 +1,86 @@
import React, { useState } from 'react'
import styled from 'styled-components'
import { useEthers } from '@usedapp/core'
import { FinalBtn, VoteBtnAgainst, VoteBtnFor } from '../Buttons'
import { VoteSubmitButton } from './VoteSubmitButton'
import { VoteChart } from './VoteChart'
interface ProposalVoteProps {
vote: number
voteWinner?: number
hideModalFunction?: (val: boolean) => void
}
export function ProposalVote({ vote, voteWinner, hideModalFunction }: ProposalVoteProps) {
const { account } = useEthers()
const [showVoteModal, setShowVoteModal] = useState(false)
return (
<Card>
{voteWinner ? <CardHeading>Proposal {voteWinner == 1 ? 'rejected' : 'passed'}</CardHeading> : <CardHeading />}
<VoteChart votesFor={1865567} votesAgainst={1740235} timeLeft={48} voteWinner={2} />
{voteWinner ? (
<FinalBtn disabled={!account}>Finalize the vote</FinalBtn>
) : (
<VotesBtns>
<VoteBtnAgainst disabled={!account}>Vote Against</VoteBtnAgainst>
<VoteBtnFor disabled={!account}>Vote For</VoteBtnFor>
</VotesBtns>
)}
<CardVoteBottom>{vote && <VoteSubmitButton votes={vote} disabled={!account} />}</CardVoteBottom>
</Card>
)
}
export const Card = styled.div`
display: flex;
flex-direction: column;
align-items: center;
width: 50%;
padding: 24px;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 6px 0px 0px 6px;
background-color: #fbfcfe;
@media (max-width: 768px) {
width: 100%;
box-shadow: none;
border-radius: unset;
background-color: unset;
padding-bottom: 0;
}
@media (max-width: 600px) {
flex-direction: column;
padding: 16px 0 0;
border-bottom: none;
}
`
export const CardHeading = styled.h2`
height: 24px;
font-weight: bold;
font-size: 17px;
line-height: 24px;
margin: 0;
margin-bottom: 15px;
`
export const VotesBtns = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
@media (max-width: 600px) {
margin-top: 24px;
}
`
const CardVoteBottom = styled.div`
display: flex;
justify-content: flex-end;
align-items: center;
`

View File

@ -0,0 +1,215 @@
import React, { useEffect, useState } from 'react'
import CountUp from 'react-countup'
import styled from 'styled-components'
import { addCommas } from '../../helpers/addCommas'
import { formatTimeLeft } from '../../helpers/fomatTimeLeft'
import { VoteGraphBar } from './VoteGraphBar'
import crossIcon from '../../assets/svg/cross.svg'
import crossWinnerIcon from '../../assets/svg/crossWinner.svg'
import checkIcon from '../../assets/svg/check.svg'
import checkWinnerIcon from '../../assets/svg/checkWinner.svg'
export interface VoteChartProps {
votesFor: number
votesAgainst: number
timeLeft: number
voteWinner?: number
proposingAmount?: number
selectedVote?: number
isAnimation?: boolean
tabletMode?: (val: boolean) => void
}
export function VoteChart({
votesFor,
votesAgainst,
timeLeft,
voteWinner,
proposingAmount,
selectedVote,
isAnimation,
tabletMode,
}: VoteChartProps) {
const [mobileVersion, setMobileVersion] = useState(false)
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 600) {
setMobileVersion(true)
} else {
setMobileVersion(false)
}
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const voteSum = votesFor + votesAgainst
const graphWidth = (100 * votesAgainst) / voteSum
let balanceWidth = graphWidth
if (proposingAmount && selectedVote) {
balanceWidth =
selectedVote === 0
? (100 * (votesAgainst + proposingAmount)) / (voteSum + proposingAmount)
: (100 * votesAgainst) / (voteSum + proposingAmount)
}
return (
<Votes>
<VotesChart className={selectedVote || tabletMode ? '' : 'notModal'}>
<VoteBox
style={{
filter: voteWinner && voteWinner === 2 ? 'grayscale(1)' : 'none',
alignItems: mobileVersion ? 'flex-start' : 'center',
}}
>
<VoteIcon src={voteWinner === 1 ? crossWinnerIcon : crossIcon} width={voteWinner === 1 ? '18px' : '14px'} />
<span>
{' '}
{isAnimation && proposingAmount && selectedVote && selectedVote === 0 ? (
<CountUp end={votesAgainst + proposingAmount} separator="," />
) : (
addCommas(votesAgainst)
)}{' '}
<span style={{ fontWeight: 'normal' }}>ABC</span>
</span>
</VoteBox>
<TimeLeft className={selectedVote ? '' : 'notModal'}>{formatTimeLeft(timeLeft)}</TimeLeft>
<VoteBox
style={{
filter: voteWinner && voteWinner === 1 ? 'grayscale(1)' : 'none',
alignItems: mobileVersion ? 'flex-start' : 'center',
}}
>
<VoteIcon src={voteWinner === 2 ? checkWinnerIcon : checkIcon} width={voteWinner === 2 ? '24px' : '18px'} />
<span>
{' '}
{isAnimation && proposingAmount && selectedVote && selectedVote === 1 ? (
<CountUp end={votesFor + proposingAmount} separator="," />
) : (
addCommas(votesFor)
)}{' '}
<span style={{ fontWeight: 'normal' }}>ABC</span>
</span>
</VoteBox>
</VotesChart>
<VoteGraphBarWrap className={selectedVote || tabletMode ? '' : 'notModal'}>
<VoteGraphBar
graphWidth={graphWidth}
balanceWidth={balanceWidth}
voteWinner={voteWinner}
isAnimation={isAnimation}
/>
<TimeLeftMobile className={selectedVote ? '' : 'notModal'}>{formatTimeLeft(timeLeft)}</TimeLeftMobile>
</VoteGraphBarWrap>
</Votes>
)
}
const Votes = styled.div`
display: flex;
flex-direction: column;
margin-bottom: 32px;
width: 100%;
position: relative;
@media (max-width: 600px) {
margin-bottom: 0;
}
`
const VotesChart = styled.div`
display: flex;
justify-content: space-between;
align-items: stretch;
position: relative;
margin-bottom: 13px;
&.notModal {
@media (max-width: 768px) {
margin-bottom: 0;
}
}
`
const VoteBox = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
align-content: center;
font-size: 12px;
text-align: center;
font-weight: normal;
& > span {
font-weight: bold;
}
@media (max-width: 768px) {
min-width: 70px;
}
@media (max-width: 600px) {
min-width: unset;
}
`
const VoteIcon = styled.img`
margin-bottom: 13px;
`
const TimeLeft = styled.div`
position: absolute;
top: 50%;
left: calc(50%);
transform: translateX(-50%);
font-size: 12px;
line-height: 16px;
letter-spacing: 0.1px;
color: #939ba1;
&.notModal {
@media (max-width: 768px) {
top: -16px;
}
@media (max-width: 600px) {
top: unset;
}
}
`
const TimeLeftMobile = styled.div`
position: absolute;
bottom: -23px;
left: calc(50%);
transform: translateX(-50%);
font-size: 0;
line-height: 16px;
letter-spacing: 0.1px;
color: #939ba1;
@media (max-width: 600px) {
font-size: 12px;
}
`
const VoteGraphBarWrap = styled.div`
position: static;
display: flex;
justify-content: center;
&.notModal {
@media (max-width: 768px) {
position: absolute;
width: 65%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@media (max-width: 600px) {
width: 70%;
}
}
`

View File

@ -0,0 +1,112 @@
import React, { useEffect, useState } from 'react'
import styled from 'styled-components'
import indicatorIcon from '../../assets/svg/indicator.svg'
function createKeyFrames(votesWidth: number, markerWidth: number) {
return `
@keyframes fade-in {
0% {
width: ${votesWidth}%;
}
100% {
width: ${markerWidth}%;
}
}
`
}
export interface VoteGraphBarProps {
balanceWidth?: number
graphWidth?: number
voteWinner?: number
isAnimation?: boolean
}
export function VoteGraphBar({ graphWidth, balanceWidth, voteWinner, isAnimation }: VoteGraphBarProps) {
const markerWidth: number = balanceWidth ? balanceWidth : 0
const votesWidth: number = graphWidth ? graphWidth : 0
const [keyFrames, setKeyFrames] = useState('')
const [style, setStyle] = useState<any>({ width: `${votesWidth}%` })
useEffect(() => {
if (isAnimation) {
setStyle({ width: `${markerWidth}%`, animation: 'fade-in 2s ease' })
setKeyFrames(createKeyFrames(votesWidth, markerWidth))
} else {
setStyle({ width: `${votesWidth}%` })
}
}, [isAnimation, votesWidth])
return (
<VoteGraph theme={{ voteWinner }}>
<style children={keyFrames} />
<VoteGraphAgainst theme={{ voteWinner }} style={style} />
<VoteBalance style={{ width: `${markerWidth}%` }} />
</VoteGraph>
)
}
const VoteGraph = styled.div<VoteGraphBarProps>`
position: relative;
width: 100%;
height: 16px;
background-color: ${({ theme }) => (theme.voteWinner === 1 ? '#f1f2f5' : '#edfff4')};
border-radius: 10px;
padding-top: 5px;
border-left: 13px solid ${({ theme }) => (theme.voteWinner === 2 ? '#f1f2f5' : '#ffeded')};
border-right: 13px solid ${({ theme }) => (theme.voteWinner === 1 ? '#f1f2f5' : '#edfff4')};
@media (max-width: 600px) {
height: 13px;
}
&::before {
content: '';
width: 16px;
height: 5px;
position: absolute;
top: -6px;
left: calc(50% - 2px);
transform: translateX(-50%);
background-image: url(${indicatorIcon});
background-size: cover;
@media (max-width: 768px) {
width: 9px;
height: 3px;
left: 50%;
}
@media (max-width: 768px) {
top: -4px;
}
}
`
const VoteGraphAgainst = styled.div<VoteGraphBarProps>`
position: absolute;
left: 0;
top: 0;
height: 16px;
background-color: ${({ theme }) => (theme.voteWinner === 2 ? '#f1f2f5' : '#ffeded')};
transition: width 2s;
z-index: 2;
@media (max-width: 600px) {
height: 13px;
}
`
const VoteBalance = styled.div`
position: absolute;
left: 0;
top: 0;
height: 16px;
background-color: transparent;
border-right: 2px solid #7e98f4;
z-index: 2;
@media (max-width: 600px) {
height: 13px;
transition: width 2s;
}
`

View File

@ -0,0 +1,15 @@
import React from 'react'
import { addCommas } from '../../helpers/addCommas'
import { VoteSendingBtn } from '../Buttons'
interface VoteSubmitButtonProps {
votes: number
disabled: boolean
}
export function VoteSubmitButton({ votes, disabled }: VoteSubmitButtonProps) {
if (votes > 0) {
return <VoteSendingBtn disabled={disabled}> {addCommas(votes)} votes need saving</VoteSendingBtn>
}
return null
}

View File

@ -0,0 +1,40 @@
import React from 'react'
import styled from 'styled-components'
import externalIcon from '../assets/svg/external.svg'
interface ViewLinkProps {
address: string
}
export function ViewLink({ address }: ViewLinkProps) {
return <Link href={address}>View on Etherscan</Link>
}
export const Link = styled.a`
color: #4360df;
position: relative;
padding-right: 20px;
font-size: 15px;
line-height: 22px;
text-decoration: none;
&:hover {
text-decoration: underline;
}
&:active {
text-decoration: none;
}
&::after {
content: '';
width: 16px;
height: 16px;
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
background-image: url(${externalIcon});
background-size: contain;
}
`

View File

@ -0,0 +1,3 @@
export function addCommas(votes: number) {
return votes.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}

View File

@ -0,0 +1,5 @@
import timeConvert from 'humanize-duration'
export function formatTimeLeft(timeLeft: number) {
return timeLeft > 0 ? timeConvert(timeLeft, { largest: 1, round: true }) + ' left' : 'Vote ended'
}

View File

@ -1174,6 +1174,11 @@
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57"
integrity sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==
"@types/humanize-duration@^3.25.1":
version "3.25.1"
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.25.1.tgz#b6140d5fc00ff3917b3f521784abef4bc0387ccc"
integrity sha512-WZU/4bb+lvzyDmZzjJtp++9mfKy6B3lH6gGISgkcz6SU8hMILKRM0vi08TxIsb0dQB4Gzo68MWLmctu6xqUi9g==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@ -1271,6 +1276,13 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
"@types/react-countup@^4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@types/react-countup/-/react-countup-4.3.1.tgz#5bba8e11c18549c92f9a6efe07e406c92c970f89"
integrity sha512-8KwOdVVKhMOdtXJoOg3YXZwWxnFnZtPojNs/DZRlhHTpOYIb1If+9+rVPnwnXDhNhphj5diUCds9E26VtVK8IA==
dependencies:
react-countup "*"
"@types/react-dom@>=16.9.0", "@types/react-dom@^17.0.9":
version "17.0.9"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
@ -3659,6 +3671,11 @@ cosmiconfig@^6.0.0:
path-type "^4.0.0"
yaml "^1.7.2"
countup.js@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/countup.js/-/countup.js-2.0.8.tgz#eca0c31c9db3f7769cba494d9315cd52dbaaf1b9"
integrity sha512-pW3xwwD+hB+xmtI16xFcuLS0D5hSQqPQWkZOdgpKQyzxCquDNo2VCFPkRw12vmvdpnicXVTcjmYiakG6biwINg==
create-ecdh@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@ -6294,6 +6311,11 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
humanize-duration@^3.27.0:
version "3.27.0"
resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.27.0.tgz#3f781b7cf8022ad587f76b9839b60bc2b29636b2"
integrity sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -9845,6 +9867,13 @@ rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-countup@*, react-countup@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-countup/-/react-countup-5.2.0.tgz#58be5d97acebf767d5c61df5c3a3417b95c45a16"
integrity sha512-R6+FIrW8ypwoAe0Q0CZ16OhrgAntnnnch7HrnRAy9miXFKk8jQzVADjNtGSoNUuJeq/ZFiiXCGJCJIAmRJ5fLg==
dependencies:
countup.js "^2.0.8"
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"