Redesign Campaign Block (#74)
* init profile tipjar backend * init profile tipjar frontend * fix lint * implement tip jar block * fix wrapping, hide tip block on self * init backend proposal tipjar * init frontend proposal tipjar * add hide title, fix bug * uncomment rate limit * rename vars, use null check * allow address and view key to be unset * add api tests * fix tsc errors * fix lint * fix CopyInput styling * fix migrations * hide tipping in proposal if address not set * add tip address to create flow * redesign campaign block * fix typo * update campaign block * add tip jar to dedicated card * fix tipjar bug * use zf light logo * switch to zf grants logo * hide profile tip jar if address not set * add comment, run prettier
This commit is contained in:
parent
8f187ad775
commit
213595cfba
|
@ -225,8 +225,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
This proposal has changes requested. The team will be able to re-submit it for
|
This proposal has changes requested. The team will be able to re-submit it
|
||||||
approval should they desire to do so.
|
for approval should they desire to do so.
|
||||||
</p>
|
</p>
|
||||||
<b>Reason:</b>
|
<b>Reason:</b>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -61,7 +61,8 @@ class ProfileUser extends React.Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isSelf && <TipJarBlock address={user.tipJarAddress} type="user" />}
|
{!isSelf &&
|
||||||
|
user.tipJarAddress && <TipJarBlock address={user.tipJarAddress} type="user" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ import { compose } from 'recompose';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import UnitDisplay from 'components/UnitDisplay';
|
import UnitDisplay from 'components/UnitDisplay';
|
||||||
import { TipJarBlock } from 'components/TipJar';
|
|
||||||
import Loader from 'components/Loader';
|
import Loader from 'components/Loader';
|
||||||
import { PROPOSAL_STAGE } from 'api/constants';
|
import { PROPOSAL_STAGE } from 'api/constants';
|
||||||
|
import ZFGrantsLogo from 'static/images/logo-name-light.svg'
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
|
@ -70,13 +70,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAcceptedWithFunding = proposal.acceptedWithFunding === true;
|
const isAcceptedWithFunding = proposal.acceptedWithFunding === true;
|
||||||
const isAcceptedWithoutFunding = proposal.acceptedWithFunding === false;
|
const isCanceled = proposal.stage === PROPOSAL_STAGE.CANCELED;
|
||||||
const isAccepted = isAcceptedWithFunding || isAcceptedWithoutFunding;
|
|
||||||
const isCancelled = proposal.stage === PROPOSAL_STAGE.CANCELED;
|
|
||||||
const isJudged = isAccepted || isCancelled;
|
|
||||||
|
|
||||||
const displayBountyFunding =
|
|
||||||
!isVersionTwo || (isVersionTwo && isAcceptedWithFunding);
|
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -97,23 +91,48 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="ProposalCampaignBlock-info">
|
{!isVersionTwo && (
|
||||||
<div className="ProposalCampaignBlock-info-label">Funding</div>
|
<div className="ProposalCampaignBlock-info">
|
||||||
<div className="ProposalCampaignBlock-info-value">
|
<div className="ProposalCampaignBlock-info-label">Funding</div>
|
||||||
<UnitDisplay value={funded} /> / <UnitDisplay value={target} symbol="ZEC" />
|
<div className="ProposalCampaignBlock-info-value">
|
||||||
|
<UnitDisplay value={funded} /> /{' '}
|
||||||
|
<UnitDisplay value={target} symbol="ZEC" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{isVersionTwo && (
|
||||||
|
<div className="ProposalCampaignBlock-info">
|
||||||
|
<div className="ProposalCampaignBlock-info-label">
|
||||||
|
{isAcceptedWithFunding ? 'Funding' : 'Requested Funding'}
|
||||||
|
</div>
|
||||||
|
<div className="ProposalCampaignBlock-info-value">
|
||||||
|
<UnitDisplay value={target} symbol="ZEC" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{bounty &&
|
{bounty &&
|
||||||
displayBountyFunding && (
|
!isVersionTwo && (
|
||||||
<div className="ProposalCampaignBlock-bounty">
|
<div className="ProposalCampaignBlock-bounty">
|
||||||
Awarded with <UnitDisplay value={bounty} symbol="ZEC" /> bounty
|
Awarded with <UnitDisplay value={bounty} symbol="ZEC" /> bounty
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isAcceptedWithoutFunding && (
|
{isVersionTwo &&
|
||||||
<div className="ProposalCampaignBlock-bounty">Accepted without funding</div>
|
isAcceptedWithFunding && (
|
||||||
)}
|
<div className="ProposalCampaignBlock-with-funding">
|
||||||
|
Funded through
|
||||||
|
<ZFGrantsLogo style={{ height: '1.5rem' }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isVersionTwo &&
|
||||||
|
!isAcceptedWithFunding && (
|
||||||
|
<div className="ProposalCampaignBlock-without-funding">
|
||||||
|
Open for Community Donations
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isVersionTwo &&
|
{!isVersionTwo &&
|
||||||
proposal.contributionMatching > 0 && (
|
proposal.contributionMatching > 0 && (
|
||||||
|
@ -144,7 +163,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
['is-success']: isRaiseGoalReached,
|
['is-success']: isRaiseGoalReached,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isCancelled ? (
|
{isCanceled ? (
|
||||||
<>
|
<>
|
||||||
<Icon type="close-circle-o" />
|
<Icon type="close-circle-o" />
|
||||||
<span>Proposal was canceled</span>
|
<span>Proposal was canceled</span>
|
||||||
|
@ -175,32 +194,12 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{isVersionTwo &&
|
{isVersionTwo &&
|
||||||
isJudged && (
|
isCanceled && (
|
||||||
<div
|
<div className="ProposalCampaignBlock-fundingOver">
|
||||||
className={classnames({
|
<Icon type="close-circle-o" />
|
||||||
['ProposalCampaignBlock-fundingOver']: true,
|
<span>Proposal was canceled</span>
|
||||||
['is-success']: isAccepted,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{proposal.stage === PROPOSAL_STAGE.CANCELED ? (
|
|
||||||
<>
|
|
||||||
<Icon type="close-circle-o" />
|
|
||||||
<span>Proposal was canceled</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon type="check-circle-o" />
|
|
||||||
<span>Proposal has been accepted</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{proposal.tipJarAddress && (
|
|
||||||
<div className="ProposalCampaignBlock-tipJarWrapper">
|
|
||||||
<TipJarBlock address={proposal.tipJarAddress} type="proposal" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -57,6 +57,25 @@
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-with-funding,
|
||||||
|
&-without-funding {
|
||||||
|
margin: 0.5rem -1.5rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #fff;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-funding {
|
||||||
|
background: @secondary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-without-funding {
|
||||||
|
background: @info-color;
|
||||||
|
}
|
||||||
|
|
||||||
&-popover {
|
&-popover {
|
||||||
&-overlay {
|
&-overlay {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Tooltip, Icon } from 'antd';
|
||||||
|
import { Proposal } from 'types';
|
||||||
|
import Loader from 'components/Loader';
|
||||||
|
import { TipJarBlock } from 'components/TipJar';
|
||||||
|
import { PROPOSAL_STAGE } from 'api/constants';
|
||||||
|
import './style.less';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
proposal: Proposal;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TippingBlock: React.SFC<Props> = ({ proposal }) => {
|
||||||
|
let content;
|
||||||
|
if (proposal) {
|
||||||
|
if (!proposal.tipJarAddress || proposal.stage === PROPOSAL_STAGE.CANCELED) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
content = (
|
||||||
|
<>
|
||||||
|
<div className="TippingBlock-info">
|
||||||
|
<div className="TippingBlock-info-label">Tips Received</div>
|
||||||
|
<div className="TippingBlock-info-value">
|
||||||
|
???
|
||||||
|
<Tooltip
|
||||||
|
placement="left"
|
||||||
|
title="Made possible if a proposal owner supplies a view key with their tip address."
|
||||||
|
>
|
||||||
|
<Icon type="info-circle" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="TippingBlock-tipJarWrapper">
|
||||||
|
<TipJarBlock address={proposal.tipJarAddress} type="proposal" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Proposal-top-side-block">
|
||||||
|
<h2 className="Proposal-top-main-block-title">Tipping</h2>
|
||||||
|
<div className="Proposal-top-main-block">{content}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TippingBlock;
|
|
@ -0,0 +1,32 @@
|
||||||
|
@import '~styles/variables.less';
|
||||||
|
|
||||||
|
.TippingBlock {
|
||||||
|
&-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 1.7rem;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 300;
|
||||||
|
opacity: 0.8;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-tipJarWrapper {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -205,9 +205,16 @@
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:nth-child(2) {
|
||||||
|
width: calc(50% - 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
width: calc(50% - 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(4) {
|
||||||
width: calc(50% - 1rem);
|
width: calc(50% - 1rem);
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +225,9 @@
|
||||||
> * {
|
> * {
|
||||||
&,
|
&,
|
||||||
&:first-child,
|
&:first-child,
|
||||||
&:last-child {
|
&:nth-child(2),
|
||||||
|
&:nth-child(3),
|
||||||
|
&:nth-child(4) {
|
||||||
width: auto;
|
width: auto;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { AlertProps } from 'antd/lib/alert';
|
||||||
import ExceptionPage from 'components/ExceptionPage';
|
import ExceptionPage from 'components/ExceptionPage';
|
||||||
import HeaderDetails from 'components/HeaderDetails';
|
import HeaderDetails from 'components/HeaderDetails';
|
||||||
import CampaignBlock from './CampaignBlock';
|
import CampaignBlock from './CampaignBlock';
|
||||||
|
import TippingBlock from './TippingBlock'
|
||||||
import TeamBlock from './TeamBlock';
|
import TeamBlock from './TeamBlock';
|
||||||
import RFPBlock from './RFPBlock';
|
import RFPBlock from './RFPBlock';
|
||||||
import Milestones from './Milestones';
|
import Milestones from './Milestones';
|
||||||
|
@ -246,6 +247,7 @@ export class ProposalDetail extends React.Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
<div className="Proposal-top-side">
|
<div className="Proposal-top-side">
|
||||||
<CampaignBlock proposal={proposal} isPreview={!isLive} />
|
<CampaignBlock proposal={proposal} isPreview={!isLive} />
|
||||||
|
<TippingBlock proposal={proposal} />
|
||||||
<TeamBlock proposal={proposal} />
|
<TeamBlock proposal={proposal} />
|
||||||
{proposal.rfp && <RFPBlock rfp={proposal.rfp} />}
|
{proposal.rfp && <RFPBlock rfp={proposal.rfp} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Modal, Icon, Button, Form, Input } from 'antd';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import QRCode from 'qrcode.react';
|
import QRCode from 'qrcode.react';
|
||||||
import { formatZcashCLI, formatZcashURI } from 'utils/formatters';
|
import { formatZcashCLI, formatZcashURI } from 'utils/formatters';
|
||||||
import { getAmountErrorFromString } from 'utils/validators'
|
import { getAmountErrorFromString } from 'utils/validators';
|
||||||
import Loader from 'components/Loader';
|
import Loader from 'components/Loader';
|
||||||
import './TipJarModal.less';
|
import './TipJarModal.less';
|
||||||
import CopyInput from 'components/CopyInput';
|
import CopyInput from 'components/CopyInput';
|
||||||
|
@ -17,27 +17,25 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
amount: string | null;
|
amount: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TipJarModal extends React.Component<Props, State> {
|
export class TipJarModal extends React.Component<Props, State> {
|
||||||
static getDerivedStateFromProps = (nextProps: Props, prevState: State) => {
|
static getDerivedStateFromProps = (nextProps: Props) => {
|
||||||
return prevState.amount === null ? { amount: nextProps.amount } : {};
|
// while modal is closed, set amount state via props
|
||||||
|
return !nextProps.isOpen ? { amount: nextProps.amount } : {};
|
||||||
};
|
};
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
amount: null,
|
amount: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isOpen, onClose, type, address } = this.props;
|
const { isOpen, onClose, type, address } = this.props;
|
||||||
const { amount } = this.state;
|
const { amount } = this.state;
|
||||||
|
|
||||||
// should not be possible due to derived state, but makes TS happy
|
const amountError = getAmountErrorFromString(amount);
|
||||||
if (amount === null) return;
|
const amountIsValid = !amountError;
|
||||||
|
|
||||||
const amountError = getAmountErrorFromString(amount)
|
|
||||||
const amountIsValid = !amountError
|
|
||||||
|
|
||||||
const cli = amountIsValid ? formatZcashCLI(address, amount) : '';
|
const cli = amountIsValid ? formatZcashCLI(address, amount) : '';
|
||||||
const uri = amountIsValid ? formatZcashURI(address, amount) : '';
|
const uri = amountIsValid ? formatZcashURI(address, amount) : '';
|
||||||
|
@ -122,5 +120,5 @@ export class TipJarModal extends React.Component<Props, State> {
|
||||||
amount: e.currentTarget.value,
|
amount: e.currentTarget.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
private handleAfterClose = () => this.setState({ amount: null });
|
private handleAfterClose = () => this.setState({ amount: '' });
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue