Show send instructions on profile pending contributions.
This commit is contained in:
parent
5a922cefee
commit
6c8ce3f87d
|
@ -220,6 +220,7 @@ export function postProposalContribution(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function postProposalComment(payload: {
|
export function postProposalComment(payload: {
|
||||||
proposalId: number;
|
proposalId: number;
|
||||||
parentCommentId?: number;
|
parentCommentId?: number;
|
||||||
|
@ -232,3 +233,10 @@ export function postProposalComment(payload: {
|
||||||
export function deleteProposalContribution(contributionId: string | number) {
|
export function deleteProposalContribution(contributionId: string | number) {
|
||||||
return axios.delete(`/api/v1/proposals/contribution/${contributionId}`);
|
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}`);
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
border-bottom: 1px solid rgba(#000, 0.06);
|
border-bottom: 1px solid rgba(#000, 0.06);
|
||||||
|
|
||||||
&-qr {
|
&-qr {
|
||||||
|
position: relative;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -28,6 +29,16 @@
|
||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
&.is-loading canvas {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-spin {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-info {
|
&-info {
|
||||||
|
|
|
@ -67,8 +67,13 @@ export default class PaymentInfo extends React.Component<Props, State> {
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
|
|
||||||
<div className="PaymentInfo-uri">
|
<div className="PaymentInfo-uri">
|
||||||
<div className="PaymentInfo-uri-qr">
|
<div className={
|
||||||
{uri ? <QRCode value={uri} /> : <Spin />}
|
classnames('PaymentInfo-uri-qr', !uri && 'is-loading')
|
||||||
|
}>
|
||||||
|
<span style={{ opacity: uri ? 1 : 0 }}>
|
||||||
|
<QRCode value={uri || ''} />
|
||||||
|
</span>
|
||||||
|
{!uri && <Spin size="large" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="PaymentInfo-uri-info">
|
<div className="PaymentInfo-uri-info">
|
||||||
<CopyInput
|
<CopyInput
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modal } from 'antd';
|
import { Modal } from 'antd';
|
||||||
import Result from 'ant-design-pro/lib/Result';
|
import Result from 'ant-design-pro/lib/Result';
|
||||||
import { postProposalContribution } from 'api/api';
|
import { postProposalContribution, getProposalContribution } from 'api/api';
|
||||||
import { ContributionWithAddresses } from 'types';
|
import { ContributionWithAddresses } from 'types';
|
||||||
import PaymentInfo from './PaymentInfo';
|
import PaymentInfo from './PaymentInfo';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
proposalId: number;
|
proposalId?: number;
|
||||||
|
contributionId?: number;
|
||||||
amount?: string;
|
amount?: string;
|
||||||
|
hasNoButtons?: boolean;
|
||||||
handleClose(): void;
|
handleClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,17 +30,21 @@ export default class ContributionModal extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUpdate(nextProps: Props) {
|
componentWillUpdate(nextProps: Props) {
|
||||||
const { isVisible, proposalId } = nextProps
|
const { isVisible, proposalId, contributionId } = nextProps;
|
||||||
if (isVisible && this.props.isVisible !== isVisible) {
|
// When modal is opened and proposalId is provided or changed
|
||||||
this.fetchAddresses(proposalId);
|
if (isVisible && proposalId) {
|
||||||
}
|
if (
|
||||||
else if (proposalId !== this.props.proposalId) {
|
this.props.isVisible !== isVisible ||
|
||||||
this.fetchAddresses(proposalId);
|
proposalId !== this.props.proposalId
|
||||||
|
) {
|
||||||
|
this.fetchAddresses(proposalId, contributionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isVisible, handleClose } = this.props;
|
const { isVisible, handleClose, hasNoButtons } = this.props;
|
||||||
const { hasSent, contribution, error } = this.state;
|
const { hasSent, contribution, error } = this.state;
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
|
@ -68,11 +74,12 @@ export default class ContributionModal extends React.Component<Props, State> {
|
||||||
<Modal
|
<Modal
|
||||||
title="Make your contribution"
|
title="Make your contribution"
|
||||||
visible={isVisible}
|
visible={isVisible}
|
||||||
closable={hasSent}
|
closable={hasSent || hasNoButtons}
|
||||||
maskClosable={hasSent}
|
maskClosable={hasSent || hasNoButtons}
|
||||||
okText={hasSent ? 'Done' : 'I’ve sent it'}
|
okText={hasSent ? 'Done' : 'I’ve sent it'}
|
||||||
onOk={hasSent ? handleClose : this.confirmSend}
|
onOk={hasSent ? handleClose : this.confirmSend}
|
||||||
onCancel={handleClose}
|
onCancel={handleClose}
|
||||||
|
footer={hasNoButtons ? '' : undefined}
|
||||||
centered
|
centered
|
||||||
>
|
>
|
||||||
{content}
|
{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 {
|
try {
|
||||||
const res = await postProposalContribution(
|
let res;
|
||||||
|
if (contributionId) {
|
||||||
|
res = await getProposalContribution(proposalId, contributionId);
|
||||||
|
} else {
|
||||||
|
res = await postProposalContribution(
|
||||||
proposalId,
|
proposalId,
|
||||||
this.props.amount || '0',
|
this.props.amount || '0',
|
||||||
);
|
);
|
||||||
|
}
|
||||||
this.setState({ contribution: res.data });
|
this.setState({ contribution: res.data });
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this.setState({ error: err.message });
|
this.setState({ error: err.message });
|
||||||
|
|
|
@ -12,6 +12,7 @@ import './ProfileContribution.less';
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
userId: number;
|
userId: number;
|
||||||
contribution: UserContribution;
|
contribution: UserContribution;
|
||||||
|
showSendInstructions(contribution: UserContribution): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
|
@ -30,7 +31,6 @@ class ProfileContribution extends React.Component<Props> {
|
||||||
let tag;
|
let tag;
|
||||||
let actions: React.ReactNode;
|
let actions: React.ReactNode;
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
// TODO: Link to block explorer
|
|
||||||
actions = (
|
actions = (
|
||||||
<a
|
<a
|
||||||
href={formatTxExplorerUrl(contribution.txId as string)}
|
href={formatTxExplorerUrl(contribution.txId as string)}
|
||||||
|
@ -50,12 +50,15 @@ class ProfileContribution extends React.Component<Props> {
|
||||||
>
|
>
|
||||||
<a>Delete</a>
|
<a>Delete</a>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
<a>Contact support</a>
|
<Link to="/support">Contact support</Link>
|
||||||
</>;
|
</>;
|
||||||
} else {
|
} else {
|
||||||
tag = <Tag color="orange">Pending</Tag>;
|
tag = <Tag color="orange">Pending</Tag>;
|
||||||
// TODO: Show ContributionModal
|
actions = (
|
||||||
actions = <a>View instructions</a>;
|
<a onClick={() => this.props.showSendInstructions(contribution)}>
|
||||||
|
View send instructions
|
||||||
|
</a>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -21,7 +21,9 @@ import ProfileComment from './ProfileComment';
|
||||||
import ProfileInvite from './ProfileInvite';
|
import ProfileInvite from './ProfileInvite';
|
||||||
import Placeholder from 'components/Placeholder';
|
import Placeholder from 'components/Placeholder';
|
||||||
import Exception from 'pages/exception';
|
import Exception from 'pages/exception';
|
||||||
|
import ContributionModal from 'components/ContributionModal';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
import { UserContribution } from 'types';
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
usersMap: AppState['users']['map'];
|
usersMap: AppState['users']['map'];
|
||||||
|
@ -35,7 +37,15 @@ interface DispatchProps {
|
||||||
|
|
||||||
type Props = RouteComponentProps<any> & StateProps & 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() {
|
componentDidMount() {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
|
@ -50,6 +60,8 @@ class Profile extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const userLookupParam = this.props.match.params.id;
|
const userLookupParam = this.props.match.params.id;
|
||||||
const { authUser, match } = this.props;
|
const { authUser, match } = this.props;
|
||||||
|
const { activeContribution } = this.state;
|
||||||
|
|
||||||
if (!userLookupParam) {
|
if (!userLookupParam) {
|
||||||
if (authUser && authUser.userid) {
|
if (authUser && authUser.userid) {
|
||||||
return <Redirect to={`/profile/${authUser.userid}`} />;
|
return <Redirect to={`/profile/${authUser.userid}`} />;
|
||||||
|
@ -128,7 +140,12 @@ class Profile extends React.Component<Props> {
|
||||||
<div>
|
<div>
|
||||||
{noneFunded && <Placeholder subtitle="Has not funded any proposals yet" />}
|
{noneFunded && <Placeholder subtitle="Has not funded any proposals yet" />}
|
||||||
{contributions.map(c => (
|
{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>
|
</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
@ -160,9 +177,20 @@ class Profile extends React.Component<Props> {
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
)}
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
<ContributionModal
|
||||||
|
isVisible={!!activeContribution}
|
||||||
|
proposalId={
|
||||||
|
activeContribution ? activeContribution.proposal.proposalId : undefined
|
||||||
|
}
|
||||||
|
contributionId={activeContribution ? activeContribution.id : undefined}
|
||||||
|
hasNoButtons
|
||||||
|
handleClose={this.closeContributionModal}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchData() {
|
private fetchData() {
|
||||||
const { match } = this.props;
|
const { match } = this.props;
|
||||||
const userLookupId = match.params.id;
|
const userLookupId = match.params.id;
|
||||||
|
@ -171,6 +199,9 @@ class Profile extends React.Component<Props> {
|
||||||
this.props.fetchUserInvites(userLookupId);
|
this.props.fetchUserInvites(userLookupId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private openContributionModal = (c: UserContribution) => this.setState({ activeContribution: c });
|
||||||
|
private closeContributionModal = () => this.setState({ activeContribution: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
const TabTitle = (disp: string, count: number) => (
|
const TabTitle = (disp: string, count: number) => (
|
||||||
|
|
|
@ -2,7 +2,6 @@ import * as React from 'react';
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
|
|
||||||
import { ProposalCampaignBlock } from 'components/Proposal/CampaignBlock';
|
import { ProposalCampaignBlock } from 'components/Proposal/CampaignBlock';
|
||||||
import Contributors from 'components/Proposal/Contributors';
|
|
||||||
|
|
||||||
import 'styles/style.less';
|
import 'styles/style.less';
|
||||||
import 'components/Proposal/style.less';
|
import 'components/Proposal/style.less';
|
||||||
|
@ -53,9 +52,4 @@ storiesOf('Proposal', module)
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
|
||||||
<CampaignBlocks style={{ margin: '0 12px' }} />
|
<CampaignBlocks style={{ margin: '0 12px' }} />
|
||||||
</div>
|
</div>
|
||||||
))
|
|
||||||
.add('Contributors', () => (
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<Contributors />
|
|
||||||
</div>
|
|
||||||
));
|
));
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Zat } from 'utils/units';
|
||||||
import { Proposal, User } from 'types';
|
import { Proposal, User } from 'types';
|
||||||
|
|
||||||
export interface Contribution {
|
export interface Contribution {
|
||||||
id: string;
|
id: number;
|
||||||
txId: string;
|
txId: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
dateCreated: number;
|
dateCreated: number;
|
||||||
|
|
Loading…
Reference in New Issue