import * as React from "react";
import { compose } from "redux";
import { formatRoute } from "react-router-named-routes";
import { BigNumber, formatEther, bigNumberify, parseEther, TxHash } from "@joincivil/typescript-types";
import { TwoStepEthTransaction } from "@joincivil/core";
import {
AppealChallengeCommitVoteCard as AppealChallengeCommitVoteCardComponent,
AppealChallengeCommitVoteCardProps,
CommitVoteSuccessIcon,
ModalContent,
ModalUnorderedList,
ModalListItem,
ReviewVote,
ReviewVoteProps,
} from "@joincivil/components";
import { getFormattedTokenBalance, Parameters, urlConstants as links } from "@joincivil/utils";
import { routes } from "../../constants";
import { CivilHelper, CivilHelperContext } from "../../apis/CivilHelper";
import { fetchSalt } from "../../helpers/salt";
import { saveVote } from "../../helpers/vote";
import {
ChallengeContainerProps,
connectChallengePhase,
connectChallengeResults,
connectParameters,
} from "../utility/HigherOrderComponents";
import { InjectedTransactionStatusModalProps, hasTransactionStatusModals } from "../utility/TransactionStatusModalsHOC";
import { AppealChallengeDetailProps, ChallengeVoteState } from "./AppealChallengeDetail";
enum TransactionTypes {
APPROVE_VOTING_RIGHTS = "APPROVE_VOTING_RIGHTS",
COMMIT_VOTE = "COMMIT_VOTE",
}
const transactionLabels = {
[TransactionTypes.APPROVE_VOTING_RIGHTS]: "Approve Voting Rights",
[TransactionTypes.COMMIT_VOTE]: "Commit Vote",
};
const multiStepTransactionLabels = {
[TransactionTypes.APPROVE_VOTING_RIGHTS]: "1 of 2",
[TransactionTypes.COMMIT_VOTE]: "2 of 2",
};
const transactionSuccessContent = {
[TransactionTypes.APPROVE_VOTING_RIGHTS]: [undefined, undefined],
[TransactionTypes.COMMIT_VOTE]: [
<>
,
Your vote was committed!
>,
<>
Please keep your secret phrase safe. You will need it to confirm your vote. Votes can not be counted and rewards
can not be claimed unless you confirm them.
>,
],
};
const transactionRejectionContent = {
[TransactionTypes.APPROVE_VOTING_RIGHTS]: [
"Your vote was not committed",
"Before committing a vote, you need to confirm the approval of your voting token deposit in your MetaMask wallet.",
],
[TransactionTypes.COMMIT_VOTE]: [
"Your vote was not committed",
"To commit a vote, you need to confirm the transaction in your MetaMask wallet.",
],
};
const transactionErrorContent = {
[TransactionTypes.APPROVE_VOTING_RIGHTS]: [
"The was an problem with commiting your vote",
<>
Please check the following and retry your transaction
The number of tokens you are voting with does not exceed your available balance.
>,
],
[TransactionTypes.COMMIT_VOTE]: [
"The was an problem with commiting your vote",
<>
Please retry your transaction
>,
],
};
const transactionStatusModalConfig = {
transactionLabels,
multiStepTransactionLabels,
transactionSuccessContent,
transactionRejectionContent,
transactionErrorContent,
};
interface AppealCommitCardKeyState {
key: number;
}
const AppealChallengeCommitVoteCard = compose<
React.ComponentClass>
>(
connectChallengePhase,
connectChallengeResults,
connectParameters,
)(AppealChallengeCommitVoteCardComponent);
class AppealChallengeCommitVote extends React.Component<
AppealChallengeDetailProps & InjectedTransactionStatusModalProps,
ChallengeVoteState & AppealCommitCardKeyState
> {
public static contextType = CivilHelperContext;
public context: CivilHelper;
constructor(props: any) {
super(props);
this.state = {
isReviewVoteModalOpen: false,
voteOption: undefined,
numTokens: undefined,
key: new Date().valueOf(),
};
}
public componentWillMount(): void {
this.props.setTransactions(this.getTransactions());
this.props.setHandleTransactionSuccessButtonClick(this.handleCommitVoteSuccessClose);
}
public render(): JSX.Element | null {
const { appealChallenge } = this.props;
const endTime = appealChallenge.poll.commitEndDate.toNumber();
const phaseLength = this.props.parameters.get(Parameters.challengeAppealCommitLen);
const tokenBalance = parseFloat(formatEther(this.props.balance || bigNumberify(0)));
const votingTokenBalance = parseFloat(formatEther(this.props.votingBalance || bigNumberify(0)));
const tokenBalanceDisplay = this.props.balance ? getFormattedTokenBalance(this.props.balance) : "";
const votingTokenBalanceDisplay = this.props.votingBalance
? getFormattedTokenBalance(this.props.votingBalance)
: "";
const salt = fetchSalt(this.props.challengeID, this.props.user);
const userHasCommittedVote =
this.props.userAppealChallengeData && !!this.props.userAppealChallengeData.didUserCommit;
const props = {
endTime,
phaseLength,
challengeID: this.props.challengeID.toString(),
userHasCommittedVote,
onCommitMaxTokens: () => this.commitMaxTokens(),
tokenBalance,
votingTokenBalance,
tokenBalanceDisplay,
votingTokenBalanceDisplay,
salt,
voteOption: this.state.voteOption,
numTokens: this.state.numTokens,
onInputChange: this.updateCommitVoteState,
onReviewVote: this.handleReviewVote,
appealChallengeID: this.props.appealChallengeID.toString(),
appealGranted: this.props.appeal.appealGranted,
key: this.state.key,
onMobileTransactionClick: this.props.onMobileTransactionClick,
appealGrantedStatementURI: this.props.appeal.appealGrantedStatementURI,
faqURL: links.FAQ_VOTING_SECTION,
};
return (
<>
{this.renderReviewVoteModal()}
>
);
}
private commitMaxTokens(): void {
let numTokens: BigNumber;
if (!this.props.votingBalance!.isZero()) {
numTokens = this.props.votingBalance!;
} else {
numTokens = this.props.balance!.add(this.props.votingBalance!);
}
const numTokensString = formatEther(numTokens);
this.setState(() => ({ numTokens: numTokensString }));
}
private renderReviewVoteModal(): JSX.Element {
const { challenge } = this.props;
const transactions = this.getTransactions();
const relativeListingDetailURL = formatRoute(routes.LISTING, { listingAddress: this.props.listingAddress });
const listingDetailURL = `https://${window.location.hostname}${relativeListingDetailURL}`;
const salt = fetchSalt(this.props.challengeID, this.props.user);
const props: ReviewVoteProps = {
newsroomName: "this newsroom",
listingDetailURL,
challengeID: this.props.appealChallengeID.toString(),
open: this.state.isReviewVoteModalOpen,
salt,
numTokens: this.state.numTokens,
voteOption: this.state.voteOption,
userAccount: this.props.user,
commitEndDate: challenge.poll.commitEndDate.toNumber(),
revealEndDate: challenge.poll.revealEndDate.toNumber(),
transactions,
postExecuteTransactions: this.closeReviewVoteModal,
handleClose: this.closeReviewVoteModal,
gasFaqURL: links.FAQ_GAS,
votingContractFaqURL: links.FAQ_WHAT_IS_PLCR_CONTRACT,
};
return ;
}
private getTransactions = (): any[] => {
return [
{
transaction: async () => {
this.props.updateTransactionStatusModalsState({
isWaitingTransactionModalOpen: true,
isTransactionProgressModalOpen: false,
isTransactionSuccessModalOpen: false,
transactionType: TransactionTypes.APPROVE_VOTING_RIGHTS,
});
return this.approveVotingRights();
},
handleTransactionHash: (txHash: TxHash) => {
this.props.updateTransactionStatusModalsState({
isWaitingTransactionModalOpen: false,
isTransactionProgressModalOpen: true,
});
},
postTransaction: () => {
this.props.updateTransactionStatusModalsState({
isWaitingTransactionModalOpen: false,
isTransactionProgressModalOpen: false,
isTransactionSuccessModalOpen: true,
});
},
handleTransactionError: this.props.handleTransactionError,
},
{
transaction: async () => {
this.props.updateTransactionStatusModalsState({
isWaitingTransactionModalOpen: true,
isTransactionProgressModalOpen: false,
isTransactionSuccessModalOpen: false,
transactionType: TransactionTypes.COMMIT_VOTE,
});
return this.commitVoteOnChallenge();
},
handleTransactionHash: (txHash: TxHash) => {
this.props.updateTransactionStatusModalsState({
isWaitingTransactionModalOpen: false,
isTransactionProgressModalOpen: true,
});
},
postTransaction: () => {
this.props.updateTransactionStatusModalsState({
isWaitingTransactionModalOpen: false,
isTransactionProgressModalOpen: false,
isTransactionSuccessModalOpen: true,
});
},
handleTransactionError: this.props.handleTransactionError,
},
];
};
private updateCommitVoteState = (data: any, callback?: () => void): void => {
if (callback) {
this.setState({ ...data }, callback);
} else {
this.setState({ ...data });
}
};
private handleReviewVote = () => {
this.setState({ isReviewVoteModalOpen: true });
};
private closeReviewVoteModal = () => {
this.setState({ isReviewVoteModalOpen: false });
};
private handleCommitVoteSuccessClose = () => {
this.props.updateTransactionStatusModalsState({ isTransactionSuccessModalOpen: false });
this.setState({ isReviewVoteModalOpen: false, key: new Date().valueOf() });
};
private approveVotingRights = async (): Promise | void> => {
const numTokens = parseEther(this.state.numTokens!.toString());
return this.context.approveVotingRightsForCommit(numTokens);
};
private commitVoteOnChallenge = async (): Promise> => {
const voteOption: BigNumber = new BigNumber(this.state.voteOption as string);
const pollID = this.props.appealChallengeID;
const saltStr = fetchSalt(pollID, this.props.user);
const salt: BigNumber = new BigNumber(saltStr as string);
const numTokens: BigNumber = parseEther(this.state.numTokens!.toString());
saveVote(pollID, this.props.user, voteOption);
return this.context.commitVote(pollID, voteOption, salt, numTokens);
};
}
export default compose>(
hasTransactionStatusModals(transactionStatusModalConfig),
)(AppealChallengeCommitVote);