Cancel / Refund proposal (#100)
* Cancel / refund modal for proposals. Fix some states where frozen contract still allowed interaction. * Add more refund states. Move styles to less. * Fix tsc, simplify logic
This commit is contained in:
parent
8be518fff7
commit
75f0b72022
|
@ -25,6 +25,7 @@ class CreateFlowPreview extends React.Component<Props> {
|
|||
banner
|
||||
/>
|
||||
<ProposalDetail
|
||||
account="0x0"
|
||||
proposalId="preview"
|
||||
fetchProposal={() => null}
|
||||
proposal={proposal}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
border: 2px dashed #d9d9d9;
|
||||
padding: 3rem;
|
||||
border-radius: 8px;
|
||||
|
|
|
@ -89,7 +89,9 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
if (proposal) {
|
||||
const { crowdFund } = proposal;
|
||||
const isFundingOver =
|
||||
crowdFund.isRaiseGoalReached || crowdFund.deadline < Date.now();
|
||||
crowdFund.isRaiseGoalReached ||
|
||||
crowdFund.deadline < Date.now() ||
|
||||
crowdFund.isFrozen;
|
||||
const isDisabled = isFundingOver || !!amountError || !amountFloat || isPreview;
|
||||
const remainingEthNum = parseFloat(
|
||||
web3.utils.fromWei(crowdFund.target.sub(crowdFund.funded), 'ether'),
|
||||
|
@ -150,7 +152,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
) : (
|
||||
<>
|
||||
<Icon type="close-circle-o" />
|
||||
<span>Proposal didn’t reach target</span>
|
||||
<span>Proposal didn’t get funded</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Modal, Alert } from 'antd';
|
||||
import { ProposalWithCrowdFund } from 'modules/proposals/reducers';
|
||||
import { web3Actions } from 'modules/web3';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
||||
interface OwnProps {
|
||||
proposal: ProposalWithCrowdFund;
|
||||
isVisible: boolean;
|
||||
handleClose(): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
isRefundActionPending: AppState['web3']['isRefundActionPending'];
|
||||
refundActionError: AppState['web3']['refundActionError'];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
triggerRefund: typeof web3Actions['triggerRefund'];
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps & OwnProps;
|
||||
|
||||
class CancelModal extends React.Component<Props> {
|
||||
componentDidUpdate() {
|
||||
if (this.props.proposal.crowdFund.isFrozen) {
|
||||
this.props.handleClose();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { proposal, isVisible, isRefundActionPending, refundActionError } = this.props;
|
||||
const hasBeenFunded = proposal.crowdFund.isRaiseGoalReached;
|
||||
const hasContributors = !!proposal.crowdFund.contributors.length;
|
||||
const disabled = isRefundActionPending;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<>Cancel proposal</>}
|
||||
visible={isVisible}
|
||||
okText="Confirm"
|
||||
cancelText="Never mind"
|
||||
onOk={this.cancelProposal}
|
||||
onCancel={this.closeModal}
|
||||
okButtonProps={{ type: 'danger', loading: disabled }}
|
||||
cancelButtonProps={{ disabled }}
|
||||
>
|
||||
{hasBeenFunded ? (
|
||||
<p>
|
||||
Are you sure you would like to issue a refund?{' '}
|
||||
<strong>This cannot be undone</strong>. Once you issue a refund, all
|
||||
contributors will be able to receive a refund of the remaining proposal
|
||||
balance.
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
Are you sure you would like to cancel this proposal?{' '}
|
||||
<strong>This cannot be undone</strong>. Once you cancel it, all contributors
|
||||
will be able to receive refunds.
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
Canceled proposals cannot be deleted and will still be viewable by contributors
|
||||
or anyone with a direct link. However, they will be de-listed everywhere else on
|
||||
Grant.io.
|
||||
</p>
|
||||
{hasContributors && (
|
||||
<p>
|
||||
Should you choose to cancel, we highly recommend posting an update to let your
|
||||
contributors know why you’ve decided to do so.
|
||||
</p>
|
||||
)}
|
||||
{refundActionError && (
|
||||
<Alert
|
||||
type="error"
|
||||
message={`Failed to ${hasBeenFunded ? 'refund' : 'cancel'} proposal`}
|
||||
description={refundActionError}
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private closeModal = () => {
|
||||
if (!this.props.isRefundActionPending) {
|
||||
this.props.handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
private cancelProposal = () => {
|
||||
this.props.triggerRefund(this.props.proposal.crowdFundContract);
|
||||
};
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, OwnProps, AppState>(
|
||||
state => ({
|
||||
isRefundActionPending: state.web3.isRefundActionPending,
|
||||
refundActionError: state.web3.refundActionError,
|
||||
}),
|
||||
{
|
||||
triggerRefund: web3Actions.triggerRefund,
|
||||
},
|
||||
)(CancelModal);
|
|
@ -7,6 +7,7 @@ import { web3Actions } from 'modules/web3';
|
|||
import { AppState } from 'store/reducers';
|
||||
import Web3Container, { Web3RenderProps } from 'lib/Web3Container';
|
||||
import UnitDisplay from 'components/UnitDisplay';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
|
||||
interface OwnProps {
|
||||
proposal: ProposalWithCrowdFund;
|
||||
|
@ -38,6 +39,19 @@ export class Milestones extends React.Component<Props> {
|
|||
milestoneActionError,
|
||||
} = this.props;
|
||||
const { crowdFund } = proposal;
|
||||
|
||||
if (!crowdFund.isRaiseGoalReached) {
|
||||
return (
|
||||
<Placeholder
|
||||
title="Milestone governance isn’t available yet"
|
||||
subtitle={`
|
||||
Milestone history and voting status will be displayed here
|
||||
once the project has been funded
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const contributor = crowdFund.contributors.find(c => c.address === accounts[0]);
|
||||
const isTrustee = crowdFund.trustees.includes(accounts[0]);
|
||||
const firstMilestone = crowdFund.milestones[0];
|
||||
|
|
|
@ -6,6 +6,7 @@ import Web3Container, { Web3RenderProps } from 'lib/Web3Container';
|
|||
import { web3Actions } from 'modules/web3';
|
||||
import { AppState } from 'store/reducers';
|
||||
import classnames from 'classnames';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
|
||||
interface OwnProps {
|
||||
proposal: ProposalWithCrowdFund;
|
||||
|
@ -32,16 +33,63 @@ class GovernanceRefunds extends React.Component<Props> {
|
|||
render() {
|
||||
const { proposal, account, isRefundActionPending, refundActionError } = this.props;
|
||||
const { crowdFund } = proposal;
|
||||
const isStillFunding =
|
||||
!crowdFund.isRaiseGoalReached && crowdFund.deadline > Date.now();
|
||||
|
||||
if (isStillFunding && !crowdFund.isFrozen) {
|
||||
return (
|
||||
<Placeholder
|
||||
title="Refund governance isn’t available yet"
|
||||
subtitle={`
|
||||
Refund voting and status will be displayed here once the
|
||||
project has been funded, or if it the project is canceled
|
||||
or fails to receive funding
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Cyclomatic complexity is too damn high here. This state should be
|
||||
// figured out on the backend and enumerated, not calculated in this component.
|
||||
const contributor = crowdFund.contributors.find(c => c.address === account);
|
||||
const isTrustee = crowdFund.trustees.includes(account);
|
||||
const hasVotedForRefund = contributor && contributor.refundVote;
|
||||
const hasRefunded = contributor && contributor.refunded;
|
||||
const refundPct = crowdFund.percentVotingForRefund;
|
||||
const didFundingFail =
|
||||
!crowdFund.isRaiseGoalReached && crowdFund.deadline < Date.now();
|
||||
|
||||
let text;
|
||||
let button;
|
||||
if (!isTrustee && contributor) {
|
||||
if (refundPct < 50) {
|
||||
let canRefund = false;
|
||||
if (hasRefunded) {
|
||||
return (
|
||||
<Alert
|
||||
type="success"
|
||||
message="Your refund has been processed"
|
||||
description={`
|
||||
We apologize for any inconvenience this propsal has caused you. Please
|
||||
let us know if there's anything we could have done to improve your
|
||||
experience.
|
||||
`}
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
} else if (refundPct > 50) {
|
||||
text = `
|
||||
The majority of funders have voted for a refund. Click below
|
||||
to receive your refund.
|
||||
`;
|
||||
canRefund = true;
|
||||
} else if (didFundingFail || crowdFund.isFrozen) {
|
||||
text = `
|
||||
The project was either canceled, or failed to reach its funding
|
||||
target before the deadline. Click below to receive a refund of
|
||||
your contribution.
|
||||
`;
|
||||
canRefund = true;
|
||||
} else {
|
||||
text = `
|
||||
As a funder of this project, you have the right to vote for a refund. If the
|
||||
amount of funds contributed by refund voters exceeds half of the project's
|
||||
|
@ -60,47 +108,38 @@ class GovernanceRefunds extends React.Component<Props> {
|
|||
onClick: () => this.voteRefund(true),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (hasRefunded) {
|
||||
return (
|
||||
<Alert
|
||||
type="success"
|
||||
message="Your refund has been processed"
|
||||
description={`
|
||||
We apologize for any inconvenience this propsal has caused you. Please
|
||||
let us know if there's anything we could have done to improve your
|
||||
experience.
|
||||
`}
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
text = (
|
||||
<>
|
||||
The majority of funders have voted for a refund. Click below to receive your
|
||||
refund.
|
||||
{!crowdFund.isFrozen && (
|
||||
<Alert
|
||||
style={{ marginTop: '1rem' }}
|
||||
type="info"
|
||||
message={`
|
||||
}
|
||||
|
||||
if (canRefund) {
|
||||
text = (
|
||||
<>
|
||||
{text}
|
||||
{!crowdFund.isFrozen && (
|
||||
<Alert
|
||||
style={{ marginTop: '1rem' }}
|
||||
type="info"
|
||||
showIcon
|
||||
message={`
|
||||
This will require multiple transactions to process, sorry
|
||||
for the inconvenience
|
||||
`}
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
button = {
|
||||
text: 'Get your refund',
|
||||
type: 'primary',
|
||||
onClick: () => this.withdrawRefund(),
|
||||
};
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
button = {
|
||||
text: 'Get your refund',
|
||||
type: 'primary',
|
||||
onClick: () => this.withdrawRefund(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (refundPct < 50) {
|
||||
if (crowdFund.isFrozen || didFundingFail) {
|
||||
text = `
|
||||
The project failed to receive funding or was canceled. Contributors are
|
||||
open to refund their contributions.
|
||||
`;
|
||||
} else if (refundPct < 50) {
|
||||
text = `
|
||||
Funders can vote to request refunds. If the amount of funds contributed by
|
||||
refund voters exceeds half of the funds contributed, all funders will be able
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import GovernanceMilestones from './Milestones';
|
||||
import GovernanceRefunds from './Refunds';
|
||||
import { ProposalWithCrowdFund } from 'modules/proposals/reducers';
|
||||
import Placeholder from 'components/Placeholder';
|
||||
import './style.less';
|
||||
|
||||
interface Props {
|
||||
|
@ -12,29 +11,15 @@ interface Props {
|
|||
export default class ProposalGovernance extends React.Component<Props> {
|
||||
render() {
|
||||
const { proposal } = this.props;
|
||||
|
||||
if (!proposal.crowdFund.isRaiseGoalReached) {
|
||||
return (
|
||||
<Placeholder
|
||||
style={{ minHeight: '220px' }}
|
||||
title="Governance isn’t available yet"
|
||||
subtitle={`
|
||||
Milestone history and voting will be displayed here once the
|
||||
project has been funded
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ProposalGovernance">
|
||||
<div style={{ flex: 1 }}>
|
||||
<h2 style={{ marginBottom: '1rem' }}>Milestone Voting</h2>
|
||||
<div className="ProposalGovernance-section">
|
||||
<h2 className="ProposalGovernance-section-title">Milestone Voting</h2>
|
||||
<GovernanceMilestones proposal={proposal} />
|
||||
</div>
|
||||
<div className="ProposalGovernance-divider" />
|
||||
<div style={{ flex: 1 }}>
|
||||
<h2 style={{ marginBottom: '1rem' }}>Refunds</h2>
|
||||
<div className="ProposalGovernance-section">
|
||||
<h2 className="ProposalGovernance-section-title">Refunds</h2>
|
||||
<GovernanceRefunds proposal={proposal} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,15 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-section {
|
||||
flex: 1;
|
||||
|
||||
&-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
width: 1px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
|
|
|
@ -7,16 +7,16 @@ import { bindActionCreators, Dispatch } from 'redux';
|
|||
import { AppState } from 'store/reducers';
|
||||
import { ProposalWithCrowdFund } from 'modules/proposals/reducers';
|
||||
import { getProposal } from 'modules/proposals/selectors';
|
||||
import { Spin, Tabs, Icon } from 'antd';
|
||||
import { Spin, Tabs, Icon, Dropdown, Menu, Button } from 'antd';
|
||||
import CampaignBlock from './CampaignBlock';
|
||||
import TeamBlock from './TeamBlock';
|
||||
import Milestones from './Milestones';
|
||||
|
||||
import CommentsTab from './Comments';
|
||||
import UpdatesTab from './Updates';
|
||||
import GovernanceTab from './Governance';
|
||||
import ContributorsTab from './Contributors';
|
||||
// import CommunityTab from './Community';
|
||||
import CancelModal from './CancelModal';
|
||||
import './style.less';
|
||||
import classnames from 'classnames';
|
||||
import { withRouter } from 'react-router';
|
||||
|
@ -36,11 +36,16 @@ interface DispatchProps {
|
|||
fetchProposal: proposalActions.TFetchProposal;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps & OwnProps;
|
||||
interface Web3Props {
|
||||
account: string;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps & Web3Props & OwnProps;
|
||||
|
||||
interface State {
|
||||
isBodyExpanded: boolean;
|
||||
isBodyOverflowing: boolean;
|
||||
isCancelOpen: boolean;
|
||||
bodyId: string;
|
||||
}
|
||||
|
||||
|
@ -48,6 +53,7 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
state: State = {
|
||||
isBodyExpanded: false,
|
||||
isBodyOverflowing: false,
|
||||
isCancelOpen: false,
|
||||
bodyId: `body-${Math.floor(Math.random() * 1000000)}`,
|
||||
};
|
||||
|
||||
|
@ -71,14 +77,37 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { proposal, isPreview } = this.props;
|
||||
const { isBodyExpanded, isBodyOverflowing, bodyId } = this.state;
|
||||
const { proposal, isPreview, account } = this.props;
|
||||
const { isBodyExpanded, isBodyOverflowing, isCancelOpen, bodyId } = this.state;
|
||||
const showExpand = !isBodyExpanded && isBodyOverflowing;
|
||||
|
||||
if (!proposal) {
|
||||
return <Spin />;
|
||||
} else {
|
||||
const { crowdFund } = proposal;
|
||||
const isTrustee = crowdFund.trustees.includes(account);
|
||||
const hasBeenFunded = crowdFund.isRaiseGoalReached;
|
||||
const isProposalActive = !hasBeenFunded && crowdFund.deadline > Date.now();
|
||||
const canRefund = (hasBeenFunded || isProposalActive) && !crowdFund.isFrozen;
|
||||
|
||||
const adminMenu = isTrustee && (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() => alert('Sorry, not yet implemented!')}
|
||||
disabled={!isProposalActive}
|
||||
>
|
||||
Edit proposal
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
style={{ color: canRefund ? '#e74c3c' : undefined }}
|
||||
onClick={this.openCancelModal}
|
||||
disabled={!canRefund}
|
||||
>
|
||||
{hasBeenFunded ? 'Refund contributors' : 'Cancel proposal'}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="Proposal">
|
||||
<div className="Proposal-top">
|
||||
|
@ -105,6 +134,20 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isTrustee && (
|
||||
<div className="Proposal-top-main-menu">
|
||||
<Dropdown
|
||||
overlay={adminMenu}
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button>
|
||||
<span>Actions</span>
|
||||
<Icon type="down" style={{ marginRight: '-0.25rem' }} />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="Proposal-top-side">
|
||||
<CampaignBlock proposal={proposal} isPreview={isPreview} />
|
||||
|
@ -134,6 +177,13 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
)}
|
||||
{isTrustee && (
|
||||
<CancelModal
|
||||
proposal={proposal}
|
||||
isVisible={isCancelOpen}
|
||||
handleClose={this.closeCancelModal}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -161,6 +211,9 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
this.setState({ isBodyOverflowing: true });
|
||||
}
|
||||
};
|
||||
|
||||
private openCancelModal = () => this.setState({ isCancelOpen: true });
|
||||
private closeCancelModal = () => this.setState({ isCancelOpen: false });
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState, ownProps: OwnProps) {
|
||||
|
@ -178,7 +231,7 @@ const withConnect = connect<StateProps, DispatchProps, OwnProps, AppState>(
|
|||
mapDispatchToProps,
|
||||
);
|
||||
|
||||
const ConnectedProposal = compose<Props, OwnProps>(
|
||||
const ConnectedProposal = compose<Props, OwnProps & Web3Props>(
|
||||
withRouter,
|
||||
withConnect,
|
||||
)(ProposalDetail);
|
||||
|
@ -194,6 +247,6 @@ export default (props: OwnProps) => (
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
render={() => <ConnectedProposal {...props} />}
|
||||
render={({ accounts }) => <ConnectedProposal account={accounts[0]} {...props} />}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
}
|
||||
|
||||
&-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100% - 19rem);
|
||||
|
@ -104,6 +105,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-menu {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-side {
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface Web3RenderProps {
|
|||
}
|
||||
|
||||
interface OwnProps {
|
||||
render(props: Web3RenderProps & any): React.ReactNode;
|
||||
render(props: Web3RenderProps & { props: any }): React.ReactNode;
|
||||
renderLoading(): React.ReactNode;
|
||||
}
|
||||
|
||||
|
|
|
@ -332,6 +332,49 @@ export function voteRefund(crowdFundContract: any, vote: boolean) {
|
|||
};
|
||||
}
|
||||
|
||||
async function freezeContract(crowdFundContract: any, account: string) {
|
||||
let isFrozen = await crowdFundContract.methods.frozen().call({ from: account });
|
||||
// Already frozen, all good here
|
||||
if (isFrozen) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
crowdFundContract.methods
|
||||
.refund()
|
||||
.send({ from: account })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
isFrozen = await crowdFundContract.methods.frozen().call({ from: account });
|
||||
resolve();
|
||||
})
|
||||
.catch((err: Error) => reject(err));
|
||||
});
|
||||
if (!isFrozen) {
|
||||
throw new Error('Proposal isn’t in a refundable state yet.');
|
||||
}
|
||||
}
|
||||
|
||||
export function triggerRefund(crowdFundContract: any) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({ type: types.WITHDRAW_REFUND_PENDING });
|
||||
const state = getState();
|
||||
const account = state.web3.accounts[0];
|
||||
|
||||
try {
|
||||
await freezeContract(crowdFundContract, account);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
dispatch({ type: types.TRIGGER_REFUND_FULFILLED });
|
||||
} catch (err) {
|
||||
dispatch({
|
||||
type: types.TRIGGER_REFUND_REJECTED,
|
||||
payload: err.message || err.toString(),
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function withdrawRefund(crowdFundContract: any, address: string) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({ type: types.WITHDRAW_REFUND_PENDING });
|
||||
|
@ -339,24 +382,7 @@ export function withdrawRefund(crowdFundContract: any, address: string) {
|
|||
const account = state.web3.accounts[0];
|
||||
|
||||
try {
|
||||
let isFrozen = await crowdFundContract.methods.frozen().call({ from: account });
|
||||
if (!isFrozen) {
|
||||
await new Promise(resolve => {
|
||||
crowdFundContract.methods
|
||||
.refund()
|
||||
.send({ from: account })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
isFrozen = await crowdFundContract.methods.frozen().call({ from: account });
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!isFrozen) {
|
||||
throw new Error('Proposal isn’t in a refundable state yet.');
|
||||
}
|
||||
|
||||
await freezeContract(crowdFundContract, account);
|
||||
await crowdFundContract.methods
|
||||
.withdraw(address)
|
||||
.send({ from: account })
|
||||
|
|
|
@ -214,6 +214,7 @@ export default (state = INITIAL_STATE, action: any): Web3State => {
|
|||
|
||||
case types.VOTE_REFUND_PENDING:
|
||||
case types.WITHDRAW_REFUND_PENDING:
|
||||
case types.TRIGGER_REFUND_PENDING:
|
||||
return {
|
||||
...state,
|
||||
isRefundActionPending: true,
|
||||
|
@ -221,12 +222,14 @@ export default (state = INITIAL_STATE, action: any): Web3State => {
|
|||
};
|
||||
case types.VOTE_REFUND_FULFILLED:
|
||||
case types.WITHDRAW_REFUND_FULFILLED:
|
||||
case types.TRIGGER_REFUND_FULFILLED:
|
||||
return {
|
||||
...state,
|
||||
isRefundActionPending: false,
|
||||
};
|
||||
case types.VOTE_REFUND_REJECTED:
|
||||
case types.WITHDRAW_REFUND_REJECTED:
|
||||
case types.TRIGGER_REFUND_REJECTED:
|
||||
return {
|
||||
...state,
|
||||
refundActionError: payload,
|
||||
|
|
|
@ -46,6 +46,11 @@ enum web3Types {
|
|||
WITHDRAW_REFUND_REJECTED = 'WITHDRAW_REFUND_REJECTED',
|
||||
WITHDRAW_REFUND_PENDING = 'WITHDRAW_REFUND_PENDING',
|
||||
|
||||
TRIGGER_REFUND = 'TRIGGER_REFUND',
|
||||
TRIGGER_REFUND_FULFILLED = 'TRIGGER_REFUND_FULFILLED',
|
||||
TRIGGER_REFUND_REJECTED = 'TRIGGER_REFUND_REJECTED',
|
||||
TRIGGER_REFUND_PENDING = 'TRIGGER_REFUND_PENDING',
|
||||
|
||||
ACCOUNTS = 'ACCOUNTS',
|
||||
ACCOUNTS_FULFILLED = 'ACCOUNTS_FULFILLED',
|
||||
ACCOUNTS_REJECTED = 'ACCOUNTS_REJECTED',
|
||||
|
|
Loading…
Reference in New Issue