Compare commits

...

25 Commits

Author SHA1 Message Date
Daniel Ternyak 2f87d96171
Merge pull request #505 from ZcashFoundation/master
Master
2021-07-12 18:20:19 -07:00
Daniel Ternyak 206ed2e63a
adjust default proposal content 2021-04-13 23:55:08 -07:00
Daniel Ternyak 819c15ba9c
funded by zomg true by default 2021-03-19 01:04:26 -05:00
Daniel Ternyak c0e05a86e6
fix 2fa 2021-02-02 01:55:18 -06:00
Daniel Ternyak b92a89d8ea
tsc 2021-02-02 01:49:06 -06:00
Daniel Ternyak 424ca4d283
fix story 2021-02-02 01:42:07 -06:00
Daniel Ternyak e12b4e1162
fix ci 2021-02-02 01:40:44 -06:00
Daniel Ternyak 7f065b4163
setup 'FUNDING BY ZOMG' 2021-02-01 19:32:12 -06:00
Daniel Ternyak 1a38eea631
fix nav on mobile 2021-01-10 23:08:42 -06:00
Daniel Ternyak e8e7004f17
fix tsl 2020-12-31 16:41:00 -06:00
Daniel Ternyak 475abec08b
move kyc acceptance to post approval 2020-12-31 03:11:13 -06:00
Daniel Ternyak ed7a3343c9
KYC acceptance property and admin UI 2020-12-28 15:07:37 -06:00
Daniel Ternyak 97b0cbc4b3
setup KYC page and update funding approved email with link to KYC page 2020-12-25 02:33:05 -06:00
Daniel Ternyak a36861d063
ensure proposals can only be submitted when KYC is accepted. Setup KYC info modal 2020-12-20 16:06:47 -06:00
Daniel Ternyak d6c7119dd0
increase scale 2020-12-07 18:22:54 -06:00
Daniel Ternyak a6dd059442
update logos 2020-12-07 18:03:57 -06:00
Daniel Ternyak ccdd6e4550
Merge branch 'develop' 2020-12-07 11:27:19 -06:00
Daniel Ternyak b192f00709
Merge pull request #501 from ZcashFoundation/develop
Provide notice for the temporary pausing in acceptance of new grants …
2020-07-24 14:43:27 -05:00
Daniel Ternyak e612e4f403
bump https-proxy-agent 2020-04-16 22:51:14 -05:00
Daniel Ternyak 08c6d6aaae
Update yarn.lock 2020-04-07 23:16:07 -05:00
Daniel Ternyak ad2743933f
Merge pull request #499 from ZcashFoundation/develop
Bump minimist from 0.0.8 to 1.2.3 in /frontend
2020-04-07 22:38:39 -05:00
Daniel Ternyak f50b516ade
Merge pull request #497 from ZcashFoundation/develop
ZF Grants 2.1
2020-04-07 21:57:24 -05:00
Daniel Ternyak 452637cc28
Merge pull request #488 from ZcashFoundation/develop
ZF Grants 2.0
2019-12-10 23:55:44 -06:00
Daniel Ternyak 11966b5060
Merge pull request #481 from ZcashFoundation/develop
Release 1.6.2
2019-08-22 10:44:36 -05:00
Daniel Ternyak 7a85a89c78
Merge pull request #468 from ZcashFoundation/develop
Release 1.6.0
2019-08-05 23:33:57 -05:00
38 changed files with 3739 additions and 311 deletions

32
.github/workflows/node.js.yml vendored Normal file
View File

@ -0,0 +1,32 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches:
- develop
- master
pull_request:
branches:
- develop
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: cd frontend && yarn && && yarn run lint && yarn run tsc

32
.github/workflows/python-app.yml vendored Normal file
View File

@ -0,0 +1,32 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python application
on:
push:
branches:
- develop
- master
pull_request:
branches:
- develop
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
cd backend && pip install -r requirements/dev.txt
- name: Test with flask test
run: |
cd backend && cp .env.example .env && flask test

View File

@ -2,34 +2,19 @@ import React from 'react';
import BN from 'bn.js';
import { view } from 'react-easy-state';
import { RouteComponentProps, withRouter } from 'react-router';
import {
Row,
Col,
Card,
Alert,
Button,
Collapse,
Popconfirm,
Input,
Tag,
message,
} from 'antd';
import { Alert, Button, Card, Col, Collapse, Input, message, Popconfirm, Row, Switch, Tag } from 'antd';
import TextArea from 'antd/lib/input/TextArea';
import store from 'src/store';
import { formatDateSeconds, formatDurationSeconds } from 'util/time';
import {
PROPOSAL_STATUS,
PROPOSAL_ARBITER_STATUS,
MILESTONE_STAGE,
PROPOSAL_STAGE,
} from 'src/types';
import { MILESTONE_STAGE, PROPOSAL_ARBITER_STATUS, PROPOSAL_STAGE, PROPOSAL_STATUS } from 'src/types';
import { Link } from 'react-router-dom';
import Back from 'components/Back';
import Markdown from 'components/Markdown';
import ArbiterControl from 'components/ArbiterControl';
import { toZat, fromZat } from 'src/util/units';
import { fromZat, toZat } from 'src/util/units';
import FeedbackModal from '../FeedbackModal';
import { formatUsd } from 'util/formatters';
import './index.less';
type Props = RouteComponentProps<any>;
@ -45,9 +30,11 @@ type State = typeof STATE;
class ProposalDetailNaked extends React.Component<Props, State> {
state = STATE;
rejectInput: null | TextArea = null;
componentDidMount() {
this.loadDetail();
}
render() {
const id = this.getIdFromQuery();
const { proposalDetail: p, proposalDetailFetching } = store;
@ -56,6 +43,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
return 'loading proposal...';
}
console.log(p.fundedByZomg);
const needsArbiter =
PROPOSAL_ARBITER_STATUS.MISSING === p.arbiter.status &&
p.status === PROPOSAL_STATUS.LIVE &&
@ -92,9 +81,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
</p>
)
}
placement="left"
cancelText="cancel"
okText="confirm"
placement='left'
cancelText='cancel'
okText='confirm'
visible={this.state.showCancelAndRefundPopover}
okButtonProps={{
loading: store.proposalDetailCanceling,
@ -103,8 +92,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
onConfirm={this.handleConfirmCancel}
>
<Button
icon="close-circle"
className="ProposalDetail-controls-control"
icon='close-circle'
className='ProposalDetail-controls-control'
loading={store.proposalDetailCanceling}
onClick={this.handleCancelAndRefundClick}
disabled={disabled}
@ -126,9 +115,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
with funding? This cannot be undone.
</p>
}
placement="left"
cancelText="cancel"
okText="confirm"
placement='left'
cancelText='cancel'
okText='confirm'
visible={this.state.showChangeToAcceptedWithFundingPopover}
okButtonProps={{
loading: store.proposalDetailCanceling,
@ -137,8 +126,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
onConfirm={this.handleChangeToAcceptWithFundingConfirm}
>
<Button
icon="close-circle"
className="ProposalDetail-controls-control"
icon='close-circle'
className='ProposalDetail-controls-control'
loading={store.proposalDetailChangingToAcceptedWithFunding}
onClick={this.handleChangeToAcceptedWithFunding}
block
@ -168,7 +157,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
p.status === PROPOSAL_STATUS.APPROVED && (
<Alert
showIcon
type="success"
type='success'
message={`Approved on ${formatDateSeconds(p.dateApproved)}`}
description={`
This proposal has been approved and will become live when a team-member
@ -183,7 +172,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
<Alert
showIcon
type={p.rfpOptIn ? 'success' : 'error'}
message={p.rfpOptIn ? 'KYC accepted' : 'KYC rejected'}
message={p.rfpOptIn ? 'KYC Accepted by user' : 'KYC rejected'}
description={
<div>
{p.rfpOptIn ? (
@ -204,25 +193,25 @@ class ProposalDetailNaked extends React.Component<Props, State> {
<Col span={isVersionTwo ? 16 : 24}>
<Alert
showIcon
type="warning"
message="Review Discussion"
type='warning'
message='Review Discussion'
description={
<div>
<p>Please review this proposal and render your judgment.</p>
<Button
className="ProposalDetail-review"
className='ProposalDetail-review'
loading={store.proposalDetailApprovingDiscussion}
icon="check"
type="primary"
icon='check'
type='primary'
onClick={() => this.handleApproveDiscussion()}
>
Open for Public Review
</Button>
<Button
className="ProposalDetail-review"
className='ProposalDetail-review'
loading={store.proposalDetailApprovingDiscussion}
icon="warning"
type="default"
icon='warning'
type='default'
onClick={() => {
FeedbackModal.open({
title: 'Request changes to this proposal?',
@ -235,10 +224,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
Request Changes
</Button>
<Button
className="ProposalDetail-review"
className='ProposalDetail-review'
loading={store.proposalDetailRejectingPermanently}
icon="close"
type="danger"
icon='close'
type='danger'
onClick={() => {
FeedbackModal.open({
title: 'Reject this proposal permanently?',
@ -267,34 +256,38 @@ class ProposalDetailNaked extends React.Component<Props, State> {
<Col span={isVersionTwo ? 16 : 24}>
<Alert
showIcon
type="warning"
message="Review Pending"
type='warning'
message='Review Pending'
description={
<div>
<p>Please review this proposal and render your judgment.</p>
<>
<Button
className='ProposalDetail-review'
loading={store.proposalDetailAcceptingProposal}
icon='check'
type='primary'
onClick={() => this.handleAcceptProposal(true, true)}
>
Approve With Funding
</Button>
<Button
className='ProposalDetail-review'
loading={store.proposalDetailAcceptingProposal}
icon='check'
type='default'
onClick={() => this.handleAcceptProposal(true, false)}
>
Approve Without Funding
</Button>
</>
<Button
className="ProposalDetail-review"
loading={store.proposalDetailAcceptingProposal}
icon="check"
type="primary"
onClick={() => this.handleAcceptProposal(true, true)}
>
Approve With Funding
</Button>
<Button
className="ProposalDetail-review"
loading={store.proposalDetailAcceptingProposal}
icon="check"
type="default"
onClick={() => this.handleAcceptProposal(true, false)}
>
Approve Without Funding
</Button>
<Button
className="ProposalDetail-review"
className='ProposalDetail-review'
loading={store.proposalDetailMarkingChangesAsResolved}
icon="close"
type="danger"
icon='close'
type='danger'
onClick={() => {
FeedbackModal.open({
title: 'Request changes to this proposal?',
@ -319,8 +312,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
p.status === PROPOSAL_STATUS.REJECTED && (
<Alert
showIcon
type="error"
message="Changes requested"
type='error'
message='Changes requested'
description={
<div>
<p>
@ -340,8 +333,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
p.changesRequestedDiscussion && (
<Alert
showIcon
type="error"
message="Changes requested"
type='error'
message='Changes requested'
description={
<div>
<p>
@ -354,10 +347,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
<br />
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
className="ProposalDetail-review"
className='ProposalDetail-review'
loading={false}
icon="check"
type="danger"
icon='check'
type='danger'
onClick={this.handleMarkChangesAsResolved}
>
Mark Request as Resolved
@ -371,17 +364,44 @@ class ProposalDetailNaked extends React.Component<Props, State> {
const renderNominateArbiter = () =>
needsArbiter &&
shouldShowArbiter && (
<Alert
showIcon
type="warning"
message="No arbiter on live proposal"
description={
<div>
<p>An arbiter is required to review milestone payout requests.</p>
<ArbiterControl {...p} />
</div>
}
/>
<>
{!p.kycApproved ? (
<Alert
showIcon
type='error'
message='KYC approval required'
description={
<div>
<p>
Please wait until an Admin has marked KYC approved before proceeding
with payouts.
</p>
<Button
className='ProposalDetail-review'
loading={store.proposalDetailApprovingKyc}
icon='check'
type='primary'
onClick={() => this.handleApproveKYC()}
>
KYC Approved
</Button>
</div>
}
/>
) : (
<Alert
showIcon
type='warning'
message='No arbiter on live proposal'
description={
<div>
<p>An arbiter is required to review milestone payout requests.</p>
<ArbiterControl {...p} />
</div>
}
/>
)}
</>
);
const renderNominatedArbiter = () =>
@ -389,8 +409,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
p.status === PROPOSAL_STATUS.LIVE && (
<Alert
showIcon
type="info"
message="Arbiter has been nominated"
type='info'
message='Arbiter has been nominated'
description={
<div>
<p>
@ -436,9 +456,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
return (
<Alert
className="ProposalDetail-alert"
className='ProposalDetail-alert'
showIcon
type="warning"
type='warning'
message={null}
description={
<div>
@ -454,9 +474,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
</p>{' '}
<pre>{p.payoutAddress}</pre>
<Input.Search
placeholder="please enter payment txid"
placeholder='please enter payment txid'
value={this.state.paidTxId}
enterButton="Mark Paid"
enterButton='Mark Paid'
onChange={e => this.setState({ paidTxId: e.target.value })}
onSearch={this.handlePaidMilestone}
/>
@ -470,7 +490,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
p.isFailed && (
<Alert
showIcon
type="error"
type='error'
message={
p.stage === PROPOSAL_STAGE.FAILED ? 'Proposal failed' : 'Proposal canceled'
}
@ -492,15 +512,16 @@ class ProposalDetailNaked extends React.Component<Props, State> {
);
const renderDeetItem = (name: string, val: any) => (
<div className="ProposalDetail-deet">
<div className='ProposalDetail-deet'>
<span>{name}</span>
{val} &nbsp;
</div>
);
// @ts-ignore
return (
<div className="ProposalDetail">
<Back to="/proposals" text="Proposals" />
<div className='ProposalDetail'>
<Back to='/proposals' text='Proposals' />
<h1>{p.title}</h1>
<Row gutter={16}>
{/* MAIN */}
@ -515,22 +536,22 @@ class ProposalDetailNaked extends React.Component<Props, State> {
{renderMilestoneAccepted()}
{renderFailed()}
<Collapse defaultActiveKey={['brief', 'content', 'milestones']}>
<Collapse.Panel key="brief" header="brief">
<Collapse.Panel key='brief' header='brief'>
{p.brief}
</Collapse.Panel>
<Collapse.Panel key="content" header="content">
<Collapse.Panel key='content' header='content'>
<Markdown source={p.content} />
</Collapse.Panel>
<Collapse.Panel key="milestones" header="milestones">
<Collapse.Panel key='milestones' header='milestones'>
{p.milestones.map((milestone, i) => (
<Card
title={
<>
{milestone.title + ' '}
{milestone.immediatePayout && (
<Tag color="magenta">Immediate Payout</Tag>
<Tag color='magenta'>Immediate Payout</Tag>
)}
</>
}
@ -555,7 +576,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
))}
</Collapse.Panel>
<Collapse.Panel key="json" header="json">
<Collapse.Panel key='json' header='json'>
<pre>{JSON.stringify(p, null, 4)}</pre>
</Collapse.Panel>
</Collapse>
@ -564,26 +585,38 @@ class ProposalDetailNaked extends React.Component<Props, State> {
{/* RIGHT SIDE */}
<Col span={6}>
{p.isVersionTwo &&
!p.acceptedWithFunding &&
p.stage === PROPOSAL_STAGE.WIP && (
<Alert
message="Accepted without funding"
description="This proposal has been posted publicly, but isn't being funded by the Zcash Foundation."
type="info"
showIcon
/>
)}
!p.acceptedWithFunding &&
p.stage === PROPOSAL_STAGE.WIP && (
<Alert
message='Accepted without funding'
description="This proposal has been posted publicly, but isn't being funded by the Zcash Foundation."
type='info'
showIcon
/>
)}
{/* ACTIONS */}
<Card size="small" className="ProposalDetail-controls">
<Card size='small' className='ProposalDetail-controls'>
{renderCancelControl()}
{renderArbiterControl()}
{
p.acceptedWithFunding &&
<div style={{ marginTop: '10px' }}>
<Switch checkedChildren='Funded by ZOMG'
unCheckedChildren='Funded by ZF'
onChange={this.handleSwitchFunder}
loading={store.proposalDetailSwitchingFunder}
checked={p.fundedByZomg} />
</div>
}
{shouldShowChangeToAcceptedWithFunding &&
renderChangeToAcceptedWithFundingControl()}
renderChangeToAcceptedWithFundingControl()}
</Card>
{/* DETAILS */}
<Card title="Details" size="small">
<Card title='Details' size='small'>
{renderDeetItem('id', p.proposalId)}
{renderDeetItem('created', formatDateSeconds(p.dateCreated))}
{renderDeetItem(
@ -595,10 +628,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
formatDurationSeconds(p.deadlineDuration),
)}
{p.datePublished &&
renderDeetItem(
'(deadline)',
formatDateSeconds(p.datePublished + p.deadlineDuration),
)}
renderDeetItem(
'(deadline)',
formatDateSeconds(p.datePublished + p.deadlineDuration),
)}
{renderDeetItem('isFailed', JSON.stringify(p.isFailed))}
{renderDeetItem('status', p.status)}
{renderDeetItem('stage', p.stage)}
@ -627,14 +660,14 @@ class ProposalDetailNaked extends React.Component<Props, State> {
</>,
)}
{p.rfp &&
renderDeetItem(
'rfp',
<Link to={`/rfps/${p.rfp.id}`}>{p.rfp.title}</Link>,
)}
renderDeetItem(
'rfp',
<Link to={`/rfps/${p.rfp.id}`}>{p.rfp.title}</Link>,
)}
</Card>
{/* TEAM */}
<Card title="Team" size="small">
<Card title='Team' size='small'>
{p.team.map(t => (
<div key={t.userid}>
<Link to={`/users/${t.userid}`}>{t.displayName}</Link>
@ -716,6 +749,11 @@ class ProposalDetailNaked extends React.Component<Props, State> {
message.info('Proposal rejected permanently');
};
private handleApproveKYC = async () => {
await store.approveProposalKYC();
message.info(`Proposal KYC approved`);
};
private handleAcceptProposal = async (
isAccepted: boolean,
withFunding: boolean,
@ -743,6 +781,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
await store.markMilestonePaid(pid, mid, this.state.paidTxId);
message.success('Marked milestone paid.');
};
private handleSwitchFunder = async (checkValue: boolean) => {
store.switchProposalFunder(checkValue);
};
}
const ProposalDetail = withRouter(view(ProposalDetailNaked));

View File

@ -2,17 +2,17 @@ import { pick } from 'lodash';
import { store } from 'react-easy-state';
import axios, { AxiosError } from 'axios';
import {
User,
Proposal,
CCR,
CommentArgs,
Contribution,
ContributionArgs,
EmailExample,
PageData,
PageQuery,
Proposal,
RFP,
RFPArgs,
EmailExample,
PageQuery,
PageData,
CommentArgs,
User,
} from './types';
// API
@ -142,6 +142,16 @@ async function approveDiscussion(
return data;
}
async function switchProposalFunder(id: number, fundedByZomg: boolean) {
const { data } = await api.put(`/admin/proposals/${id}/adjust-funder`, {fundedByZomg});
return data;
}
async function approveProposalKYC(id: number) {
const { data } = await api.put(`/admin/proposals/${id}/approve-kyc`);
return data;
}
async function acceptProposal(
id: number,
isAccepted: boolean,
@ -345,6 +355,8 @@ const app = store({
proposalDetailApprovingDiscussion: false,
proposalDetailMarkingChangesAsResolved: false,
proposalDetailAcceptingProposal: false,
proposalDetailApprovingKyc: false,
proposalDetailSwitchingFunder: false,
proposalDetailMarkingMilestonePaid: false,
proposalDetailCanceling: false,
proposalDetailUpdating: false,
@ -688,6 +700,43 @@ const app = store({
handleApiError(e);
}
},
async switchProposalFunder(fundedByZomg: boolean) {
if (!app.proposalDetail) {
const m = 'store.acceptProposal(): Expected proposalDetail to be populated!';
app.generalError.push(m);
console.error(m);
return;
}
app.proposalDetailSwitchingFunder = true;
try {
const { proposalId } = app.proposalDetail;
const res = await switchProposalFunder(proposalId, fundedByZomg);
app.updateProposalInStore(res);
} catch (e) {
handleApiError(e);
}
app.proposalDetailSwitchingFunder = false;
},
async approveProposalKYC() {
if (!app.proposalDetail) {
const m = 'store.acceptProposal(): Expected proposalDetail to be populated!';
app.generalError.push(m);
console.error(m);
return;
}
app.proposalDetailApprovingKyc = true;
try {
const { proposalId } = app.proposalDetail;
const res = await approveProposalKYC(proposalId);
app.updateProposalInStore(res);
} catch (e) {
handleApiError(e);
}
app.proposalDetailApprovingKyc = false;
},
async acceptProposal(
isAccepted: boolean,
withFunding: boolean,
@ -975,6 +1024,7 @@ function createDefaultPageData<T>(sort: string): PageData<T> {
}
type FNFetchPage = (params: PageQuery) => Promise<any>;
interface PageParent<T> {
page: PageData<T>;
}

View File

@ -123,6 +123,8 @@ export interface Proposal {
isVersionTwo: boolean;
changesRequestedDiscussion: boolean | null;
changesRequestedDiscussionReason: string | null;
kycApproved: null | boolean;
fundedByZomg: boolean;
}
export interface Comment {
id: number;

View File

@ -5414,7 +5414,7 @@ minimist@^1.2.0, minimist@~1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minimist@^1.2.2:
minimist@^1.2.3:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==

View File

@ -4,7 +4,7 @@ from functools import reduce
from flask import Blueprint, request
from marshmallow import fields, validate
from sqlalchemy import func, or_, text
from sqlalchemy import func, text
import grant.utils.admin as admin
import grant.utils.auth as auth
@ -25,7 +25,7 @@ from grant.proposal.models import (
admin_proposal_contributions_schema,
)
from grant.rfp.models import RFP, admin_rfp_schema, admin_rfps_schema
from grant.user.models import User, UserSettings, admin_users_schema, admin_user_schema
from grant.user.models import User, admin_users_schema, admin_user_schema
from grant.utils import pagination
from grant.utils.enums import (
ProposalStatus,
@ -377,6 +377,35 @@ def open_proposal_for_discussion(proposal_id, is_open_for_discussion, reject_rea
return proposal_schema.dump(proposal)
@blueprint.route('/proposals/<id>/approve-kyc', methods=['PUT'])
@admin.admin_auth_required
def approve_proposal_kyc(id):
proposal = Proposal.query.get(id)
if not proposal:
return {"message": "No proposal found."}, 404
proposal.kyc_approved = True
db.session.add(proposal)
db.session.commit()
return proposal_schema.dump(proposal)
@blueprint.route('/proposals/<id>/adjust-funder', methods=['PUT'])
@body({
"fundedByZomg": fields.Bool(required=True),
})
@admin.admin_auth_required
def adjust_funder(id, funded_by_zomg):
proposal = Proposal.query.get(id)
if not proposal:
return {"message": "No proposal found."}, 404
proposal.funded_by_zomg = funded_by_zomg
db.session.add(proposal)
db.session.commit()
return proposal_schema.dump(proposal)
@blueprint.route('/proposals/<id>/accept', methods=['PUT'])
@body({
"isAccepted": fields.Bool(required=True),

View File

@ -1,8 +1,8 @@
import datetime
import json
from typing import Optional
from decimal import Decimal, ROUND_DOWN
from functools import reduce
from typing import Optional
from marshmallow import post_dump
from sqlalchemy import func, or_, select, ForeignKey
@ -10,15 +10,14 @@ from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import column_property
from grant.comment.models import Comment
from grant.milestone.models import Milestone
from grant.email.send import send_email
from grant.extensions import ma, db
from grant.milestone.models import Milestone
from grant.settings import PROPOSAL_STAKING_AMOUNT, PROPOSAL_TARGET_MAX
from grant.task.jobs import ContributionExpired
from grant.utils.enums import (
ProposalStatus,
ProposalStage,
Category,
ContributionStatus,
ProposalArbiterStatus,
MilestoneStage,
@ -332,43 +331,49 @@ class ProposalRevision(db.Model):
if old_proposal.title != new_proposal.title:
proposal_changes.append({"type": ProposalChange.PROPOSAL_EDIT_TITLE})
milestone_changes = ProposalRevision.calculate_milestone_changes(old_proposal.milestones, new_proposal.milestones)
milestone_changes = ProposalRevision.calculate_milestone_changes(old_proposal.milestones,
new_proposal.milestones)
return proposal_changes + milestone_changes
def default_proposal_content():
return """# Applicant background
return """### If you have any doubts about the questions below, please reach out to anyone on the ZOMG on the [Zcash forums](https://forum.zcashcommunity.com/).
Summarize you and/or your teams background and experience. Demonstrate that you have the skills and expertise necessary for the project that youre proposing. Institutional bona fides are not required, but we want to hear about your track record.
# Description of Problem or Opportunity
In addition to describing the problem/opportunity, please give a sense of how serious or urgent of a need you believe this to be. What evidence do you have? What validation have you already done, or how do you think you could validate this?
# Motivation and overview
# Proposed Solution
Describe the solution at a high level. Please be specific about who the users and stakeholders are and how they would interact with your solution. E.g. retail ZEC holders, Zcash core devs, wallet devs, DeFi users, potential Zcash community participants.
What are your high-level goals? Why are they important? How is your project connected to [ZFs mission](https://www.zfnd.org/about/#mission) and priorities? Whose needs will it serve?
# Solution Format
What is the exact form of the deliverable youre creating? E.g. code shipped within the zcashd and zebra code bases, a website, a feature within a wallet, a text/markdown file, user manuals, etc.
# Technical approach
Dive into the _how_ of your project. Describe your approaches, components, workflows, methodology, etc. Bullet points and diagrams are appreciated!
# How big of a problem would it be to not solve this problem?
# Execution risks
What obstacles do you expect? What is most likely to go wrong? Which unknown factors or dependencies could jeopardize success? Who would have to incorporate your work in order for it to be usable?
What obstacles do you expect? What is most likely to go wrong? Which unknown factors or dependencies could jeopardize success? What are your contingency plans? Will subsequent activities be required to maximize impact?
# Downsides
# Unintended Consequences Downsides
What are the negative ramifications if your project is successful? Consider usability, stability, privacy, integrity, availability, decentralization, interoperability, maintainability, technical debt, requisite education, etc.
# Evaluation plan
What metrics for success can you share with the community once youre done? In addition to quantitative metrics, what qualitative metrics do you think you could report?
What will your project look like if successful? How will we be able to tell? Include quantifiable metrics if possible.
# Tasks and schedule
# Schedule and Milestones
What is your timeline for the project? Include concrete milestones and the major tasks required to complete each milestone.
# Budget and justification
# Budget and Payout Timeline
How much funding do you need, and how will it be allocated (e.g., compensation for your effort, specific equipment, specific external services)? Specify a total cost, break it up into budget items, and explain the rationale for each. Feel free to present multiple options in terms of scope and cost.
How much funding do you need, and how will it be allocated (e.g., compensation for your effort, specific equipment, specific external services)? Please tie your payout timelines to the milestones presented in the previous step. Convention has been for applicants to base their budget on hours of work and an hourly rate, but we are open to proposals based on the value of outcomes instead.
# Applicant background
Summarize you and/or your teams background and experience. Demonstrate that you have the skills and expertise necessary for the project that youre proposing. Institutional bona fides are not required, but we want to hear about your track record.
"""
@ -391,6 +396,9 @@ class Proposal(db.Model):
date_approved = db.Column(db.DateTime)
date_published = db.Column(db.DateTime)
reject_reason = db.Column(db.String())
kyc_approved = db.Column(db.Boolean(), nullable=True, default=False)
funded_by_zomg = db.Column(db.Boolean(), nullable=True, default=False)
accepted_with_funding = db.Column(db.Boolean(), nullable=True)
changes_requested_discussion = db.Column(db.Boolean(), nullable=True)
changes_requested_discussion_reason = db.Column(db.String(255), nullable=True)
@ -420,21 +428,23 @@ class Proposal(db.Model):
)
followers_count = column_property(
select([func.count(proposal_follower.c.proposal_id)])
.where(proposal_follower.c.proposal_id == id)
.correlate_except(proposal_follower)
.where(proposal_follower.c.proposal_id == id)
.correlate_except(proposal_follower)
)
likes = db.relationship(
"User", secondary=proposal_liker, back_populates="liked_proposals"
)
likes_count = column_property(
select([func.count(proposal_liker.c.proposal_id)])
.where(proposal_liker.c.proposal_id == id)
.correlate_except(proposal_liker)
.where(proposal_liker.c.proposal_id == id)
.correlate_except(proposal_liker)
)
live_draft_parent_id = db.Column(db.Integer, ForeignKey('proposal.id'))
live_draft = db.relationship("Proposal", uselist=False, backref=db.backref('live_draft_parent', remote_side=[id], uselist=False))
live_draft = db.relationship("Proposal", uselist=False,
backref=db.backref('live_draft_parent', remote_side=[id], uselist=False))
revisions = db.relationship(ProposalRevision, foreign_keys=[ProposalRevision.proposal_id], lazy=True, cascade="all, delete-orphan")
revisions = db.relationship(ProposalRevision, foreign_keys=[ProposalRevision.proposal_id], lazy=True,
cascade="all, delete-orphan")
def __init__(
self,
@ -460,6 +470,7 @@ class Proposal(db.Model):
self.deadline_duration = deadline_duration
self.stage = stage
self.version = '2'
self.funded_by_zomg = True
@staticmethod
def simple_validate(proposal):
@ -525,7 +536,7 @@ class Proposal(db.Model):
# Validate payout address
if not is_z_address_valid(self.payout_address):
raise ValidationException("Payout address is not a valid z address")
# Validate tip jar address
if self.tip_jar_address and not is_z_address_valid(self.tip_jar_address):
raise ValidationException("Tip address is not a valid z address")
@ -533,7 +544,6 @@ class Proposal(db.Model):
# Then run through regular validation
Proposal.simple_validate(vars(self))
def validate_milestone_days(self):
for milestone in self.milestones:
if milestone.immediate_payout:
@ -610,11 +620,11 @@ class Proposal(db.Model):
self.rfp_opt_in = opt_in
def create_contribution(
self,
amount,
user_id: int = None,
staking: bool = False,
private: bool = True,
self,
amount,
user_id: int = None,
staking: bool = False,
private: bool = True,
):
contribution = ProposalContribution(
proposal_id=self.id,
@ -921,8 +931,8 @@ class Proposal(db.Model):
return False
res = (
db.session.query(proposal_follower)
.filter_by(user_id=authed.id, proposal_id=self.id)
.count()
.filter_by(user_id=authed.id, proposal_id=self.id)
.count()
)
if res:
return True
@ -937,8 +947,8 @@ class Proposal(db.Model):
return False
res = (
db.session.query(proposal_liker)
.filter_by(user_id=authed.id, proposal_id=self.id)
.count()
.filter_by(user_id=authed.id, proposal_id=self.id)
.count()
)
if res:
return True
@ -1096,7 +1106,9 @@ class ProposalSchema(ma.Schema):
"tip_jar_view_key",
"changes_requested_discussion",
"changes_requested_discussion_reason",
"live_draft_id"
"live_draft_id",
"kyc_approved",
"funded_by_zomg"
)
date_created = ma.Method("get_date_created")
@ -1106,6 +1118,7 @@ class ProposalSchema(ma.Schema):
is_version_two = ma.Method("get_is_version_two")
tip_jar_view_key = ma.Method("get_tip_jar_view_key")
live_draft_id = ma.Method("get_live_draft_id")
funded_by_zomg = ma.Method("get_funded_by_zomg")
updates = ma.Nested("ProposalUpdateSchema", many=True)
team = ma.Nested("UserSchema", many=True)
@ -1115,6 +1128,14 @@ class ProposalSchema(ma.Schema):
rfp = ma.Nested("RFPSchema", exclude=["accepted_proposals"])
arbiter = ma.Nested("ProposalArbiterSchema", exclude=["proposal"])
def get_funded_by_zomg(self, obj):
if obj.funded_by_zomg is None:
return False
elif obj.funded_by_zomg is False:
return False
else:
return True
def get_proposal_id(self, obj):
return obj.id

View File

@ -1,5 +1,5 @@
<p style="margin: 0;">
Congratulations, your proposal has been funded by the Zcash Foundation! Once an arbiter is selected by the Foundation, you'll be able to request payouts according to your grant's milestone schedule.
Congratulations, your proposal has been funded by the Zcash Foundation! Once an arbiter is selected by the Foundation, you'll be able to request payouts according to your grant's milestone schedule. <a href='https://grants.zfnd.org/kyc'>Click here</a> for instructions on documentation you need to submit before the Zcash Foundation can transfer funds.
</p>
{% if args.admin_note %}

View File

@ -91,7 +91,7 @@
<tr>
<td align="center" style="padding: 40px 10px 40px 10px;" valign="top">
<a href="{{ args.home_url }}" target="_blank">
<img alt="ZF Grants logo" border="0" height="44" src="https://s3.us-east-2.amazonaws.com/zf-grants-prod/email-logo.png"
<img alt="ZF Grants logo" border="0" height="44" src="https://i.imgur.com/tYx0apf.png"
style="display: block; width: 220px; max-width: 220px; min-width: 220px; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;"
width="220">
</a>

View File

@ -0,0 +1,28 @@
"""empty message
Revision ID: 91b16dc2fd74
Revises: d03c91f3038d
Create Date: 2021-02-01 17:00:23.721765
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '91b16dc2fd74'
down_revision = 'd03c91f3038d'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('proposal', sa.Column('funded_by_zomg', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('proposal', 'funded_by_zomg')
# ### end Alembic commands ###

View File

@ -0,0 +1,28 @@
"""empty message
Revision ID: d03c91f3038d
Revises: bea5c35d0cd6
Create Date: 2020-12-27 15:48:36.787259
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd03c91f3038d'
down_revision = 'bea5c35d0cd6'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('proposal', sa.Column('kyc_approved', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('proposal', 'kyc_approved')
# ### end Alembic commands ###

View File

@ -37,6 +37,7 @@ const Tos = loadable(() => import('pages/tos'));
const ProposalTutorial = loadable(() => import('pages/proposal-tutorial'));
const About = loadable(() => import('pages/about'), opts);
const Privacy = loadable(() => import('pages/privacy'), opts);
const Kyc = loadable(() => import('pages/kyc'), opts);
const Contact = loadable(() => import('pages/contact'), opts);
const CodeOfConduct = loadable(() => import('pages/code-of-conduct'), opts);
const VerifyEmail = loadable(() => import('pages/email-verify'), opts);
@ -254,6 +255,18 @@ const routeConfigs: RouteConfig[] = [
},
onlyLoggedIn: false,
},
{
// Privacy page
route: {
path: '/kyc',
component: Kyc,
exact: true,
},
template: {
title: 'KYC',
},
onlyLoggedIn: false,
},
{
// Privacy page
route: {

View File

@ -86,11 +86,11 @@ export const STAGE_UI: { [key in PROPOSAL_FILTERS]: StageUI } = {
color: '#8e44ad',
},
ACCEPTED_WITH_FUNDING: {
label: 'Funded by ZF',
label: 'Funded',
color: '#8e44ad',
},
ACCEPTED_WITHOUT_FUNDING: {
label: 'Not Funded by ZF',
label: 'Not Funded',
color: '#8e44ad',
},
WIP: {

View File

@ -0,0 +1,34 @@
.KYCModal {
&.ant-modal {
width: 50vw !important;
}
& .ant-modal-body {
padding-right: 2.5vw;
padding-left: 2.5vw;
}
& ::marker {
content: counters(list-item, '.') ':';
color: #CF8A00;
font-weight: bold;
}
& ol ol {
padding-left: 60px;
}
& li {
padding-left: 0.5em;
padding-right: 0.5em;
margin-bottom: 5px;
}
& li li {
counter-reset: section
}
}

View File

@ -1,16 +1,18 @@
import React from 'react';
import { connect } from 'react-redux';
import { Input, Form, Alert, Popconfirm, message, Radio } from 'antd';
import { Alert, Form, Input, message, Modal, Popconfirm, Radio } from 'antd';
import { RadioChangeEvent } from 'antd/lib/radio';
import { ProposalDraft, RFP } from 'types';
import { getCreateErrors } from 'modules/create/utils';
import { Link } from 'react-router-dom';
import { unlinkProposalRFP } from 'modules/create/actions';
import { AppState } from 'store/reducers';
import './Basics.less';
interface OwnProps {
proposalId: number;
initialState?: Partial<State>;
updateForm(form: Partial<ProposalDraft>): void;
}
@ -30,6 +32,7 @@ interface State extends Partial<ProposalDraft> {
brief: string;
target: string;
rfp?: RFP;
visible: boolean
}
class CreateFlowBasics extends React.Component<Props, State> {
@ -39,10 +42,29 @@ class CreateFlowBasics extends React.Component<Props, State> {
title: '',
brief: '',
target: '',
visible: false,
...(props.initialState || {}),
};
}
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = () => {
this.setState({
visible: false,
});
};
handleCancel = () => {
this.setState({
visible: false,
});
};
componentDidUpdate(prevProps: Props) {
const { unlinkProposalRFPError, isUnlinkingProposalRFP } = this.props;
if (
@ -72,21 +94,21 @@ class CreateFlowBasics extends React.Component<Props, State> {
}
return (
<Form layout="vertical" style={{ maxWidth: 600, margin: '0 auto' }}>
<Form layout='vertical' style={{ maxWidth: 600, margin: '0 auto' }}>
{rfp && (
<Alert
className="CreateFlow-rfpAlert"
type="info"
message="This proposal is linked to a request"
className='CreateFlow-rfpAlert'
type='info'
message='This proposal is linked to a request'
description={
<>
This proposal is for the open request{' '}
<Link to={`/requests/${rfp.id}`} target="_blank">
<Link to={`/requests/${rfp.id}`} target='_blank'>
{rfp.title}
</Link>
. If you didnt mean to do this, or want to unlink it,{' '}
<Popconfirm
title="Are you sure? This cannot be undone."
title='Are you sure? This cannot be undone.'
onConfirm={this.unlinkRfp}
okButtonProps={{ loading: isUnlinkingProposalRFP }}
>
@ -100,9 +122,9 @@ class CreateFlowBasics extends React.Component<Props, State> {
)}
<Alert
className="CreateFlow-rfpAlert"
type="warning"
message="KYC (know your customer)"
className='CreateFlow-rfpAlert'
type='warning'
message='KYC (know your customer)'
description={
<>
<div>
@ -110,12 +132,12 @@ class CreateFlowBasics extends React.Component<Props, State> {
provide identifying information to the Zcash Foundation.
<Radio.Group onChange={this.handleRfpOptIn}>
<Radio value={true} checked={rfpOptIn && rfpOptIn === true}>
<b>Yes</b>, I am willing to provide KYC information
<b>Yes</b>, I am willing to provide KYC information as outlined by the <span
onClick={() => this.showModal()} style={{ color: 'CF8A00', textDecoration: 'underline' }}>ZF KYC requirements</span>
</Radio>
<Radio value={false} checked={rfpOptIn !== null && rfpOptIn === false}>
<b>No</b>, I do not wish to provide KYC information and understand my
proposal may still be posted on ZF Grants, but I will not be eligible
to funding from the Zcash Foundation.
<b>No</b>, I do not wish to provide KYC information and understand I will not be able to submit my
proposal.
</Radio>
</Radio.Group>
</div>
@ -123,16 +145,82 @@ class CreateFlowBasics extends React.Component<Props, State> {
}
/>
<Modal
title='Know Your Customer (KYC) Compliance'
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
className={'KYCModal'}
>
<ol>
<li>To execute a transfer of funds, the Zcash Foundation is legally required to obtain the following
information
from you: [Privacy guarantee]
<ol>
<li>A photocopy of your state-issued identification (passport, driver's license, etc.)</li>
<li>A filled-out form <a href={'https://www.irs.gov/pub/irs-pdf/fw9.pdf'}>W-9</a> (if US taxpayer) or <a
href={'https://www.irs.gov/pub/irs-pdf/fw8ben.pdf'}>W-8BEN</a> (if nonresident alien individual), or
a <a href={'https://www.irs.gov/pub/irs-pdf/fw8bene.pdf'}>W-8BEN-E</a> (if foreign corporation)
</li>
<li>The Foundation will run a Sanctions Screening and Fraud Monitoring on each recipient of its funds.
As a condition of receiving the funds you represent to us, now and until the latter of the submission of a
report on the status of the work covered by the proposal or the use of all of the funds, (i) that you are not
in violation of any law relating to terrorism or money laundering (Anti-Terrorism Laws), including
Executive Order No. 13224 on Terrorist Financing, effective September 24, 2001 (the Executive Order), and the
Uniting and Strengthening America by Providing Appropriate Tools Required to Intercept and Obstruct
Terrorism Act of 2001(Title III of P.L. No. 107-56) (known as the PATRIOT Act). (ii) neither you or any affiliated
person or entity is a person that is listed in the annex to, or is otherwise subject to the provisions of,
the Executive Order or a person that is named as a specially designated national and blocked person on
the most current list published by the US Department of the Treasury, Office of Foreign Assets Control (OFAC) at its
official website or any replacement website or other replacement official publication of such list;
(iii) neither you or any affiliated person or entity is subject to blocking provisions or otherwise a target of
sanctions imposed under any sanctions program administered by OFAC; and (iv) neither you or any affiliate person
or entity deals in, or otherwise engages in any transaction relating to any property or interests in
property blocked pursuant to the Executive Order.
</li>
<li>With certain limited exceptions, in the following January the Zcash Foundation will report the value
of the funds as taxable income on either US tax form 1099-MISC (for US taxpayers) or 1042-S (for foreign
persons). These forms will report the value of the award in USD at the date it was distributed. You may need to
include this income when filing your taxes, and it may affect your total tax due and estimated tax
payments. Here are more details on <a href={'https://www.irs.gov/forms-pubs/about-form-1099-misc'}>filing the
1099-MISC</a> in the US, and its tax implications.
</li>
</ol>
</li>
<li>Your funds will be disbursed in a shielded Zcash cryptocurrency (ZEC), then it will be via an
on-blockchain
fund transfer transaction. The Foundation will use this third-party service and market for converting ZEC
to
other currencies are listed here based on the price of the agreed-upon date close of day at :
<a href={'https://messari.io/asset/zcash'}>https://messari.io/asset/zcash</a>. For all grants, the
agreed-upon date will be the date that the grant was
approved, as noted in the grant platform. Note that the Zcash Foundation understands the regulatory and
compliance risks associated with transacting in cryptocurrencies.
</li>
<li>Tax Implications: Please be aware that in some countries, taxes will be due on the ZEC grant you receive
(for the receipt of ZEC, when you sell/exchange it, or both). Specifically:
<ol>
<li>Capital gain tax may be due if you later sell/exchange your ZEC for a higher price</li>
</ol>
</li>
</ol>
</Modal>
<Form.Item
label="Title"
label='Title'
validateStatus={errors.title ? 'error' : undefined}
help={errors.title}
>
<Input
size="large"
name="title"
placeholder="Short and sweet"
type="text"
size='large'
name='title'
placeholder='Short and sweet'
type='text'
value={title}
onChange={this.handleInputChange}
maxLength={200}
@ -140,13 +228,13 @@ class CreateFlowBasics extends React.Component<Props, State> {
</Form.Item>
<Form.Item
label="Brief"
label='Brief'
validateStatus={errors.brief ? 'error' : undefined}
help={errors.brief}
>
<Input.TextArea
name="brief"
placeholder="A one-liner elevator-pitch version of your proposal, max 140 chars."
name='brief'
placeholder='A one-liner elevator-pitch version of your proposal, max 140 chars.'
value={brief}
onChange={this.handleInputChange}
rows={3}
@ -155,7 +243,7 @@ class CreateFlowBasics extends React.Component<Props, State> {
</Form.Item>
<Form.Item
label="Target amount"
label='Target amount'
validateStatus={errors.target ? 'error' : undefined}
help={
errors.target ||
@ -163,13 +251,13 @@ class CreateFlowBasics extends React.Component<Props, State> {
}
>
<Input
size="large"
name="target"
placeholder="1.5"
type="number"
size='large'
name='target'
placeholder='1.5'
type='number'
value={target}
onChange={this.handleInputChange}
addonBefore="$"
addonBefore='$'
maxLength={16}
/>
</Form.Item>

View File

@ -80,17 +80,17 @@ class HeaderDrawer extends React.Component<Props> {
<Menu.Item key="/proposals">
<Link to="/proposals">Browse proposals</Link>
</Menu.Item>
{/*<Menu.Item key="/create">*/}
{/* <Link to="/create">Start a proposal</Link>*/}
{/*</Menu.Item>*/}
<Menu.Item key="/create">
<Link to="/create">Start a proposal</Link>
</Menu.Item>
</Menu.ItemGroup>
<Menu.ItemGroup title="Requests">
<Menu.Item key="/requests">
<Link to="/requests">Browse requests</Link>
</Menu.Item>
<Menu.Item key="/create-request">
<Link to="/create-request">Create a Request</Link>
</Menu.Item>
{/*<Menu.Item key="/create-request">*/}
{/* <Link to="/create-request">Create a Request</Link>*/}
{/*</Menu.Item>*/}
</Menu.ItemGroup>
</Menu>
</Drawer>

View File

@ -105,15 +105,6 @@ class Header extends React.Component<Props, State> {
)}
</Link>
</div>
<div className="Header-links-button is-desktop">
<Link to="/create-request">
{Array.isArray(ccrDrafts) && ccrDrafts.length > 0 ? (
<Button type={'primary'}>My Requests</Button>
) : (
<Button type={'primary'}>Create a Request</Button>
)}
</Link>
</div>
<HeaderAuth/>
</div>

View File

@ -63,11 +63,11 @@
&-logo {
height: 2rem;
width: auto;
transform: translateY(-7%);
transform: scale(1.6) translateY(-7%);
transition: transform @header-transition ease;
.is-transparent & {
transform: scale(1.4) translateY(18%);
transform: scale(2) translateY(18%);
}
}
}

View File

@ -30,8 +30,8 @@ const HomeIntro: React.SFC<Props> = ({ t, authUser }) => (
{t('home.intro.signup')}
</Link>
)}
<Link className="HomeIntro-content-buttons-button" to="/create-request">
{t('home.intro.ccr')}
<Link className="HomeIntro-content-buttons-button" to="/create">
Create a Proposal
</Link>
</div>
</div>

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { UserProposal, STATUS } from 'types';
import { STATUS, UserProposal } from 'types';
import './ProfileProposal.less';
import UserRow from 'components/UserRow';
import UnitDisplay from 'components/UnitDisplay';
@ -23,7 +23,8 @@ export default class Profile extends React.Component<OwnProps> {
isVersionTwo,
acceptedWithFunding,
status,
changesRequestedDiscussionReason
changesRequestedDiscussionReason,
fundedByZomg,
} = this.props.proposal;
// pulled from `variables.less`
@ -31,18 +32,24 @@ export default class Profile extends React.Component<OwnProps> {
const secondaryColor = '#2D2A26';
const isOpenForDiscussion = status === STATUS.DISCUSSION;
const discussionColor = changesRequestedDiscussionReason ? 'red' : infoColor
const discussionTag = changesRequestedDiscussionReason ? 'Changes Requested' : 'Open for Public Review'
const discussionColor = changesRequestedDiscussionReason ? 'red' : infoColor;
const discussionTag = changesRequestedDiscussionReason
? 'Changes Requested'
: 'Open for Public Review';
let tagColor = infoColor
let tagMessage = 'Open for Contributions'
let tagColor = infoColor;
let tagMessage = 'Open for Contributions';
if (acceptedWithFunding) {
tagColor = secondaryColor
tagMessage = 'Funded by ZF'
tagColor = secondaryColor;
if (!fundedByZomg) {
tagMessage = 'Funded by ZF';
} else {
tagMessage = 'Funded by ZOMG';
}
} else if (isOpenForDiscussion) {
tagColor = discussionColor
tagMessage = discussionTag
tagColor = discussionColor;
tagMessage = discussionTag;
}
return (

View File

@ -1,6 +1,6 @@
import React from 'react';
import moment from 'moment';
import { Icon, Popover, Tooltip, Alert } from 'antd';
import { Alert, Icon, Popover, Tooltip } from 'antd';
import { Proposal, STATUS } from 'types';
import classnames from 'classnames';
import { connect } from 'react-redux';
@ -12,6 +12,8 @@ import Loader from 'components/Loader';
import { PROPOSAL_STAGE } from 'api/constants';
import { formatUsd } from 'utils/formatters';
import ZFGrantsLogo from 'static/images/logo-name-light.svg';
import ZomgLogo from 'static/images/zomg-logo.png';
import './style.less';
interface OwnProps {
@ -134,7 +136,11 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
isAcceptedWithFunding && (
<div className="ProposalCampaignBlock-with-funding">
Funded through &nbsp;
<ZFGrantsLogo style={{ height: '1.5rem' }} />
{proposal.fundedByZomg ? (
<img src={ZomgLogo} alt={'Zomg logo'} style={{ height: '1.5rem' }} />
) : (
<ZFGrantsLogo style={{ height: '1.5rem' }} />
)}
</div>
)}

View File

@ -29,6 +29,7 @@ export class ProposalCard extends React.Component<Proposal> {
percentFunded,
acceptedWithFunding,
status,
fundedByZomg,
} = this.props;
// pulled from `variables.less`
@ -46,7 +47,11 @@ export class ProposalCard extends React.Component<Proposal> {
if (isVersionTwo && status === STATUS.LIVE) {
if (acceptedWithFunding) {
tagColor = secondaryColor;
tagMessage = 'Funded by ZF';
if (!fundedByZomg) {
tagMessage = 'Funded by ZF';
} else {
tagMessage = 'Funded by ZOMG';
}
} else {
tagColor = infoColor;
tagMessage = 'Not Funded';

View File

@ -83,8 +83,8 @@ export function getCreateErrors(
}
// RFP opt-in
if (rfpOptIn === null) {
errors.rfpOptIn = 'Please accept or decline KYC';
if (!rfpOptIn) {
errors.rfpOptIn = 'Please accept KYC to submit.';
}
// Title
@ -260,6 +260,7 @@ export function makeProposalPreviewFromDraft(draft: ProposalDraft): ProposalDeta
authedLiked: false,
likesCount: 0,
isVersionTwo: true,
fundedByZomg: false,
milestones: draft.milestones.map((m, idx) => ({
id: idx,
index: idx,

View File

@ -0,0 +1,7 @@
import React from 'react';
import MarkdownPage from 'components/MarkdownPage';
import KYC from 'static/markdown/KYC.md';
const Kyc = () => <MarkdownPage markdown={KYC} />;
export default Kyc;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 70 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 74 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1300.8 419.2" style="enable-background:new 0 0 1300.8 419.2;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;fill:#100400;}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#0F7000;}
.st4{fill:#FFFFFF;}
.st5{fill:#F8BB14;}
.st6{fill:none;stroke:#FFFFFF;stroke-width:24.24;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;}
.st7{font-family:'Roboto-Medium';}
.st8{font-size:304.0703px;}
</style>
<g id="Layer_2" class="st0">
<rect x="-102.5" y="-82" class="st1" width="1515.7" height="567.6"/>
</g>
<g id="Layer_1" xmlns:serif="http://www.serif.com/">
<g>
<g transform="matrix(0.347046,-4.1523e-17,-1.03807e-17,0.347046,307.53,12.3394)">
<g id="Color">
<g>
<g id="Color1" transform="matrix(1.09544,1.31066e-16,3.27664e-17,1.09544,-953.204,-33.2755)" serif:id="Color">
<g transform="matrix(1.19303,-3.26522e-48,2.90837e-48,1.06265,-101.088,-115.302)">
<rect x="572.7" y="564.3" class="st2" width="37.4" height="265.4"/>
</g>
<g transform="matrix(1,-2.73691e-48,2.73691e-48,1,0,-21.0434)">
<path class="st3" d="M970,472.4c-7.7-25.6-24.4,55.7-167.7,79.1c-112.6,18.3-168.6,129.2-148.5,161.9
c13.1,21.4,111.2,52.7,194.7-8.3C931.7,644.3,977.7,498,970,472.4z M651.6,707c3,5.4,78.8-15.7,123.6-40.6
c52.8-29.3,108.9-79.9,104.4-85.7c-2.9-3.8-63.8,23.9-123.7,57.7C704.1,667.6,646.4,697.7,651.6,707z"/>
</g>
<g transform="matrix(1,-2.73691e-48,2.73691e-48,1,0,-21.0434)">
<path class="st3" d="M295.5,443.5C308,424.7,306.9,490,436.7,528c102,29.9,131.7,123.6,106.1,146.1
c-16.7,14.7-115.4,25.3-181.2-33.5C295.9,581.9,283,462.2,295.5,443.5z M544.7,672c-2.8,3.9-52.5-14.7-91.1-39
c-30-19-76.9-55.4-64.6-63.8c5.6-3.8,41.1,21,77.6,44.2C504.4,637.4,550.2,664.4,544.7,672z"/>
</g>
<g transform="matrix(0.927915,-1.11022e-16,-2.77556e-17,0.927915,854.005,46.6736)">
<g transform="matrix(1,0,0,1,-581.95,-44.2319)">
<path class="st4" d="M312.2,514.8c-131.2,0-238-106.8-238-238c0-131.2,106.8-238,238-238c131.2,0,238,106.8,238,238
C550.2,408,443.4,514.8,312.2,514.8z M312.2-8.3C155-8.3,27.1,119.6,27.1,276.8S155,561.9,312.2,561.9
S597.3,434,597.3,276.8S469.4-8.3,312.2-8.3z"/>
</g>
<path class="st5" d="M-269.8,7.8c-123.9,0-224.7,100.8-224.7,224.7c0,123.9,100.8,224.7,224.7,224.7S-45.1,356.4-45.1,232.5
C-45.1,108.6-145.9,7.8-269.8,7.8z M-168.1,143.7l-22,27.9l-98.6,135.7h120.6v57.6h-77.8v47.6h-5.7v0.2h-36.3v-0.2h-5.7
v-47.6h-77.8v-43.5l22-27.9l98.6-135.7h-120.6v-57.6h77.8V52.5h47.8v47.7h77.8V143.7z"/>
</g>
</g>
<g id="Hand" transform="matrix(1.23479,1.47739e-16,3.95e-17,1.32055,-1023.9,-346.117)">
<path class="st6" d="M139.9,880c0,0,93-1.5,138.8-9.1c48.9-8.1,105.2-39.7,154.8-39.4c49.5,0.3,87.5,32.1,142.6,41.4
c59,10,156.2,9.9,180.2,27.8c19.8,14.7,5.2,49.9-32.8,64c-26,9.7-73.4,19.4-110.9,19.1c-53.6-0.5-217.9-23.2-210.9-21.8
c7,1.4,168.5,39.2,252.9,30.2c84.8-9,192.9-67.4,256.1-84.4c40-10.7,92.5-19.5,123.1-17.3c38.6,2.8,64,22.1,47.7,32.5
c-57.7,36.8-263.6,156-393.9,188.1c-86.5,21.3-273,9.5-313,4.2c-60.9-8-98.3-24.4-139.4-53.2c-23.7-16.6-95.3-45.2-95.3-45.2
V880z"/>
</g>
<g transform="matrix(121.473,1.45339e-14,3.31689e-15,110.889,-21599.6,-106761)">
<text transform="matrix(2.598518e-02 0 0 2.598510e-02 181.6097 969.9001)" class="st4 st7 st8">zomg</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,6 @@
# KYC Policy
To execute a transfer of funds, the Zcash Foundation is legally required to comply with the requirements [described here](https://www.zfnd.org/about/aml-kyc-requirements/). Please send the following to <a href="mailto:grants@zfnd.org">grants@zfnd.org</a>, we are unable to send money until we receive this documentation:
- A photocopy of your state-issued identification (passport, drivers license, etc.).
- A filled-out form [W-9](https://www.irs.gov/pub/irs-pdf/fw9.pdf) (if US taxpayer) or [W-8BEN](https://www.irs.gov/pub/irs-pdf/fw8ben.pdf) (if nonresident alien individual), or a [W-8BEN-E](https://www.irs.gov/pub/irs-pdf/fw8bene.pdf) (if foreign corporation).

View File

@ -43,7 +43,6 @@
ul,
ol {
padding-left: 30px;
font-size: 1.05rem;
}
ul {

View File

@ -190,6 +190,7 @@
"@types/storybook__react": "^3.0.9",
"rimraf": "2.6.2",
"string-hash": "1.1.3",
"webapp-webpack-plugin": "2.3.1"
"webapp-webpack-plugin": "2.3.1",
"https-proxy-agent": "^2.2.3"
}
}

View File

@ -170,6 +170,7 @@ export function generateProposal({
liveDraftId: null,
tipJarAddress: null,
tipJarViewKey: null,
fundedByZomg: false,
arbiter: {
status: PROPOSAL_ARBITER_STATUS.ACCEPTED,
user: {

View File

@ -81,6 +81,7 @@ export interface Proposal extends Omit<ProposalDraft, 'target' | 'invites'> {
liveDraftId: string | null;
isTeamMember?: boolean; // FE derived
isArbiter?: boolean; // FE derived
fundedByZomg: boolean;
}
export interface TeamInviteWithProposal extends TeamInvite {
@ -124,6 +125,7 @@ export interface UserProposal {
changesRequestedDiscussionReason: string | null;
acceptedWithFunding: boolean | null;
isVersionTwo: boolean;
fundedByZomg: boolean;
}
// NOTE: sync with backend/grant/proposal/models.py STATUSES

View File

@ -2438,6 +2438,13 @@ agent-base@^4.1.0:
dependencies:
es6-promisify "^5.0.0"
agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
dependencies:
es6-promisify "^5.0.0"
"airbnb-js-shims@^1 || ^2":
version "2.1.1"
resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.1.1.tgz#a509611480db7e6d9db62fe2acfaeb473b6842ac"
@ -6530,6 +6537,14 @@ https-proxy-agent@2.2.1:
agent-base "^4.1.0"
debug "^3.1.0"
https-proxy-agent@^2.2.3:
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
dependencies:
agent-base "^4.3.0"
debug "^3.1.0"
https-proxy@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/https-proxy/-/https-proxy-0.0.2.tgz#9e7d542f1ce8d37c06e1f940a8a9a227bb48ddf0"