From d54a64a712f3e9e67b5d3aaf0002e4b12f51c035 Mon Sep 17 00:00:00 2001 From: AJ Webb Date: Tue, 20 Jul 2021 10:55:49 -1000 Subject: [PATCH 1/3] Add proposal voting table --- .../Proposal/Components/ProposalDeposits.js | 3 +- .../Components/ProposalTimingProgressBar.js | 1 + .../Components/ProposalVotingTable.js | 36 +++++++++++++++++++ src/Pages/Proposal/Components/index.js | 1 + src/Pages/Proposal/Proposal.js | 4 +++ src/utils/table/formatTableData.js | 22 ++++++++++-- 6 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 src/Pages/Proposal/Components/ProposalVotingTable.js diff --git a/src/Pages/Proposal/Components/ProposalDeposits.js b/src/Pages/Proposal/Components/ProposalDeposits.js index 394cf962..a6d118a3 100644 --- a/src/Pages/Proposal/Components/ProposalDeposits.js +++ b/src/Pages/Proposal/Components/ProposalDeposits.js @@ -22,7 +22,7 @@ const ProposalDeposits = () => { }, [currentPage, getProposalDeposits, proposalId]); const tableHeaders = [ - { displayName: 'Depositor', dataName: 'moniker' }, + { displayName: 'Depositor', dataName: 'voter' }, { displayName: 'Deposit Type', dataName: 'depositType' }, { displayName: 'Amount', dataName: 'amount' }, { displayName: 'Tx Hash', dataName: 'txHash' }, @@ -33,7 +33,6 @@ const ProposalDeposits = () => { const tableData = proposalDeposits.map(d => ({ ...d, depositType: d.type, - moniker: d.voter.moniker || d.voter.address, })); return ( diff --git a/src/Pages/Proposal/Components/ProposalTimingProgressBar.js b/src/Pages/Proposal/Components/ProposalTimingProgressBar.js index acbe70a3..bf5f3dbb 100644 --- a/src/Pages/Proposal/Components/ProposalTimingProgressBar.js +++ b/src/Pages/Proposal/Components/ProposalTimingProgressBar.js @@ -19,6 +19,7 @@ const ProgressValue = styled.span` left: 0; display: block; width: ${({ value }) => (value > 100 ? 100 : value)}%; + height: ${({ initial }) => initial && '50%'}; max-height: 100%; text-indent: -9999px; background-color: ${({ initial, theme }) => (initial ? theme.CHART_PIE_A : theme.CHART_PIE_F)}; diff --git a/src/Pages/Proposal/Components/ProposalVotingTable.js b/src/Pages/Proposal/Components/ProposalVotingTable.js new file mode 100644 index 00000000..80204161 --- /dev/null +++ b/src/Pages/Proposal/Components/ProposalVotingTable.js @@ -0,0 +1,36 @@ +import React, { useEffect } from 'react'; +import { useParams } from 'react-router'; +import { Table } from 'Components'; +import { useGovernance } from 'redux/hooks'; + +const ProposalTiming = () => { + const { proposalId } = useParams(); + const { getProposalVotes, proposalVotes, proposalVotesLoading: tableLoading } = useGovernance(); + + useEffect(() => { + if (proposalId) { + getProposalVotes(proposalId); + } + }, [getProposalVotes, proposalId]); + + const { votes: tableData } = proposalVotes; + + const tableHeaders = [ + { displayName: 'Voter', dataName: 'voter' }, + { displayName: 'Tx Hash', dataName: 'txHash' }, + { displayName: 'Answer', dataName: 'answer' }, + { displayName: 'Block Height', dataName: 'blockHeight' }, + { displayName: 'Timestamp', dataName: 'txTimestamp' }, + ]; + + return ( + + ); +}; + +export default ProposalTiming; diff --git a/src/Pages/Proposal/Components/index.js b/src/Pages/Proposal/Components/index.js index f884deed..a5ebc01b 100644 --- a/src/Pages/Proposal/Components/index.js +++ b/src/Pages/Proposal/Components/index.js @@ -2,3 +2,4 @@ export { default as ProposalDeposits } from './ProposalDeposits'; export { default as ProposalInformation } from './ProposalInformation'; export { default as ProposalTimingProgressBar } from './ProposalTimingProgressBar'; export { default as ProposalTimingTable } from './ProposalTimingTable'; +export { default as ProposalVotingTable } from './ProposalVotingTable'; diff --git a/src/Pages/Proposal/Proposal.js b/src/Pages/Proposal/Proposal.js index 18d00e16..35e70e53 100644 --- a/src/Pages/Proposal/Proposal.js +++ b/src/Pages/Proposal/Proposal.js @@ -7,6 +7,7 @@ import { ProposalInformation, ProposalTimingProgressBar, ProposalTimingTable, + ProposalVotingTable, } from './Components'; const Proposal = () => { @@ -37,6 +38,9 @@ const Proposal = () => {
+
+ +
); }; diff --git a/src/utils/table/formatTableData.js b/src/utils/table/formatTableData.js index 6dbf5edc..138daf8c 100644 --- a/src/utils/table/formatTableData.js +++ b/src/utils/table/formatTableData.js @@ -3,6 +3,7 @@ import { numberFormat } from '../number/numberFormat'; import { formatDenom } from '../number/formatDenom'; import { capitalize } from '../string/capitalize'; import { getUTCTime } from '../date/getUTCTime'; +import { isEmpty } from '../lang/isEmpty'; // Thought process here: There is a lot of repeating data that we've been cleaning and massaging. // Why not just look for any of these common values and have a standard format for them. @@ -173,8 +174,7 @@ export const formatTableData = (data = [], tableHeaders) => { dataObj?.addressId || dataObj?.proposerAddress || dataObj?.ownerAddress || - dataObj?.holdingAccount || - dataObj?.voter?.address; + dataObj?.holdingAccount; finalObj[dataName] = { value: maxLength(serverValue, 16, 3), link: `/validator/${linkAddress}`, @@ -182,6 +182,20 @@ export const formatTableData = (data = [], tableHeaders) => { }; break; } + // Address / Moniker of a voter + case 'voter': { + const isValidator = !isEmpty(serverValue.validatorAddr); + const page = isValidator ? 'validator' : 'accounts'; + const address = isValidator ? serverValue.validatorAddr : serverValue.address; + const value = serverValue?.moniker || address; + + finalObj[dataName] = { + value: maxLength(value, 16, 3), + link: `/${page}/${address}`, + hover: value, + }; + break; + } // Convert given time to standard readable UTC string case 'depositEndTime': // fallthrough case 'submitTime': // fallthrough @@ -296,6 +310,10 @@ export const formatTableData = (data = [], tableHeaders) => { finalObj[dataName] = { value: `${numberFormat(serverValue)} %` }; break; } + // Server value capitalized, remove VOTE_OPTION_ + case 'answer': + finalObj[dataName] = { value: capitalize(serverValue.replace(/vote_option_/gi, '')) }; + break; // Server value capitalized case 'depositType': // fallthrough case 'markerType': // fallthrough From 663baf7d334b62726f95ac43a5c10274e08c58c7 Mon Sep 17 00:00:00 2001 From: AJ Webb Date: Wed, 21 Jul 2021 12:39:50 -1000 Subject: [PATCH 2/3] Add vote progress bar --- src/Components/Progress/Progress.js | 97 +++++++++++++++++++ src/Components/Progress/index.js | 1 + src/Components/Summary/Summary.js | 4 +- src/Components/index.js | 1 + .../Components/ProposalInformation.js | 4 +- .../Components/ProposalTimingProgressBar.js | 88 ++++------------- .../Components/ProposalVotingGraph.js | 96 ++++++++++++++++++ src/Pages/Proposal/Components/index.js | 1 + src/Pages/Proposal/Proposal.js | 4 + 9 files changed, 224 insertions(+), 72 deletions(-) create mode 100644 src/Components/Progress/Progress.js create mode 100644 src/Components/Progress/index.js create mode 100644 src/Pages/Proposal/Components/ProposalVotingGraph.js diff --git a/src/Components/Progress/Progress.js b/src/Components/Progress/Progress.js new file mode 100644 index 00000000..84c82ca0 --- /dev/null +++ b/src/Components/Progress/Progress.js @@ -0,0 +1,97 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; +import { breakpoints } from 'consts'; + +const ProgressBar = styled.div` + position: relative; + margin-bottom: 8px; + width: 100%; + height: 30px; + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5) inset; + background-color: ${({ theme }) => theme.BACKGROUND_LIGHT}; +`; + +const ProgressValue = styled.span` + position: absolute; + left: ${({ start = 0 }) => start}%; + display: block; + width: ${({ value }) => (value > 100 ? 100 : value)}%; + height: ${({ height = 100 }) => height}%; + text-indent: -9999px; + background-color: ${({ color, theme }) => theme[color]}; +`; + +const KeyContainer = styled.div` + margin: 10px 0 0; + display: flex; + width: 100%; + gap: 1.6rem; + flex-direction: column; + + @media ${breakpoints.up('md')} { + flex-direction: row; + } +`; + +const Key = styled.div` + display: flex; + align-items: center; + flex-basis: calc(25% - 1.6rem); + gap: 0.8rem; + font-size: 1.4rem; + font-weight: 200; + line-height: 1.75; +`; + +const KeySquare = styled.span` + height: 100%; + max-height: 100%; + min-height: ${({ minHeight }) => minHeight}px; + width: 5px; + background-color: ${({ color, theme }) => theme[color]}; +`; + +const Progress = ({ data, keySquareHeight }) => ( + + + {data.map(p => ( + + ))} + + + + {data.map(p => ( + + + {typeof p.content === 'function' && p.content()} + + ))} + + +); + +Progress.propTypes = { + data: PropTypes.arrayOf( + PropTypes.shape({ + color: PropTypes.string, + content: PropTypes.func, + height: PropTypes.number, + start: PropTypes.number, + value: PropTypes.number, + }) + ).isRequired, + keySquareHeight: PropTypes.number, +}; + +Progress.defaultProps = { + keySquareHeight: 30, +}; + +export default Progress; diff --git a/src/Components/Progress/index.js b/src/Components/Progress/index.js new file mode 100644 index 00000000..70ffd511 --- /dev/null +++ b/src/Components/Progress/index.js @@ -0,0 +1 @@ +export { default } from './Progress'; diff --git a/src/Components/Summary/Summary.js b/src/Components/Summary/Summary.js index 76d4cd50..5c8abc57 100644 --- a/src/Components/Summary/Summary.js +++ b/src/Components/Summary/Summary.js @@ -144,11 +144,11 @@ const buildSummaryValue = (rowData, theme) => { }; const buildSummaryRow = (rowData, theme) => { - const { title, value, isJson } = rowData; + const { isJson, nobreak = false, title, value } = rowData; const valueMissing = value === undefined || value === null || value === ''; return ( - + {title}: {valueMissing ? '--' : buildSummaryValue(rowData, theme)} diff --git a/src/Components/index.js b/src/Components/index.js index 994a54a6..3e8b8bba 100644 --- a/src/Components/index.js +++ b/src/Components/index.js @@ -24,4 +24,5 @@ export { default as DropdownBtn } from './DropdownBtn'; export { default as Modal } from './Modal'; export { default as Input } from './Input'; export { default as InfiniteScroll } from './InfiniteScroll'; +export { default as Progress } from './Progress'; export * from './Layout'; diff --git a/src/Pages/Proposal/Components/ProposalInformation.js b/src/Pages/Proposal/Components/ProposalInformation.js index a4db395b..8f27ca08 100644 --- a/src/Pages/Proposal/Components/ProposalInformation.js +++ b/src/Pages/Proposal/Components/ProposalInformation.js @@ -21,11 +21,11 @@ const ProposalInformation = () => { const summaryData = [ { title: 'ID', value: proposalId }, - { title: 'Title', value: title }, + { title: 'Title', value: title, nobreak: true }, { title: 'Status', value: status }, { title: 'Proposer', value: moniker || address }, { title: 'Type', value: type }, - { title: 'Description', value: description }, + { title: 'Description', value: description, nobreak: true }, details && { title: 'Details', value: JSON.stringify(details), isJson: true }, ].filter(sd => sd); diff --git a/src/Pages/Proposal/Components/ProposalTimingProgressBar.js b/src/Pages/Proposal/Components/ProposalTimingProgressBar.js index bf5f3dbb..62a3932e 100644 --- a/src/Pages/Proposal/Components/ProposalTimingProgressBar.js +++ b/src/Pages/Proposal/Components/ProposalTimingProgressBar.js @@ -1,55 +1,8 @@ -import React, { Fragment } from 'react'; -import styled from 'styled-components'; -import { Content, Loading } from 'Components'; +import React from 'react'; +import { Content, Loading, Progress } from 'Components'; import { useGovernance } from 'redux/hooks'; -import { breakpoints } from 'consts'; import { formatDenom } from 'utils'; - -const ProgressBar = styled.div` - position: relative; - margin-bottom: 8px; - width: 100%; - height: 20px; - box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5) inset; - background-color: ${({ theme }) => theme.BACKGROUND_LIGHT}; -`; - -const ProgressValue = styled.span` - position: absolute; - left: 0; - display: block; - width: ${({ value }) => (value > 100 ? 100 : value)}%; - height: ${({ initial }) => initial && '50%'}; - max-height: 100%; - text-indent: -9999px; - background-color: ${({ initial, theme }) => (initial ? theme.CHART_PIE_A : theme.CHART_PIE_F)}; -`; - -const Key = styled.p` - display: flex; - align-items: center; - gap: 0.8rem; - margin: 0; - margin-bottom: 0.8rem; - - &:last-child() { - margin-bottom: 0; - } - - @media ${breakpoints.up('sm')} { - margin-right: 1.6rem; - - &:last-child() { - margin-right: 0; - } - } -`; - -const KeySquare = styled.span` - height: 30px; - width: 30px; - background-color: ${({ initial, theme }) => (initial ? theme.CHART_PIE_A : theme.CHART_PIE_F)}; -`; +import Big from 'big.js'; const ProposalTiming = () => { const { proposal, proposalLoading } = useGovernance(); @@ -60,28 +13,27 @@ const ProposalTiming = () => { const needed = timings?.deposit?.needed; const denom = timings?.deposit?.denom; + const getPercentage = (num = 0, den = 1) => new Big(num).div(den).times(100).toNumber(); + + const progressData = [ + { + color: 'CHART_PIE_C', + content: () => Current - {formatDenom(current, denom)}, + value: getPercentage(current, needed), + }, + { + color: 'CHART_PIE_F', + content: () => Initial - {formatDenom(initial, denom)}, + height: 50, + value: getPercentage(initial, needed), + }, + ]; + return ( {proposalLoading && } - {!proposalLoading && ( - - - - {formatDenom(current, denom)} - - - {formatDenom(initial, denom)} - - - - Initial - {formatDenom(initial, denom)} - - - Current - {formatDenom(current, denom)} - - - )} + {!proposalLoading && } ); }; diff --git a/src/Pages/Proposal/Components/ProposalVotingGraph.js b/src/Pages/Proposal/Components/ProposalVotingGraph.js new file mode 100644 index 00000000..9fd75704 --- /dev/null +++ b/src/Pages/Proposal/Components/ProposalVotingGraph.js @@ -0,0 +1,96 @@ +import React from 'react'; +import Big from 'big.js'; +import { useGovernance } from 'redux/hooks'; +import { Content, Loading, Progress } from 'Components'; +import { formatDenom, numberFormat } from 'utils'; + +const ProposalVotingGraph = () => { + const { proposalVotes, proposalVotesLoading } = useGovernance(); + + const { tally } = proposalVotes; + const total = formatDenom(...Object.values(tally?.total?.amount || {})); + + const getPercent = amt => + new Big(amt) + .div(tally?.total?.amount?.amount || 1) + .times(100) + .toNumber(); + + const yesAmount = tally?.yes?.amount; + const noAmount = tally?.no?.amount; + const noWithVetoAmount = tally?.noWithVeto?.amount; + const abstainAmount = tally?.abstain?.amount; + + const yesPercent = getPercent(yesAmount?.amount || 0); + const noPercent = getPercent(noAmount?.amount || 0); + const noWithVetoPercent = getPercent(noWithVetoAmount?.amount || 0); + const abstainPercent = getPercent(abstainAmount?.amount || 0); + + const noStart = yesPercent; + const noWithVetoStart = noStart + noPercent; + const abstainStart = noWithVetoStart + noWithVetoPercent; + + const progressData = [ + // yes + { + color: 'CHART_PIE_A', + content: () => ( +
+
Yes
+
{numberFormat(yesPercent)}%
+
{formatDenom(...Object.values(yesAmount || {}))}
+
+ ), + value: yesPercent, + }, + // no + { + color: 'CHART_PIE_H', + content: () => ( +
+
No
+
{numberFormat(noPercent)}%
+
{formatDenom(...Object.values(noAmount || {}))}
+
+ ), + start: noStart, + value: noPercent, + }, + // noWithVeto + { + color: 'CHART_PIE_C', + content: () => ( +
+
NoWithVeto
+
{numberFormat(noWithVetoPercent)}%
+
{formatDenom(...Object.values(noWithVetoAmount || {}))}
+
+ ), + start: noWithVetoStart, + value: noWithVetoPercent, + }, + // abstain + { + color: 'CHART_PIE_I', + content: () => ( +
+
Abstain
+
{numberFormat(abstainPercent)}%
+
{formatDenom(...Object.values(abstainAmount || {}))}
+
+ ), + start: abstainStart, + value: abstainPercent, + }, + ]; + + return ( + + {proposalVotesLoading && } + + {!proposalVotesLoading && } + + ); +}; + +export default ProposalVotingGraph; diff --git a/src/Pages/Proposal/Components/index.js b/src/Pages/Proposal/Components/index.js index a5ebc01b..0b6344c7 100644 --- a/src/Pages/Proposal/Components/index.js +++ b/src/Pages/Proposal/Components/index.js @@ -2,4 +2,5 @@ export { default as ProposalDeposits } from './ProposalDeposits'; export { default as ProposalInformation } from './ProposalInformation'; export { default as ProposalTimingProgressBar } from './ProposalTimingProgressBar'; export { default as ProposalTimingTable } from './ProposalTimingTable'; +export { default as ProposalVotingGraph } from './ProposalVotingGraph'; export { default as ProposalVotingTable } from './ProposalVotingTable'; diff --git a/src/Pages/Proposal/Proposal.js b/src/Pages/Proposal/Proposal.js index 35e70e53..039eca47 100644 --- a/src/Pages/Proposal/Proposal.js +++ b/src/Pages/Proposal/Proposal.js @@ -7,6 +7,7 @@ import { ProposalInformation, ProposalTimingProgressBar, ProposalTimingTable, + ProposalVotingGraph, ProposalVotingTable, } from './Components'; @@ -38,6 +39,9 @@ const Proposal = () => {
+
+ +
From 2fc997239488a4f24186427fd502be5c12b9beb3 Mon Sep 17 00:00:00 2001 From: AJ Webb Date: Wed, 21 Jul 2021 13:18:57 -1000 Subject: [PATCH 3/3] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15a977d2..59aa3598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Add Gov link to global nav #135 - Add proposal detail page and proposal info #136 - Add proposal timing detail and progress bar #137 +- Add proposal voting detail and progress bar #138 - Add proposol deposit card to detail page #139 ### Improvements