Show send instructions on profile pending contributions.

This commit is contained in:
Will O'Beirne 2019-01-09 17:26:28 -05:00
parent 5a922cefee
commit 6c8ce3f87d
No known key found for this signature in database
GPG Key ID: 44C190DB5DEAF9F6
8 changed files with 98 additions and 31 deletions

View File

@ -220,6 +220,7 @@ export function postProposalContribution(
});
}
export function postProposalComment(payload: {
proposalId: number;
parentCommentId?: number;
@ -232,3 +233,10 @@ export function postProposalComment(payload: {
export function deleteProposalContribution(contributionId: string | number) {
return axios.delete(`/api/v1/proposals/contribution/${contributionId}`);
}
export function getProposalContribution(
proposalId: number,
contributionId: number,
): Promise<{ data: ContributionWithAddresses }> {
return axios.get(`/api/v1/proposals/${proposalId}/contributions/${contributionId}`);
}

View File

@ -19,6 +19,7 @@
border-bottom: 1px solid rgba(#000, 0.06);
&-qr {
position: relative;
padding: 0.5rem;
margin-right: 1rem;
border-radius: 4px;
@ -28,6 +29,16 @@
canvas {
display: block;
}
&.is-loading canvas {
opacity: 0;
}
.ant-spin {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
&-info {

View File

@ -67,8 +67,13 @@ export default class PaymentInfo extends React.Component<Props, State> {
</Radio.Group>
<div className="PaymentInfo-uri">
<div className="PaymentInfo-uri-qr">
{uri ? <QRCode value={uri} /> : <Spin />}
<div className={
classnames('PaymentInfo-uri-qr', !uri && 'is-loading')
}>
<span style={{ opacity: uri ? 1 : 0 }}>
<QRCode value={uri || ''} />
</span>
{!uri && <Spin size="large" />}
</div>
<div className="PaymentInfo-uri-info">
<CopyInput

View File

@ -1,14 +1,16 @@
import React from 'react';
import { Modal } from 'antd';
import Result from 'ant-design-pro/lib/Result';
import { postProposalContribution } from 'api/api';
import { postProposalContribution, getProposalContribution } from 'api/api';
import { ContributionWithAddresses } from 'types';
import PaymentInfo from './PaymentInfo';
interface OwnProps {
isVisible: boolean;
proposalId: number;
proposalId?: number;
contributionId?: number;
amount?: string;
hasNoButtons?: boolean;
handleClose(): void;
}
@ -28,17 +30,21 @@ export default class ContributionModal extends React.Component<Props, State> {
};
componentWillUpdate(nextProps: Props) {
const { isVisible, proposalId } = nextProps
if (isVisible && this.props.isVisible !== isVisible) {
this.fetchAddresses(proposalId);
}
else if (proposalId !== this.props.proposalId) {
this.fetchAddresses(proposalId);
const { isVisible, proposalId, contributionId } = nextProps;
// When modal is opened and proposalId is provided or changed
if (isVisible && proposalId) {
if (
this.props.isVisible !== isVisible ||
proposalId !== this.props.proposalId
) {
this.fetchAddresses(proposalId, contributionId);
}
}
}
render() {
const { isVisible, handleClose } = this.props;
const { isVisible, handleClose, hasNoButtons } = this.props;
const { hasSent, contribution, error } = this.state;
let content;
@ -68,11 +74,12 @@ export default class ContributionModal extends React.Component<Props, State> {
<Modal
title="Make your contribution"
visible={isVisible}
closable={hasSent}
maskClosable={hasSent}
closable={hasSent || hasNoButtons}
maskClosable={hasSent || hasNoButtons}
okText={hasSent ? 'Done' : 'Ive sent it'}
onOk={hasSent ? handleClose : this.confirmSend}
onCancel={handleClose}
footer={hasNoButtons ? '' : undefined}
centered
>
{content}
@ -80,12 +87,20 @@ export default class ContributionModal extends React.Component<Props, State> {
);
}
private async fetchAddresses(proposalId: number) {
private async fetchAddresses(
proposalId: number,
contributionId?: number,
) {
try {
const res = await postProposalContribution(
proposalId,
this.props.amount || '0',
);
let res;
if (contributionId) {
res = await getProposalContribution(proposalId, contributionId);
} else {
res = await postProposalContribution(
proposalId,
this.props.amount || '0',
);
}
this.setState({ contribution: res.data });
} catch(err) {
this.setState({ error: err.message });

View File

@ -12,6 +12,7 @@ import './ProfileContribution.less';
interface OwnProps {
userId: number;
contribution: UserContribution;
showSendInstructions(contribution: UserContribution): void;
}
interface DispatchProps {
@ -30,7 +31,6 @@ class ProfileContribution extends React.Component<Props> {
let tag;
let actions: React.ReactNode;
if (isConfirmed) {
// TODO: Link to block explorer
actions = (
<a
href={formatTxExplorerUrl(contribution.txId as string)}
@ -50,12 +50,15 @@ class ProfileContribution extends React.Component<Props> {
>
<a>Delete</a>
</Popconfirm>
<a>Contact support</a>
<Link to="/support">Contact support</Link>
</>;
} else {
tag = <Tag color="orange">Pending</Tag>;
// TODO: Show ContributionModal
actions = <a>View instructions</a>;
actions = (
<a onClick={() => this.props.showSendInstructions(contribution)}>
View send instructions
</a>
);
}
return (

View File

@ -21,7 +21,9 @@ import ProfileComment from './ProfileComment';
import ProfileInvite from './ProfileInvite';
import Placeholder from 'components/Placeholder';
import Exception from 'pages/exception';
import ContributionModal from 'components/ContributionModal';
import './style.less';
import { UserContribution } from 'types';
interface StateProps {
usersMap: AppState['users']['map'];
@ -35,7 +37,15 @@ interface DispatchProps {
type Props = RouteComponentProps<any> & StateProps & DispatchProps;
class Profile extends React.Component<Props> {
interface State {
activeContribution: UserContribution | null;
}
class Profile extends React.Component<Props, State> {
state: State = {
activeContribution: null,
};
componentDidMount() {
this.fetchData();
}
@ -50,6 +60,8 @@ class Profile extends React.Component<Props> {
render() {
const userLookupParam = this.props.match.params.id;
const { authUser, match } = this.props;
const { activeContribution } = this.state;
if (!userLookupParam) {
if (authUser && authUser.userid) {
return <Redirect to={`/profile/${authUser.userid}`} />;
@ -128,7 +140,12 @@ class Profile extends React.Component<Props> {
<div>
{noneFunded && <Placeholder subtitle="Has not funded any proposals yet" />}
{contributions.map(c => (
<ProfileContribution key={c.id} userId={user.userid} contribution={c} />
<ProfileContribution
key={c.id}
userId={user.userid}
contribution={c}
showSendInstructions={this.openContributionModal}
/>
))}
</div>
</Tabs.TabPane>
@ -160,9 +177,20 @@ class Profile extends React.Component<Props> {
</Tabs.TabPane>
)}
</Tabs>
<ContributionModal
isVisible={!!activeContribution}
proposalId={
activeContribution ? activeContribution.proposal.proposalId : undefined
}
contributionId={activeContribution ? activeContribution.id : undefined}
hasNoButtons
handleClose={this.closeContributionModal}
/>
</div>
);
}
private fetchData() {
const { match } = this.props;
const userLookupId = match.params.id;
@ -171,6 +199,9 @@ class Profile extends React.Component<Props> {
this.props.fetchUserInvites(userLookupId);
}
}
private openContributionModal = (c: UserContribution) => this.setState({ activeContribution: c });
private closeContributionModal = () => this.setState({ activeContribution: null });
}
const TabTitle = (disp: string, count: number) => (

View File

@ -2,7 +2,6 @@ import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { ProposalCampaignBlock } from 'components/Proposal/CampaignBlock';
import Contributors from 'components/Proposal/Contributors';
import 'styles/style.less';
import 'components/Proposal/style.less';
@ -53,9 +52,4 @@ storiesOf('Proposal', module)
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<CampaignBlocks style={{ margin: '0 12px' }} />
</div>
))
.add('Contributors', () => (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Contributors />
</div>
));

View File

@ -2,7 +2,7 @@ import { Zat } from 'utils/units';
import { Proposal, User } from 'types';
export interface Contribution {
id: string;
id: number;
txId: string;
amount: string;
dateCreated: number;