zcash-grant-system/frontend/client/components/CreateFlow/Review.tsx

227 lines
6.2 KiB
TypeScript
Raw Normal View History

import React from 'react';
import { connect } from 'react-redux';
import { Timeline } from 'antd';
import { getCreateErrors, KeyOfForm, FIELD_NAME_MAP } from 'modules/create/utils';
import Markdown from 'components/Markdown';
import UserAvatar from 'components/UserAvatar';
import { AppState } from 'store/reducers';
import { CREATE_STEP } from './index';
import { ProposalDraft } from 'types';
import { formatUsd } from 'utils/formatters';
import './Review.less';
interface OwnProps {
setStep(step: CREATE_STEP): void;
}
interface StateProps {
form: ProposalDraft;
}
type Props = OwnProps & StateProps;
interface Field {
key: KeyOfForm;
content: React.ReactNode;
error: string | Falsy;
isHide?: boolean;
}
interface Section {
step: CREATE_STEP;
name: string;
fields: Field[];
}
class CreateReview extends React.Component<Props> {
render() {
const { form } = this.props;
const errors = getCreateErrors(this.props.form);
const sections: Section[] = [
{
step: CREATE_STEP.BASICS,
name: 'Basics',
fields: [
{
key: 'title',
content: <h2 style={{ fontSize: '1.6rem', margin: 0 }}>{form.title}</h2>,
error: errors.title,
},
{
key: 'rfpOptIn',
content: <div>{form.rfpOptIn ? 'Accepted' : 'Declined'}</div>,
error: errors.rfpOptIn,
},
{
key: 'brief',
content: form.brief,
error: errors.brief,
},
{
key: 'target',
content: <div style={{ fontSize: '1.2rem' }}>{formatUsd(form.target)}</div>,
error: errors.target,
},
],
},
{
step: CREATE_STEP.TEAM,
name: 'Team',
fields: [
{
key: 'team',
content: <ReviewTeam team={form.team} invites={form.invites} />,
error: errors.team && errors.team.join(' '),
},
],
},
{
step: CREATE_STEP.DETAILS,
name: 'Details',
fields: [
{
key: 'content',
content: <Markdown source={form.content} />,
2018-11-14 13:21:41 -08:00
error: errors.content,
},
],
},
{
step: CREATE_STEP.MILESTONES,
name: 'Milestones',
fields: [
{
key: 'milestones',
content: <ReviewMilestones milestones={form.milestones} />,
error: errors.milestones && errors.milestones.join(' '),
},
],
},
{
step: CREATE_STEP.PAYMENT,
2019-03-04 12:02:51 -08:00
name: 'Payment',
fields: [
{
key: 'payoutAddress',
content: <code>{form.payoutAddress}</code>,
error: errors.payoutAddress,
},
],
},
{
step: CREATE_STEP.PAYMENT,
name: 'Tipping',
fields: [
{
key: 'tipJarAddress',
content: <code>{form.tipJarAddress}</code>,
error: errors.tipJarAddress,
},
],
},
];
return (
<div className="CreateReview">
CCRs (#86) * CCRs API / Models boilerplate * start on frontend * backendy things * Create CCR redux module, integrate API endpoints, create types * Fix/Cleanup API * Wire up CreateRequestDraftList * bounty->target * Add 'Create Request Flow' MVP * cleanup * Tweak filenames * Simplify migrations * fix migrations * CCR Staking MVP * tslint * Get Pending Requests into Profile * Remove staking requirement * more staking related removals * MVP Admin integration * Make RFP when CCR is accepted * Add pagination to CCRs in Admin Improve styles for Proposals * Hookup notifications Adjust copy * Simplify ccr->rfp relationship Add admin approval email Fixup copy * Show Message on RFP Detail Make Header CTAs change based on draft status Adjust proposal card style * Bugfix: Show header for non signed in users * Add 'create a request' to intro * Profile Created CCRs RFP CCR attribution * ignore * CCR Price in USD (#85) * 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 * init backend changes * init admin changes * init frontend changes * fix backend tests * update campaign block * be - init rfp usd changes * admin - init rfp usd changes * fe - fully adapt api util functions to usd * fe - init rfp usd changes * adapt profile created to usd * misc usd changes * 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 * conditionally add info icon and tooltip to funding line * admin - disallow decimals in RFPs * fe - cover usd string edge case * add Usd as rfp bounty type * fix migration order * fix email bug * adapt CCRs to USD * implement CCR preview * fix tsc * Copy Updates and UX Tweaks (#87) * Add default structure to proposal content * Landing page copy * Hide contributors tab for v2 proposals * Minor UX tweaks for Liking/Following/Tipping * Copy for Tipping Tooltip, proposal explainer for review, and milestone day estimate notice. * Fix header styles bug and remove commented out styles. * Revert "like" / "unfollow" hyphenication * Comment out unused tests related to staking Increase PROPOSAL_TARGET_MAX in .env.example * Comment out ccr approval email send until ready * Adjust styles, copy. * fix proposal prune test (#88) * fix USD display in preview, fix non-unique key (#90) * Pre-stepper explainer for CCRs. * Tweak styles * Default content for CCRs * fix tsc * CCR approval and rejection emails * add back admin_approval_ccr email templates * Link ccr author name to profile in RFPs * copy tweaks * copy tweak * hookup mangle user command * Fix/add endif in jinja * fix tests * review * fix review
2019-12-05 17:01:02 -08:00
{sections.map((s, i) => (
<div className="CreateReview-section" key={`${s.step}${i}`}>
{s.fields.map(
f =>
!f.isHide && (
<div className="ReviewField" key={f.key}>
<div className="ReviewField-label">
{FIELD_NAME_MAP[f.key]}
{f.error && (
<div className="ReviewField-label-error">{f.error}</div>
)}
</div>
<div className="ReviewField-content">
{this.isEmpty(form[f.key]) ? (
<div className="ReviewField-content-empty">N/A</div>
) : (
f.content
)}
</div>
</div>
),
)}
<div className="ReviewField">
<div className="ReviewField-label" />
<div className="ReviewField-content">
<button
className="ReviewField-content-edit"
onClick={() => this.setStep(s.step)}
>
Edit {s.name}
</button>
</div>
</div>
</div>
))}
</div>
);
}
private setStep = (step: CREATE_STEP) => {
this.props.setStep(step);
};
private isEmpty(value: any) {
if (typeof value === 'boolean') {
return false; // defined booleans are never empty
}
return !value || value.length === 0;
}
}
export default connect<StateProps, {}, OwnProps, AppState>(state => ({
form: state.create.form as ProposalDraft,
}))(CreateReview);
const ReviewMilestones = ({
milestones,
}: {
milestones: ProposalDraft['milestones'];
}) => (
<Timeline>
{milestones.map(m => (
2018-10-09 12:30:09 -07:00
<Timeline.Item key={m.title}>
<div className="ReviewMilestone">
<div className="ReviewMilestone-title">{m.title || <em>No title</em>}</div>
<div className="ReviewMilestone-info">
{m.immediatePayout || !m.daysEstimated ? '0' : m.daysEstimated} days
{' '}
{m.payoutPercent || '0'}% of funds
</div>
<div className="ReviewMilestone-description">
{m.content || <em>No description</em>}
</div>
</div>
</Timeline.Item>
))}
</Timeline>
);
const ReviewTeam: React.SFC<{
team: ProposalDraft['team'];
invites: ProposalDraft['invites'];
}> = ({ team, invites }) => {
const pendingInvites = invites.filter(inv => inv.accepted === null).length;
return (
<div className="ReviewTeam">
{team.map((u, idx) => (
<div className="ReviewTeam-member" key={idx}>
<UserAvatar className="ReviewTeam-member-avatar" user={u} />
<div className="ReviewTeam-member-info">
<div className="ReviewTeam-member-info-name">{u.displayName}</div>
<div className="ReviewTeam-member-info-title">{u.title}</div>
</div>
</div>
))}
{!!pendingInvites && (
<div className="ReviewTeam-invites">+ {pendingInvites} invite(s) pending</div>
)}
</div>
);
};