diff --git a/app/components/views/ProposalDetails/ChooseVoteOption.js b/app/components/views/ProposalDetails/ChooseVoteOption.js new file mode 100644 index 0000000000..acb1892d6d --- /dev/null +++ b/app/components/views/ProposalDetails/ChooseVoteOption.js @@ -0,0 +1,125 @@ +import { FormattedMessage as T } from "react-intl"; +import { PassphraseModalButton } from "buttons"; +import { fetchMachine } from "stateMachines/FetchStateMachine"; +import { useMachine } from "@xstate/react"; +import { StakeyBounceXs } from "indicators"; +import { useDispatch } from "react-redux"; +import { useState } from "react"; +import { ProposalError } from "./helpers" +import * as gov from "actions/GovernanceActions"; + +const VoteOption = ({ value, description, onClick, checked }) => ( +
+ +
+); + +function UpdateVoteChoiceModalButton({ onSubmit, newVoteChoice, eligibleTicketCount }) { + return ( + + +
+
+ {newVoteChoice} +
+ + } + modalDescription={ + + } + disabled={!newVoteChoice} + onSubmit={onSubmit} + buttonLabel={} + /> +)}; + +function getError(error) { + if (!error) return; + if (typeof error === "string") return error; + if (typeof error === "object") { + if (error.message) return error.message; + return JSON.stringify(error); + } +} + +function ChooseVoteOption({ + viewedProposalDetails, voteOptions, currentVoteChoice, votingComplete, eligibleTicketCount +}) { + const [ newVoteChoice, setVoteOption ] = useState(null); + + const dispatch = useDispatch(); + const onUpdateVoteChoice = (privatePassphrase) => dispatch( + gov.updateVoteChoice(viewedProposalDetails, newVoteChoice, privatePassphrase) + ); + const [ state, send ] = useMachine(fetchMachine, { + actions: { + initial: () => ({}), + load: (context, event) => { + const { privatePassphrase } = event; + if(!newVoteChoice) return; + onUpdateVoteChoice(privatePassphrase) + .then(() => send("RESOLVE")) + .catch(error => send({ type: "REJECT", error })); + } + } + }); + + const error = state && state.context && getError(state.context.error); + const ChooseOptions = () => ( + <> +
+
+
+ { voteOptions.map(o => { + return currentVoteChoice === "abstain" && setVoteOption(o.id) } + checked={ newVoteChoice ? newVoteChoice === o.id : currentVoteChoice !== "abstain" ? currentVoteChoice.id === o.id : null } + /> + })} +
+
+ { !votingComplete && + send({type: "FETCH", privatePassphrase }), eligibleTicketCount + }} /> + } + + ); + + switch (state.value) { + case "idle": + return ; + case "loading": + return ( +
+ + ... +
+ ); + case "success": + return ; + case "failure": + return ; + } +} + +export default ChooseVoteOption; diff --git a/app/components/views/ProposalDetails/Page.js b/app/components/views/ProposalDetails/Page.js index 490ba5c42a..491f86e214 100644 --- a/app/components/views/ProposalDetails/Page.js +++ b/app/components/views/ProposalDetails/Page.js @@ -4,59 +4,58 @@ import { PoliteiaLink, VerticalAccordion } from "shared"; import { ProposalNotVoting, NoTicketsVotingInfo, OverviewField, OverviewVotingProgressInfo, NoElligibleTicketsVotingInfo, UpdatingVoteChoice, TimeValue, - ChooseVoteOption, ProposalText, ProposalAbandoned + ProposalText, ProposalAbandoned } from "./helpers"; +import ChooseVoteOption from "./ChooseVoteOption"; import { VOTESTATUS_ACTIVEVOTE, VOTESTATUS_FINISHEDVOTE, PROPOSALSTATUS_ABANDONED } from "actions/GovernanceActions"; import { useSelector } from "react-redux"; +import { useState } from "react" import * as sel from "selectors"; -function ProposalDetails ({ viewedProposalDetails, goBackHistory, - showPurchaseTicketsPage, setVoteOption, onUpdateVoteChoice, - newVoteChoice, updateVoteChoiceAttempt, text, showWalletEligibleTickets, - onToggleWalletEligibleTickets }) { - +function ProposalDetails ({ + viewedProposalDetails, showPurchaseTicketsPage, setVoteOption, + newVoteChoice, updateVoteChoiceAttempt, text, goBackHistory, + onUpdateVoteChoice +}) { + const { + creator, timestamp, endTimestamp, currentVoteChoice, hasEligibleTickets, + name, token, voteStatus, proposalStatus, voteOptions, voteCounts, + version, quorumMinimumVotes, walletEligibleTickets + } = viewedProposalDetails; + const eligibleTicketCount = viewedProposalDetails.walletEligibleTickets && viewedProposalDetails.walletEligibleTickets.length; const tsDate = useSelector(sel.tsDate); const hasTickets = useSelector(sel.hasTickets); - const { name, token, voteStatus, proposalStatus, voteOptions, voteCounts, - creator, timestamp, endTimestamp, currentVoteChoice, hasEligibleTickets, - version, quorumMinimumVotes, walletEligibleTickets } = viewedProposalDetails; + const [ showWalletEligibleTickets, toggleWalletEligibleTickets ] = useState(false); - const getVoteInfo = ({ - voteStatus, voteOptions, onUpdateVoteChoice, setVoteOption, newVoteChoice, - eligibleTicketCount,currentVoteChoice, showPurchaseTicketsPage - }) => { + // getVoteInfo is an auxiliar function to get the properly vote info component. + function getVoteInfo () { if (voteStatus === VOTESTATUS_FINISHEDVOTE) { return ; } if (voteStatus === VOTESTATUS_ACTIVEVOTE) { - if (updateVoteChoiceAttempt) { - return ; - } if (!hasTickets) { return ; } if (!hasEligibleTickets) { return ; } - - return ; + return ; } return ; }; - - const eligibleTicketCount = viewedProposalDetails.walletEligibleTickets && viewedProposalDetails.walletEligibleTickets.length; let voteInfo = null; // Check if proposal is abandoned. If it is not we check its vote status if (proposalStatus === PROPOSALSTATUS_ABANDONED) { voteInfo = ; } else { voteInfo = getVoteInfo({ - voteStatus, voteOptions, onUpdateVoteChoice, setVoteOption, newVoteChoice, - eligibleTicketCount,currentVoteChoice, showPurchaseTicketsPage + voteStatus, voteOptions, setVoteOption, newVoteChoice, eligibleTicketCount, + currentVoteChoice, showPurchaseTicketsPage, hasTickets, hasEligibleTickets }); } @@ -99,7 +98,7 @@ function ProposalDetails ({ viewedProposalDetails, goBackHistory,
} show={showWalletEligibleTickets} - onToggleAccordion={onToggleWalletEligibleTickets} + onToggleAccordion={ () => toggleWalletEligibleTickets(!showWalletEligibleTickets)} className="proposal-details-wallet-eligible-tickets" > {walletEligibleTickets.map((t, i) => ( diff --git a/app/components/views/ProposalDetails/UpdateVoteChoiceModalButton.js b/app/components/views/ProposalDetails/UpdateVoteChoiceModalButton.js deleted file mode 100644 index 7693ca4502..0000000000 --- a/app/components/views/ProposalDetails/UpdateVoteChoiceModalButton.js +++ /dev/null @@ -1,25 +0,0 @@ -import { FormattedMessage as T } from "react-intl"; -import { PassphraseModalButton } from "buttons"; - - -export default ({ onUpdateVoteChoice, newVoteChoice, eligibleTicketCount }) => ( - - -
-
- {newVoteChoice} -
- } - modalDescription={ - } - disabled={!newVoteChoice} - onSubmit={onUpdateVoteChoice} - buttonLabel={} - /> -); diff --git a/app/components/views/ProposalDetails/helpers.js b/app/components/views/ProposalDetails/helpers.js index a9bf615036..922d9362a3 100644 --- a/app/components/views/ProposalDetails/helpers.js +++ b/app/components/views/ProposalDetails/helpers.js @@ -1,9 +1,8 @@ import { shell } from "electron"; import { KeyBlueButton, InvisibleConfirmPoliteiaModalButton } from "buttons"; import { FormattedMessage as T } from "react-intl"; -import { StakeyBounceXs, VotingProgress, PoliteiaLoading } from "indicators"; +import { VotingProgress, PoliteiaLoading } from "indicators"; import { showCheck } from "helpers"; -import UpdateVoteChoiceModalButton from "./UpdateVoteChoiceModalButton"; import { default as ReactMarkdown } from "react-markdown"; import { FormattedRelative } from "shared"; @@ -32,36 +31,6 @@ export const NoElligibleTicketsVotingInfo = ({ showPurchaseTicketsPage }) => ( ); -const VoteOption = ({ value, description, onClick, checked }) => ( -
- onClick(value) : null} - value={value} checked={checked} /> -
-); - -export const ChooseVoteOption = ({ voteOptions, onUpdateVoteChoice, setVoteOption, newVoteChoice, currentVoteChoice, votingComplete, eligibleTicketCount }) => ( - <> -
-
-
- {voteOptions.map(o => ( - - ))} -
-
- {!votingComplete && } - -); - -export const UpdatingVoteChoice = () => ( -
- - ... -
-); - export const OverviewField = showCheck(( { label, value } ) => (
{label}:
diff --git a/app/components/views/ProposalDetails/index.js b/app/components/views/ProposalDetails/index.js index c2ce501150..edd2eb43cc 100644 --- a/app/components/views/ProposalDetails/index.js +++ b/app/components/views/ProposalDetails/index.js @@ -16,8 +16,6 @@ function ProposalDetails() { let viewedProposalDetails; let text = ""; - const [ showWalletEligibleTickets, toggleShowWalletEligibleTickets ] = useState(false); - const [ newVoteChoice, setVoteOption ] = useState(null); const { token } = useParams(); const proposalsDetails = useSelector(sel.proposalsDetails); const getProposalError = useSelector(sel.getProposalError); @@ -25,12 +23,6 @@ function ProposalDetails() { const getProposalDetails = (token) => dispatch(gov.getProposalDetails(token)); const goBackHistory = () => dispatch(cli.goBackHistory()); - async function onUpdateVoteChoice(privatePassphrase) { - if (!viewedProposalDetails || !newVoteChoice) return; - await dispatch(gov.updateVoteChoice(viewedProposalDetails, newVoteChoice, privatePassphrase)); - return true; - } - const [ state, send ] = useMachine(fetchMachine, { actions: { initial: () => { @@ -55,11 +47,7 @@ function ProposalDetails() { text += politeiaMarkdownIndexMd(f.payload); } }); - return ; + return ; case "failure": return ; default: diff --git a/app/reducers/governance.js b/app/reducers/governance.js index efb14dcd9d..ddd6d58230 100644 --- a/app/reducers/governance.js +++ b/app/reducers/governance.js @@ -55,20 +55,14 @@ export default function governance(state = {}, action) { }, proposals: { ...action.proposals } }; - case UPDATEVOTECHOICE_ATTEMPT: - return { ...state, updateVoteChoiceAttempt: true }; case UPDATEVOTECHOICE_SUCCESS: return { ...state, proposals: { ...action.proposals }, - updateVoteChoiceAttempt: false, proposalsDetails: { ...state.proposalsDetails, [action.token]: { ...action.proposal } } }; - - case UPDATEVOTECHOICE_FAILED: - return { ...state, updateVoteChoiceAttempt: false }; case WALLETREADY: return { ...state, lastPoliteiaAccessTime: action.lastPoliteiaAccessTime, diff --git a/app/selectors.js b/app/selectors.js index 702d66544c..37292f30db 100644 --- a/app/selectors.js +++ b/app/selectors.js @@ -996,7 +996,6 @@ export const allAgendas = createSelector( export const treasuryBalance = get([ "grpc", "treasuryBalance" ]); -export const updateVoteChoiceAttempt = get([ "governance", "updateVoteChoiceAttempt" ]); export const proposals = get([ "governance", "proposals" ]); export const proposallistpagesize = get([ "governance", "proposallistpagesize" ]); export const getProposalsAttempt = get([ "governance", "getProposalsAttempt" ]); diff --git a/app/stateMachines/FetchStateMachine.js b/app/stateMachines/FetchStateMachine.js index e983c63e31..bab58d0432 100644 --- a/app/stateMachines/FetchStateMachine.js +++ b/app/stateMachines/FetchStateMachine.js @@ -4,7 +4,8 @@ export const fetchMachine = Machine({ id: "fetch", initial: "idle", context: { - retries: 0 + retries: 0, + error: null }, states: { idle: { @@ -18,7 +19,12 @@ export const fetchMachine = Machine({ entry: [ "load" ], on: { RESOLVE: "success", - REJECT: "failure" + REJECT: { + target: "failure", + actions: assign({ + error: (context, event) => event.error + }) + } } }, success: {