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={
|
||||
<div>
|
||||
<p>
|
||||
This proposal has changes requested. The team will be able to re-submit it for
|
||||
approval should they desire to do so.
|
||||
This proposal has changes requested. The team will be able to re-submit it
|
||||
for approval should they desire to do so.
|
||||
</p>
|
||||
<b>Reason:</b>
|
||||
<br />
|
||||
|
|
|
@ -61,7 +61,8 @@ class ProfileUser extends React.Component<Props, State> {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isSelf && <TipJarBlock address={user.tipJarAddress} type="user" />}
|
||||
{!isSelf &&
|
||||
user.tipJarAddress && <TipJarBlock address={user.tipJarAddress} type="user" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import { compose } from 'recompose';
|
|||
import { AppState } from 'store/reducers';
|
||||
import { withRouter } from 'react-router';
|
||||
import UnitDisplay from 'components/UnitDisplay';
|
||||
import { TipJarBlock } from 'components/TipJar';
|
||||
import Loader from 'components/Loader';
|
||||
import { PROPOSAL_STAGE } from 'api/constants';
|
||||
import ZFGrantsLogo from 'static/images/logo-name-light.svg'
|
||||
import './style.less';
|
||||
|
||||
interface OwnProps {
|
||||
|
@ -70,13 +70,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
const isAcceptedWithFunding = proposal.acceptedWithFunding === true;
|
||||
const isAcceptedWithoutFunding = proposal.acceptedWithFunding === false;
|
||||
const isAccepted = isAcceptedWithFunding || isAcceptedWithoutFunding;
|
||||
const isCancelled = proposal.stage === PROPOSAL_STAGE.CANCELED;
|
||||
const isJudged = isAccepted || isCancelled;
|
||||
|
||||
const displayBountyFunding =
|
||||
!isVersionTwo || (isVersionTwo && isAcceptedWithFunding);
|
||||
const isCanceled = proposal.stage === PROPOSAL_STAGE.CANCELED;
|
||||
|
||||
content = (
|
||||
<React.Fragment>
|
||||
|
@ -97,22 +91,47 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isVersionTwo && (
|
||||
<div className="ProposalCampaignBlock-info">
|
||||
<div className="ProposalCampaignBlock-info-label">Funding</div>
|
||||
<div className="ProposalCampaignBlock-info-value">
|
||||
<UnitDisplay value={funded} /> / <UnitDisplay value={target} symbol="ZEC" />
|
||||
<UnitDisplay value={funded} /> /{' '}
|
||||
<UnitDisplay value={target} symbol="ZEC" />
|
||||
</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 &&
|
||||
displayBountyFunding && (
|
||||
!isVersionTwo && (
|
||||
<div className="ProposalCampaignBlock-bounty">
|
||||
Awarded with <UnitDisplay value={bounty} symbol="ZEC" /> bounty
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAcceptedWithoutFunding && (
|
||||
<div className="ProposalCampaignBlock-bounty">Accepted without funding</div>
|
||||
{isVersionTwo &&
|
||||
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 &&
|
||||
|
@ -144,7 +163,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
['is-success']: isRaiseGoalReached,
|
||||
})}
|
||||
>
|
||||
{isCancelled ? (
|
||||
{isCanceled ? (
|
||||
<>
|
||||
<Icon type="close-circle-o" />
|
||||
<span>Proposal was canceled</span>
|
||||
|
@ -175,30 +194,10 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
))}
|
||||
|
||||
{isVersionTwo &&
|
||||
isJudged && (
|
||||
<div
|
||||
className={classnames({
|
||||
['ProposalCampaignBlock-fundingOver']: true,
|
||||
['is-success']: isAccepted,
|
||||
})}
|
||||
>
|
||||
{proposal.stage === PROPOSAL_STAGE.CANCELED ? (
|
||||
<>
|
||||
isCanceled && (
|
||||
<div className="ProposalCampaignBlock-fundingOver">
|
||||
<Icon type="close-circle-o" />
|
||||
<span>Proposal was canceled</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon type="check-circle-o" />
|
||||
<span>Proposal has been accepted</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.tipJarAddress && (
|
||||
<div className="ProposalCampaignBlock-tipJarWrapper">
|
||||
<TipJarBlock address={proposal.tipJarAddress} type="proposal" />
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
|
|
@ -57,6 +57,25 @@
|
|||
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 {
|
||||
&-overlay {
|
||||
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;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
&:nth-child(2) {
|
||||
width: calc(50% - 1rem);
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: calc(50% - 1rem);
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
width: calc(50% - 1rem);
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +225,9 @@
|
|||
> * {
|
||||
&,
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
&:nth-child(2),
|
||||
&:nth-child(3),
|
||||
&:nth-child(4) {
|
||||
width: auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AlertProps } from 'antd/lib/alert';
|
|||
import ExceptionPage from 'components/ExceptionPage';
|
||||
import HeaderDetails from 'components/HeaderDetails';
|
||||
import CampaignBlock from './CampaignBlock';
|
||||
import TippingBlock from './TippingBlock'
|
||||
import TeamBlock from './TeamBlock';
|
||||
import RFPBlock from './RFPBlock';
|
||||
import Milestones from './Milestones';
|
||||
|
@ -246,6 +247,7 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
</div>
|
||||
<div className="Proposal-top-side">
|
||||
<CampaignBlock proposal={proposal} isPreview={!isLive} />
|
||||
<TippingBlock proposal={proposal} />
|
||||
<TeamBlock proposal={proposal} />
|
||||
{proposal.rfp && <RFPBlock rfp={proposal.rfp} />}
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Modal, Icon, Button, Form, Input } from 'antd';
|
|||
import classnames from 'classnames';
|
||||
import QRCode from 'qrcode.react';
|
||||
import { formatZcashCLI, formatZcashURI } from 'utils/formatters';
|
||||
import { getAmountErrorFromString } from 'utils/validators'
|
||||
import { getAmountErrorFromString } from 'utils/validators';
|
||||
import Loader from 'components/Loader';
|
||||
import './TipJarModal.less';
|
||||
import CopyInput from 'components/CopyInput';
|
||||
|
@ -17,27 +17,25 @@ interface Props {
|
|||
}
|
||||
|
||||
interface State {
|
||||
amount: string | null;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export class TipJarModal extends React.Component<Props, State> {
|
||||
static getDerivedStateFromProps = (nextProps: Props, prevState: State) => {
|
||||
return prevState.amount === null ? { amount: nextProps.amount } : {};
|
||||
static getDerivedStateFromProps = (nextProps: Props) => {
|
||||
// while modal is closed, set amount state via props
|
||||
return !nextProps.isOpen ? { amount: nextProps.amount } : {};
|
||||
};
|
||||
|
||||
state: State = {
|
||||
amount: null,
|
||||
amount: '',
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isOpen, onClose, type, address } = this.props;
|
||||
const { amount } = this.state;
|
||||
|
||||
// should not be possible due to derived state, but makes TS happy
|
||||
if (amount === null) return;
|
||||
|
||||
const amountError = getAmountErrorFromString(amount)
|
||||
const amountIsValid = !amountError
|
||||
const amountError = getAmountErrorFromString(amount);
|
||||
const amountIsValid = !amountError;
|
||||
|
||||
const cli = amountIsValid ? formatZcashCLI(address, amount) : '';
|
||||
const uri = amountIsValid ? formatZcashURI(address, amount) : '';
|
||||
|
@ -122,5 +120,5 @@ export class TipJarModal extends React.Component<Props, State> {
|
|||
amount: e.currentTarget.value,
|
||||
});
|
||||
|
||||
private handleAfterClose = () => this.setState({ amount: null });
|
||||
private handleAfterClose = () => this.setState({ amount: '' });
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue