|
@ -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
|
|
@ -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
|
|
@ -2,34 +2,19 @@ import React from 'react';
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
import { view } from 'react-easy-state';
|
import { view } from 'react-easy-state';
|
||||||
import { RouteComponentProps, withRouter } from 'react-router';
|
import { RouteComponentProps, withRouter } from 'react-router';
|
||||||
import {
|
import { Alert, Button, Card, Col, Collapse, Input, message, Popconfirm, Row, Switch, Tag } from 'antd';
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Card,
|
|
||||||
Alert,
|
|
||||||
Button,
|
|
||||||
Collapse,
|
|
||||||
Popconfirm,
|
|
||||||
Input,
|
|
||||||
Tag,
|
|
||||||
message,
|
|
||||||
} from 'antd';
|
|
||||||
import TextArea from 'antd/lib/input/TextArea';
|
import TextArea from 'antd/lib/input/TextArea';
|
||||||
import store from 'src/store';
|
import store from 'src/store';
|
||||||
import { formatDateSeconds, formatDurationSeconds } from 'util/time';
|
import { formatDateSeconds, formatDurationSeconds } from 'util/time';
|
||||||
import {
|
import { MILESTONE_STAGE, PROPOSAL_ARBITER_STATUS, PROPOSAL_STAGE, PROPOSAL_STATUS } from 'src/types';
|
||||||
PROPOSAL_STATUS,
|
|
||||||
PROPOSAL_ARBITER_STATUS,
|
|
||||||
MILESTONE_STAGE,
|
|
||||||
PROPOSAL_STAGE,
|
|
||||||
} from 'src/types';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Back from 'components/Back';
|
import Back from 'components/Back';
|
||||||
import Markdown from 'components/Markdown';
|
import Markdown from 'components/Markdown';
|
||||||
import ArbiterControl from 'components/ArbiterControl';
|
import ArbiterControl from 'components/ArbiterControl';
|
||||||
import { toZat, fromZat } from 'src/util/units';
|
import { fromZat, toZat } from 'src/util/units';
|
||||||
import FeedbackModal from '../FeedbackModal';
|
import FeedbackModal from '../FeedbackModal';
|
||||||
import { formatUsd } from 'util/formatters';
|
import { formatUsd } from 'util/formatters';
|
||||||
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
type Props = RouteComponentProps<any>;
|
type Props = RouteComponentProps<any>;
|
||||||
|
@ -45,9 +30,11 @@ type State = typeof STATE;
|
||||||
class ProposalDetailNaked extends React.Component<Props, State> {
|
class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
state = STATE;
|
state = STATE;
|
||||||
rejectInput: null | TextArea = null;
|
rejectInput: null | TextArea = null;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadDetail();
|
this.loadDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const id = this.getIdFromQuery();
|
const id = this.getIdFromQuery();
|
||||||
const { proposalDetail: p, proposalDetailFetching } = store;
|
const { proposalDetail: p, proposalDetailFetching } = store;
|
||||||
|
@ -56,6 +43,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
return 'loading proposal...';
|
return 'loading proposal...';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(p.fundedByZomg);
|
||||||
|
|
||||||
const needsArbiter =
|
const needsArbiter =
|
||||||
PROPOSAL_ARBITER_STATUS.MISSING === p.arbiter.status &&
|
PROPOSAL_ARBITER_STATUS.MISSING === p.arbiter.status &&
|
||||||
p.status === PROPOSAL_STATUS.LIVE &&
|
p.status === PROPOSAL_STATUS.LIVE &&
|
||||||
|
@ -92,9 +81,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
placement="left"
|
placement='left'
|
||||||
cancelText="cancel"
|
cancelText='cancel'
|
||||||
okText="confirm"
|
okText='confirm'
|
||||||
visible={this.state.showCancelAndRefundPopover}
|
visible={this.state.showCancelAndRefundPopover}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
loading: store.proposalDetailCanceling,
|
loading: store.proposalDetailCanceling,
|
||||||
|
@ -103,8 +92,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
onConfirm={this.handleConfirmCancel}
|
onConfirm={this.handleConfirmCancel}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
icon="close-circle"
|
icon='close-circle'
|
||||||
className="ProposalDetail-controls-control"
|
className='ProposalDetail-controls-control'
|
||||||
loading={store.proposalDetailCanceling}
|
loading={store.proposalDetailCanceling}
|
||||||
onClick={this.handleCancelAndRefundClick}
|
onClick={this.handleCancelAndRefundClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -126,9 +115,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
with funding? This cannot be undone.
|
with funding? This cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
placement="left"
|
placement='left'
|
||||||
cancelText="cancel"
|
cancelText='cancel'
|
||||||
okText="confirm"
|
okText='confirm'
|
||||||
visible={this.state.showChangeToAcceptedWithFundingPopover}
|
visible={this.state.showChangeToAcceptedWithFundingPopover}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
loading: store.proposalDetailCanceling,
|
loading: store.proposalDetailCanceling,
|
||||||
|
@ -137,8 +126,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
onConfirm={this.handleChangeToAcceptWithFundingConfirm}
|
onConfirm={this.handleChangeToAcceptWithFundingConfirm}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
icon="close-circle"
|
icon='close-circle'
|
||||||
className="ProposalDetail-controls-control"
|
className='ProposalDetail-controls-control'
|
||||||
loading={store.proposalDetailChangingToAcceptedWithFunding}
|
loading={store.proposalDetailChangingToAcceptedWithFunding}
|
||||||
onClick={this.handleChangeToAcceptedWithFunding}
|
onClick={this.handleChangeToAcceptedWithFunding}
|
||||||
block
|
block
|
||||||
|
@ -168,7 +157,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
p.status === PROPOSAL_STATUS.APPROVED && (
|
p.status === PROPOSAL_STATUS.APPROVED && (
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type="success"
|
type='success'
|
||||||
message={`Approved on ${formatDateSeconds(p.dateApproved)}`}
|
message={`Approved on ${formatDateSeconds(p.dateApproved)}`}
|
||||||
description={`
|
description={`
|
||||||
This proposal has been approved and will become live when a team-member
|
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
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type={p.rfpOptIn ? 'success' : 'error'}
|
type={p.rfpOptIn ? 'success' : 'error'}
|
||||||
message={p.rfpOptIn ? 'KYC accepted' : 'KYC rejected'}
|
message={p.rfpOptIn ? 'KYC Accepted by user' : 'KYC rejected'}
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
{p.rfpOptIn ? (
|
{p.rfpOptIn ? (
|
||||||
|
@ -204,25 +193,25 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
<Col span={isVersionTwo ? 16 : 24}>
|
<Col span={isVersionTwo ? 16 : 24}>
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type="warning"
|
type='warning'
|
||||||
message="Review Discussion"
|
message='Review Discussion'
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<p>Please review this proposal and render your judgment.</p>
|
<p>Please review this proposal and render your judgment.</p>
|
||||||
<Button
|
<Button
|
||||||
className="ProposalDetail-review"
|
className='ProposalDetail-review'
|
||||||
loading={store.proposalDetailApprovingDiscussion}
|
loading={store.proposalDetailApprovingDiscussion}
|
||||||
icon="check"
|
icon='check'
|
||||||
type="primary"
|
type='primary'
|
||||||
onClick={() => this.handleApproveDiscussion()}
|
onClick={() => this.handleApproveDiscussion()}
|
||||||
>
|
>
|
||||||
Open for Public Review
|
Open for Public Review
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="ProposalDetail-review"
|
className='ProposalDetail-review'
|
||||||
loading={store.proposalDetailApprovingDiscussion}
|
loading={store.proposalDetailApprovingDiscussion}
|
||||||
icon="warning"
|
icon='warning'
|
||||||
type="default"
|
type='default'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
FeedbackModal.open({
|
FeedbackModal.open({
|
||||||
title: 'Request changes to this proposal?',
|
title: 'Request changes to this proposal?',
|
||||||
|
@ -235,10 +224,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
Request Changes
|
Request Changes
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="ProposalDetail-review"
|
className='ProposalDetail-review'
|
||||||
loading={store.proposalDetailRejectingPermanently}
|
loading={store.proposalDetailRejectingPermanently}
|
||||||
icon="close"
|
icon='close'
|
||||||
type="danger"
|
type='danger'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
FeedbackModal.open({
|
FeedbackModal.open({
|
||||||
title: 'Reject this proposal permanently?',
|
title: 'Reject this proposal permanently?',
|
||||||
|
@ -267,34 +256,38 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
<Col span={isVersionTwo ? 16 : 24}>
|
<Col span={isVersionTwo ? 16 : 24}>
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type="warning"
|
type='warning'
|
||||||
message="Review Pending"
|
message='Review Pending'
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<p>Please review this proposal and render your judgment.</p>
|
<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
|
<Button
|
||||||
className="ProposalDetail-review"
|
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.proposalDetailMarkingChangesAsResolved}
|
loading={store.proposalDetailMarkingChangesAsResolved}
|
||||||
icon="close"
|
icon='close'
|
||||||
type="danger"
|
type='danger'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
FeedbackModal.open({
|
FeedbackModal.open({
|
||||||
title: 'Request changes to this proposal?',
|
title: 'Request changes to this proposal?',
|
||||||
|
@ -319,8 +312,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
p.status === PROPOSAL_STATUS.REJECTED && (
|
p.status === PROPOSAL_STATUS.REJECTED && (
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type="error"
|
type='error'
|
||||||
message="Changes requested"
|
message='Changes requested'
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
@ -340,8 +333,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
p.changesRequestedDiscussion && (
|
p.changesRequestedDiscussion && (
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type="error"
|
type='error'
|
||||||
message="Changes requested"
|
message='Changes requested'
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
@ -354,10 +347,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
<br />
|
<br />
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<Button
|
<Button
|
||||||
className="ProposalDetail-review"
|
className='ProposalDetail-review'
|
||||||
loading={false}
|
loading={false}
|
||||||
icon="check"
|
icon='check'
|
||||||
type="danger"
|
type='danger'
|
||||||
onClick={this.handleMarkChangesAsResolved}
|
onClick={this.handleMarkChangesAsResolved}
|
||||||
>
|
>
|
||||||
Mark Request as Resolved
|
Mark Request as Resolved
|
||||||
|
@ -371,17 +364,44 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
const renderNominateArbiter = () =>
|
const renderNominateArbiter = () =>
|
||||||
needsArbiter &&
|
needsArbiter &&
|
||||||
shouldShowArbiter && (
|
shouldShowArbiter && (
|
||||||
<Alert
|
<>
|
||||||
showIcon
|
{!p.kycApproved ? (
|
||||||
type="warning"
|
<Alert
|
||||||
message="No arbiter on live proposal"
|
showIcon
|
||||||
description={
|
type='error'
|
||||||
<div>
|
message='KYC approval required'
|
||||||
<p>An arbiter is required to review milestone payout requests.</p>
|
description={
|
||||||
<ArbiterControl {...p} />
|
<div>
|
||||||
</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 = () =>
|
const renderNominatedArbiter = () =>
|
||||||
|
@ -389,8 +409,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
p.status === PROPOSAL_STATUS.LIVE && (
|
p.status === PROPOSAL_STATUS.LIVE && (
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type="info"
|
type='info'
|
||||||
message="Arbiter has been nominated"
|
message='Arbiter has been nominated'
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
@ -436,9 +456,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
className="ProposalDetail-alert"
|
className='ProposalDetail-alert'
|
||||||
showIcon
|
showIcon
|
||||||
type="warning"
|
type='warning'
|
||||||
message={null}
|
message={null}
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
|
@ -454,9 +474,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
</p>{' '}
|
</p>{' '}
|
||||||
<pre>{p.payoutAddress}</pre>
|
<pre>{p.payoutAddress}</pre>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder="please enter payment txid"
|
placeholder='please enter payment txid'
|
||||||
value={this.state.paidTxId}
|
value={this.state.paidTxId}
|
||||||
enterButton="Mark Paid"
|
enterButton='Mark Paid'
|
||||||
onChange={e => this.setState({ paidTxId: e.target.value })}
|
onChange={e => this.setState({ paidTxId: e.target.value })}
|
||||||
onSearch={this.handlePaidMilestone}
|
onSearch={this.handlePaidMilestone}
|
||||||
/>
|
/>
|
||||||
|
@ -470,7 +490,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
p.isFailed && (
|
p.isFailed && (
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type="error"
|
type='error'
|
||||||
message={
|
message={
|
||||||
p.stage === PROPOSAL_STAGE.FAILED ? 'Proposal failed' : 'Proposal canceled'
|
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) => (
|
const renderDeetItem = (name: string, val: any) => (
|
||||||
<div className="ProposalDetail-deet">
|
<div className='ProposalDetail-deet'>
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
{val}
|
{val}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
return (
|
return (
|
||||||
<div className="ProposalDetail">
|
<div className='ProposalDetail'>
|
||||||
<Back to="/proposals" text="Proposals" />
|
<Back to='/proposals' text='Proposals' />
|
||||||
<h1>{p.title}</h1>
|
<h1>{p.title}</h1>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
{/* MAIN */}
|
{/* MAIN */}
|
||||||
|
@ -515,22 +536,22 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
{renderMilestoneAccepted()}
|
{renderMilestoneAccepted()}
|
||||||
{renderFailed()}
|
{renderFailed()}
|
||||||
<Collapse defaultActiveKey={['brief', 'content', 'milestones']}>
|
<Collapse defaultActiveKey={['brief', 'content', 'milestones']}>
|
||||||
<Collapse.Panel key="brief" header="brief">
|
<Collapse.Panel key='brief' header='brief'>
|
||||||
{p.brief}
|
{p.brief}
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
|
|
||||||
<Collapse.Panel key="content" header="content">
|
<Collapse.Panel key='content' header='content'>
|
||||||
<Markdown source={p.content} />
|
<Markdown source={p.content} />
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
|
|
||||||
<Collapse.Panel key="milestones" header="milestones">
|
<Collapse.Panel key='milestones' header='milestones'>
|
||||||
{p.milestones.map((milestone, i) => (
|
{p.milestones.map((milestone, i) => (
|
||||||
<Card
|
<Card
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
{milestone.title + ' '}
|
{milestone.title + ' '}
|
||||||
{milestone.immediatePayout && (
|
{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>
|
||||||
|
|
||||||
<Collapse.Panel key="json" header="json">
|
<Collapse.Panel key='json' header='json'>
|
||||||
<pre>{JSON.stringify(p, null, 4)}</pre>
|
<pre>{JSON.stringify(p, null, 4)}</pre>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
@ -564,26 +585,38 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
{/* RIGHT SIDE */}
|
{/* RIGHT SIDE */}
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
{p.isVersionTwo &&
|
{p.isVersionTwo &&
|
||||||
!p.acceptedWithFunding &&
|
!p.acceptedWithFunding &&
|
||||||
p.stage === PROPOSAL_STAGE.WIP && (
|
p.stage === PROPOSAL_STAGE.WIP && (
|
||||||
<Alert
|
<Alert
|
||||||
message="Accepted without funding"
|
message='Accepted without funding'
|
||||||
description="This proposal has been posted publicly, but isn't being funded by the Zcash Foundation."
|
description="This proposal has been posted publicly, but isn't being funded by the Zcash Foundation."
|
||||||
type="info"
|
type='info'
|
||||||
showIcon
|
showIcon
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ACTIONS */}
|
{/* ACTIONS */}
|
||||||
<Card size="small" className="ProposalDetail-controls">
|
<Card size='small' className='ProposalDetail-controls'>
|
||||||
{renderCancelControl()}
|
{renderCancelControl()}
|
||||||
{renderArbiterControl()}
|
{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 &&
|
{shouldShowChangeToAcceptedWithFunding &&
|
||||||
renderChangeToAcceptedWithFundingControl()}
|
renderChangeToAcceptedWithFundingControl()}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* DETAILS */}
|
{/* DETAILS */}
|
||||||
<Card title="Details" size="small">
|
<Card title='Details' size='small'>
|
||||||
{renderDeetItem('id', p.proposalId)}
|
{renderDeetItem('id', p.proposalId)}
|
||||||
{renderDeetItem('created', formatDateSeconds(p.dateCreated))}
|
{renderDeetItem('created', formatDateSeconds(p.dateCreated))}
|
||||||
{renderDeetItem(
|
{renderDeetItem(
|
||||||
|
@ -595,10 +628,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
formatDurationSeconds(p.deadlineDuration),
|
formatDurationSeconds(p.deadlineDuration),
|
||||||
)}
|
)}
|
||||||
{p.datePublished &&
|
{p.datePublished &&
|
||||||
renderDeetItem(
|
renderDeetItem(
|
||||||
'(deadline)',
|
'(deadline)',
|
||||||
formatDateSeconds(p.datePublished + p.deadlineDuration),
|
formatDateSeconds(p.datePublished + p.deadlineDuration),
|
||||||
)}
|
)}
|
||||||
{renderDeetItem('isFailed', JSON.stringify(p.isFailed))}
|
{renderDeetItem('isFailed', JSON.stringify(p.isFailed))}
|
||||||
{renderDeetItem('status', p.status)}
|
{renderDeetItem('status', p.status)}
|
||||||
{renderDeetItem('stage', p.stage)}
|
{renderDeetItem('stage', p.stage)}
|
||||||
|
@ -627,14 +660,14 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
</>,
|
</>,
|
||||||
)}
|
)}
|
||||||
{p.rfp &&
|
{p.rfp &&
|
||||||
renderDeetItem(
|
renderDeetItem(
|
||||||
'rfp',
|
'rfp',
|
||||||
<Link to={`/rfps/${p.rfp.id}`}>{p.rfp.title}</Link>,
|
<Link to={`/rfps/${p.rfp.id}`}>{p.rfp.title}</Link>,
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* TEAM */}
|
{/* TEAM */}
|
||||||
<Card title="Team" size="small">
|
<Card title='Team' size='small'>
|
||||||
{p.team.map(t => (
|
{p.team.map(t => (
|
||||||
<div key={t.userid}>
|
<div key={t.userid}>
|
||||||
<Link to={`/users/${t.userid}`}>{t.displayName}</Link>
|
<Link to={`/users/${t.userid}`}>{t.displayName}</Link>
|
||||||
|
@ -716,6 +749,11 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
message.info('Proposal rejected permanently');
|
message.info('Proposal rejected permanently');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleApproveKYC = async () => {
|
||||||
|
await store.approveProposalKYC();
|
||||||
|
message.info(`Proposal KYC approved`);
|
||||||
|
};
|
||||||
|
|
||||||
private handleAcceptProposal = async (
|
private handleAcceptProposal = async (
|
||||||
isAccepted: boolean,
|
isAccepted: boolean,
|
||||||
withFunding: boolean,
|
withFunding: boolean,
|
||||||
|
@ -743,6 +781,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
await store.markMilestonePaid(pid, mid, this.state.paidTxId);
|
await store.markMilestonePaid(pid, mid, this.state.paidTxId);
|
||||||
message.success('Marked milestone paid.');
|
message.success('Marked milestone paid.');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleSwitchFunder = async (checkValue: boolean) => {
|
||||||
|
store.switchProposalFunder(checkValue);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProposalDetail = withRouter(view(ProposalDetailNaked));
|
const ProposalDetail = withRouter(view(ProposalDetailNaked));
|
||||||
|
|
|
@ -2,17 +2,17 @@ import { pick } from 'lodash';
|
||||||
import { store } from 'react-easy-state';
|
import { store } from 'react-easy-state';
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import {
|
import {
|
||||||
User,
|
|
||||||
Proposal,
|
|
||||||
CCR,
|
CCR,
|
||||||
|
CommentArgs,
|
||||||
Contribution,
|
Contribution,
|
||||||
ContributionArgs,
|
ContributionArgs,
|
||||||
|
EmailExample,
|
||||||
|
PageData,
|
||||||
|
PageQuery,
|
||||||
|
Proposal,
|
||||||
RFP,
|
RFP,
|
||||||
RFPArgs,
|
RFPArgs,
|
||||||
EmailExample,
|
User,
|
||||||
PageQuery,
|
|
||||||
PageData,
|
|
||||||
CommentArgs,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// API
|
// API
|
||||||
|
@ -142,6 +142,16 @@ async function approveDiscussion(
|
||||||
return data;
|
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(
|
async function acceptProposal(
|
||||||
id: number,
|
id: number,
|
||||||
isAccepted: boolean,
|
isAccepted: boolean,
|
||||||
|
@ -345,6 +355,8 @@ const app = store({
|
||||||
proposalDetailApprovingDiscussion: false,
|
proposalDetailApprovingDiscussion: false,
|
||||||
proposalDetailMarkingChangesAsResolved: false,
|
proposalDetailMarkingChangesAsResolved: false,
|
||||||
proposalDetailAcceptingProposal: false,
|
proposalDetailAcceptingProposal: false,
|
||||||
|
proposalDetailApprovingKyc: false,
|
||||||
|
proposalDetailSwitchingFunder: false,
|
||||||
proposalDetailMarkingMilestonePaid: false,
|
proposalDetailMarkingMilestonePaid: false,
|
||||||
proposalDetailCanceling: false,
|
proposalDetailCanceling: false,
|
||||||
proposalDetailUpdating: false,
|
proposalDetailUpdating: false,
|
||||||
|
@ -688,6 +700,43 @@ const app = store({
|
||||||
handleApiError(e);
|
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(
|
async acceptProposal(
|
||||||
isAccepted: boolean,
|
isAccepted: boolean,
|
||||||
withFunding: boolean,
|
withFunding: boolean,
|
||||||
|
@ -975,6 +1024,7 @@ function createDefaultPageData<T>(sort: string): PageData<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FNFetchPage = (params: PageQuery) => Promise<any>;
|
type FNFetchPage = (params: PageQuery) => Promise<any>;
|
||||||
|
|
||||||
interface PageParent<T> {
|
interface PageParent<T> {
|
||||||
page: PageData<T>;
|
page: PageData<T>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,8 @@ export interface Proposal {
|
||||||
isVersionTwo: boolean;
|
isVersionTwo: boolean;
|
||||||
changesRequestedDiscussion: boolean | null;
|
changesRequestedDiscussion: boolean | null;
|
||||||
changesRequestedDiscussionReason: string | null;
|
changesRequestedDiscussionReason: string | null;
|
||||||
|
kycApproved: null | boolean;
|
||||||
|
fundedByZomg: boolean;
|
||||||
}
|
}
|
||||||
export interface Comment {
|
export interface Comment {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -5414,7 +5414,7 @@ minimist@^1.2.0, minimist@~1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||||
|
|
||||||
minimist@^1.2.2:
|
minimist@^1.2.3:
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||||
|
|
|
@ -4,7 +4,7 @@ from functools import reduce
|
||||||
|
|
||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from marshmallow import fields, validate
|
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.admin as admin
|
||||||
import grant.utils.auth as auth
|
import grant.utils.auth as auth
|
||||||
|
@ -25,7 +25,7 @@ from grant.proposal.models import (
|
||||||
admin_proposal_contributions_schema,
|
admin_proposal_contributions_schema,
|
||||||
)
|
)
|
||||||
from grant.rfp.models import RFP, admin_rfp_schema, admin_rfps_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 import pagination
|
||||||
from grant.utils.enums import (
|
from grant.utils.enums import (
|
||||||
ProposalStatus,
|
ProposalStatus,
|
||||||
|
@ -377,6 +377,35 @@ def open_proposal_for_discussion(proposal_id, is_open_for_discussion, reject_rea
|
||||||
return proposal_schema.dump(proposal)
|
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'])
|
@blueprint.route('/proposals/<id>/accept', methods=['PUT'])
|
||||||
@body({
|
@body({
|
||||||
"isAccepted": fields.Bool(required=True),
|
"isAccepted": fields.Bool(required=True),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
from typing import Optional
|
|
||||||
from decimal import Decimal, ROUND_DOWN
|
from decimal import Decimal, ROUND_DOWN
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from marshmallow import post_dump
|
from marshmallow import post_dump
|
||||||
from sqlalchemy import func, or_, select, ForeignKey
|
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 sqlalchemy.orm import column_property
|
||||||
|
|
||||||
from grant.comment.models import Comment
|
from grant.comment.models import Comment
|
||||||
from grant.milestone.models import Milestone
|
|
||||||
from grant.email.send import send_email
|
from grant.email.send import send_email
|
||||||
from grant.extensions import ma, db
|
from grant.extensions import ma, db
|
||||||
|
from grant.milestone.models import Milestone
|
||||||
from grant.settings import PROPOSAL_STAKING_AMOUNT, PROPOSAL_TARGET_MAX
|
from grant.settings import PROPOSAL_STAKING_AMOUNT, PROPOSAL_TARGET_MAX
|
||||||
from grant.task.jobs import ContributionExpired
|
from grant.task.jobs import ContributionExpired
|
||||||
from grant.utils.enums import (
|
from grant.utils.enums import (
|
||||||
ProposalStatus,
|
ProposalStatus,
|
||||||
ProposalStage,
|
ProposalStage,
|
||||||
Category,
|
|
||||||
ContributionStatus,
|
ContributionStatus,
|
||||||
ProposalArbiterStatus,
|
ProposalArbiterStatus,
|
||||||
MilestoneStage,
|
MilestoneStage,
|
||||||
|
@ -332,43 +331,49 @@ class ProposalRevision(db.Model):
|
||||||
if old_proposal.title != new_proposal.title:
|
if old_proposal.title != new_proposal.title:
|
||||||
proposal_changes.append({"type": ProposalChange.PROPOSAL_EDIT_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
|
return proposal_changes + milestone_changes
|
||||||
|
|
||||||
|
|
||||||
def default_proposal_content():
|
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 team’s background and experience. Demonstrate that you have the skills and expertise necessary for the project that you’re 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 [ZF’s mission](https://www.zfnd.org/about/#mission) and priorities? Whose needs will it serve?
|
# Solution Format
|
||||||
|
What is the exact form of the deliverable you’re 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
|
# Technical approach
|
||||||
|
|
||||||
Dive into the _how_ of your project. Describe your approaches, components, workflows, methodology, etc. Bullet points and diagrams are appreciated!
|
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
|
# 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.
|
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
|
# Evaluation plan
|
||||||
|
What metrics for success can you share with the community once you’re 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.
|
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 team’s background and experience. Demonstrate that you have the skills and expertise necessary for the project that you’re 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_approved = db.Column(db.DateTime)
|
||||||
date_published = db.Column(db.DateTime)
|
date_published = db.Column(db.DateTime)
|
||||||
reject_reason = db.Column(db.String())
|
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)
|
accepted_with_funding = db.Column(db.Boolean(), nullable=True)
|
||||||
changes_requested_discussion = 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)
|
changes_requested_discussion_reason = db.Column(db.String(255), nullable=True)
|
||||||
|
@ -420,21 +428,23 @@ class Proposal(db.Model):
|
||||||
)
|
)
|
||||||
followers_count = column_property(
|
followers_count = column_property(
|
||||||
select([func.count(proposal_follower.c.proposal_id)])
|
select([func.count(proposal_follower.c.proposal_id)])
|
||||||
.where(proposal_follower.c.proposal_id == id)
|
.where(proposal_follower.c.proposal_id == id)
|
||||||
.correlate_except(proposal_follower)
|
.correlate_except(proposal_follower)
|
||||||
)
|
)
|
||||||
likes = db.relationship(
|
likes = db.relationship(
|
||||||
"User", secondary=proposal_liker, back_populates="liked_proposals"
|
"User", secondary=proposal_liker, back_populates="liked_proposals"
|
||||||
)
|
)
|
||||||
likes_count = column_property(
|
likes_count = column_property(
|
||||||
select([func.count(proposal_liker.c.proposal_id)])
|
select([func.count(proposal_liker.c.proposal_id)])
|
||||||
.where(proposal_liker.c.proposal_id == id)
|
.where(proposal_liker.c.proposal_id == id)
|
||||||
.correlate_except(proposal_liker)
|
.correlate_except(proposal_liker)
|
||||||
)
|
)
|
||||||
live_draft_parent_id = db.Column(db.Integer, ForeignKey('proposal.id'))
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -460,6 +470,7 @@ class Proposal(db.Model):
|
||||||
self.deadline_duration = deadline_duration
|
self.deadline_duration = deadline_duration
|
||||||
self.stage = stage
|
self.stage = stage
|
||||||
self.version = '2'
|
self.version = '2'
|
||||||
|
self.funded_by_zomg = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def simple_validate(proposal):
|
def simple_validate(proposal):
|
||||||
|
@ -525,7 +536,7 @@ class Proposal(db.Model):
|
||||||
# Validate payout address
|
# Validate payout address
|
||||||
if not is_z_address_valid(self.payout_address):
|
if not is_z_address_valid(self.payout_address):
|
||||||
raise ValidationException("Payout address is not a valid z address")
|
raise ValidationException("Payout address is not a valid z address")
|
||||||
|
|
||||||
# Validate tip jar address
|
# Validate tip jar address
|
||||||
if self.tip_jar_address and not is_z_address_valid(self.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")
|
raise ValidationException("Tip address is not a valid z address")
|
||||||
|
@ -533,7 +544,6 @@ class Proposal(db.Model):
|
||||||
# Then run through regular validation
|
# Then run through regular validation
|
||||||
Proposal.simple_validate(vars(self))
|
Proposal.simple_validate(vars(self))
|
||||||
|
|
||||||
|
|
||||||
def validate_milestone_days(self):
|
def validate_milestone_days(self):
|
||||||
for milestone in self.milestones:
|
for milestone in self.milestones:
|
||||||
if milestone.immediate_payout:
|
if milestone.immediate_payout:
|
||||||
|
@ -610,11 +620,11 @@ class Proposal(db.Model):
|
||||||
self.rfp_opt_in = opt_in
|
self.rfp_opt_in = opt_in
|
||||||
|
|
||||||
def create_contribution(
|
def create_contribution(
|
||||||
self,
|
self,
|
||||||
amount,
|
amount,
|
||||||
user_id: int = None,
|
user_id: int = None,
|
||||||
staking: bool = False,
|
staking: bool = False,
|
||||||
private: bool = True,
|
private: bool = True,
|
||||||
):
|
):
|
||||||
contribution = ProposalContribution(
|
contribution = ProposalContribution(
|
||||||
proposal_id=self.id,
|
proposal_id=self.id,
|
||||||
|
@ -921,8 +931,8 @@ class Proposal(db.Model):
|
||||||
return False
|
return False
|
||||||
res = (
|
res = (
|
||||||
db.session.query(proposal_follower)
|
db.session.query(proposal_follower)
|
||||||
.filter_by(user_id=authed.id, proposal_id=self.id)
|
.filter_by(user_id=authed.id, proposal_id=self.id)
|
||||||
.count()
|
.count()
|
||||||
)
|
)
|
||||||
if res:
|
if res:
|
||||||
return True
|
return True
|
||||||
|
@ -937,8 +947,8 @@ class Proposal(db.Model):
|
||||||
return False
|
return False
|
||||||
res = (
|
res = (
|
||||||
db.session.query(proposal_liker)
|
db.session.query(proposal_liker)
|
||||||
.filter_by(user_id=authed.id, proposal_id=self.id)
|
.filter_by(user_id=authed.id, proposal_id=self.id)
|
||||||
.count()
|
.count()
|
||||||
)
|
)
|
||||||
if res:
|
if res:
|
||||||
return True
|
return True
|
||||||
|
@ -1096,7 +1106,9 @@ class ProposalSchema(ma.Schema):
|
||||||
"tip_jar_view_key",
|
"tip_jar_view_key",
|
||||||
"changes_requested_discussion",
|
"changes_requested_discussion",
|
||||||
"changes_requested_discussion_reason",
|
"changes_requested_discussion_reason",
|
||||||
"live_draft_id"
|
"live_draft_id",
|
||||||
|
"kyc_approved",
|
||||||
|
"funded_by_zomg"
|
||||||
)
|
)
|
||||||
|
|
||||||
date_created = ma.Method("get_date_created")
|
date_created = ma.Method("get_date_created")
|
||||||
|
@ -1106,6 +1118,7 @@ class ProposalSchema(ma.Schema):
|
||||||
is_version_two = ma.Method("get_is_version_two")
|
is_version_two = ma.Method("get_is_version_two")
|
||||||
tip_jar_view_key = ma.Method("get_tip_jar_view_key")
|
tip_jar_view_key = ma.Method("get_tip_jar_view_key")
|
||||||
live_draft_id = ma.Method("get_live_draft_id")
|
live_draft_id = ma.Method("get_live_draft_id")
|
||||||
|
funded_by_zomg = ma.Method("get_funded_by_zomg")
|
||||||
|
|
||||||
updates = ma.Nested("ProposalUpdateSchema", many=True)
|
updates = ma.Nested("ProposalUpdateSchema", many=True)
|
||||||
team = ma.Nested("UserSchema", many=True)
|
team = ma.Nested("UserSchema", many=True)
|
||||||
|
@ -1115,6 +1128,14 @@ class ProposalSchema(ma.Schema):
|
||||||
rfp = ma.Nested("RFPSchema", exclude=["accepted_proposals"])
|
rfp = ma.Nested("RFPSchema", exclude=["accepted_proposals"])
|
||||||
arbiter = ma.Nested("ProposalArbiterSchema", exclude=["proposal"])
|
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):
|
def get_proposal_id(self, obj):
|
||||||
return obj.id
|
return obj.id
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<p style="margin: 0;">
|
<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>
|
</p>
|
||||||
|
|
||||||
{% if args.admin_note %}
|
{% if args.admin_note %}
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="padding: 40px 10px 40px 10px;" valign="top">
|
<td align="center" style="padding: 40px 10px 40px 10px;" valign="top">
|
||||||
<a href="{{ args.home_url }}" target="_blank">
|
<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;"
|
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">
|
width="220">
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -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 ###
|
|
@ -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 ###
|
|
@ -37,6 +37,7 @@ const Tos = loadable(() => import('pages/tos'));
|
||||||
const ProposalTutorial = loadable(() => import('pages/proposal-tutorial'));
|
const ProposalTutorial = loadable(() => import('pages/proposal-tutorial'));
|
||||||
const About = loadable(() => import('pages/about'), opts);
|
const About = loadable(() => import('pages/about'), opts);
|
||||||
const Privacy = loadable(() => import('pages/privacy'), opts);
|
const Privacy = loadable(() => import('pages/privacy'), opts);
|
||||||
|
const Kyc = loadable(() => import('pages/kyc'), opts);
|
||||||
const Contact = loadable(() => import('pages/contact'), opts);
|
const Contact = loadable(() => import('pages/contact'), opts);
|
||||||
const CodeOfConduct = loadable(() => import('pages/code-of-conduct'), opts);
|
const CodeOfConduct = loadable(() => import('pages/code-of-conduct'), opts);
|
||||||
const VerifyEmail = loadable(() => import('pages/email-verify'), opts);
|
const VerifyEmail = loadable(() => import('pages/email-verify'), opts);
|
||||||
|
@ -254,6 +255,18 @@ const routeConfigs: RouteConfig[] = [
|
||||||
},
|
},
|
||||||
onlyLoggedIn: false,
|
onlyLoggedIn: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Privacy page
|
||||||
|
route: {
|
||||||
|
path: '/kyc',
|
||||||
|
component: Kyc,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
title: 'KYC',
|
||||||
|
},
|
||||||
|
onlyLoggedIn: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Privacy page
|
// Privacy page
|
||||||
route: {
|
route: {
|
||||||
|
|
|
@ -86,11 +86,11 @@ export const STAGE_UI: { [key in PROPOSAL_FILTERS]: StageUI } = {
|
||||||
color: '#8e44ad',
|
color: '#8e44ad',
|
||||||
},
|
},
|
||||||
ACCEPTED_WITH_FUNDING: {
|
ACCEPTED_WITH_FUNDING: {
|
||||||
label: 'Funded by ZF',
|
label: 'Funded',
|
||||||
color: '#8e44ad',
|
color: '#8e44ad',
|
||||||
},
|
},
|
||||||
ACCEPTED_WITHOUT_FUNDING: {
|
ACCEPTED_WITHOUT_FUNDING: {
|
||||||
label: 'Not Funded by ZF',
|
label: 'Not Funded',
|
||||||
color: '#8e44ad',
|
color: '#8e44ad',
|
||||||
},
|
},
|
||||||
WIP: {
|
WIP: {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,16 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
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 { RadioChangeEvent } from 'antd/lib/radio';
|
||||||
import { ProposalDraft, RFP } from 'types';
|
import { ProposalDraft, RFP } from 'types';
|
||||||
import { getCreateErrors } from 'modules/create/utils';
|
import { getCreateErrors } from 'modules/create/utils';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { unlinkProposalRFP } from 'modules/create/actions';
|
import { unlinkProposalRFP } from 'modules/create/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import './Basics.less';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
proposalId: number;
|
proposalId: number;
|
||||||
initialState?: Partial<State>;
|
initialState?: Partial<State>;
|
||||||
|
|
||||||
updateForm(form: Partial<ProposalDraft>): void;
|
updateForm(form: Partial<ProposalDraft>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +32,7 @@ interface State extends Partial<ProposalDraft> {
|
||||||
brief: string;
|
brief: string;
|
||||||
target: string;
|
target: string;
|
||||||
rfp?: RFP;
|
rfp?: RFP;
|
||||||
|
visible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateFlowBasics extends React.Component<Props, State> {
|
class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
|
@ -39,10 +42,29 @@ class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
title: '',
|
title: '',
|
||||||
brief: '',
|
brief: '',
|
||||||
target: '',
|
target: '',
|
||||||
|
visible: false,
|
||||||
...(props.initialState || {}),
|
...(props.initialState || {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOk = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
componentDidUpdate(prevProps: Props) {
|
||||||
const { unlinkProposalRFPError, isUnlinkingProposalRFP } = this.props;
|
const { unlinkProposalRFPError, isUnlinkingProposalRFP } = this.props;
|
||||||
if (
|
if (
|
||||||
|
@ -72,21 +94,21 @@ class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form layout="vertical" style={{ maxWidth: 600, margin: '0 auto' }}>
|
<Form layout='vertical' style={{ maxWidth: 600, margin: '0 auto' }}>
|
||||||
{rfp && (
|
{rfp && (
|
||||||
<Alert
|
<Alert
|
||||||
className="CreateFlow-rfpAlert"
|
className='CreateFlow-rfpAlert'
|
||||||
type="info"
|
type='info'
|
||||||
message="This proposal is linked to a request"
|
message='This proposal is linked to a request'
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
This proposal is for the open request{' '}
|
This proposal is for the open request{' '}
|
||||||
<Link to={`/requests/${rfp.id}`} target="_blank">
|
<Link to={`/requests/${rfp.id}`} target='_blank'>
|
||||||
{rfp.title}
|
{rfp.title}
|
||||||
</Link>
|
</Link>
|
||||||
. If you didn’t mean to do this, or want to unlink it,{' '}
|
. If you didn’t mean to do this, or want to unlink it,{' '}
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="Are you sure? This cannot be undone."
|
title='Are you sure? This cannot be undone.'
|
||||||
onConfirm={this.unlinkRfp}
|
onConfirm={this.unlinkRfp}
|
||||||
okButtonProps={{ loading: isUnlinkingProposalRFP }}
|
okButtonProps={{ loading: isUnlinkingProposalRFP }}
|
||||||
>
|
>
|
||||||
|
@ -100,9 +122,9 @@ class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Alert
|
<Alert
|
||||||
className="CreateFlow-rfpAlert"
|
className='CreateFlow-rfpAlert'
|
||||||
type="warning"
|
type='warning'
|
||||||
message="KYC (know your customer)"
|
message='KYC (know your customer)'
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
|
@ -110,12 +132,12 @@ class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
provide identifying information to the Zcash Foundation.
|
provide identifying information to the Zcash Foundation.
|
||||||
<Radio.Group onChange={this.handleRfpOptIn}>
|
<Radio.Group onChange={this.handleRfpOptIn}>
|
||||||
<Radio value={true} checked={rfpOptIn && rfpOptIn === true}>
|
<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>
|
||||||
<Radio value={false} checked={rfpOptIn !== null && rfpOptIn === false}>
|
<Radio value={false} checked={rfpOptIn !== null && rfpOptIn === false}>
|
||||||
<b>No</b>, I do not wish to provide KYC information and understand my
|
<b>No</b>, I do not wish to provide KYC information and understand I will not be able to submit my
|
||||||
proposal may still be posted on ZF Grants, but I will not be eligible
|
proposal.
|
||||||
to funding from the Zcash Foundation.
|
|
||||||
</Radio>
|
</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</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
|
<Form.Item
|
||||||
label="Title"
|
label='Title'
|
||||||
validateStatus={errors.title ? 'error' : undefined}
|
validateStatus={errors.title ? 'error' : undefined}
|
||||||
help={errors.title}
|
help={errors.title}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
size="large"
|
size='large'
|
||||||
name="title"
|
name='title'
|
||||||
placeholder="Short and sweet"
|
placeholder='Short and sweet'
|
||||||
type="text"
|
type='text'
|
||||||
value={title}
|
value={title}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
maxLength={200}
|
maxLength={200}
|
||||||
|
@ -140,13 +228,13 @@ class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Brief"
|
label='Brief'
|
||||||
validateStatus={errors.brief ? 'error' : undefined}
|
validateStatus={errors.brief ? 'error' : undefined}
|
||||||
help={errors.brief}
|
help={errors.brief}
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
name="brief"
|
name='brief'
|
||||||
placeholder="A one-liner elevator-pitch version of your proposal, max 140 chars."
|
placeholder='A one-liner elevator-pitch version of your proposal, max 140 chars.'
|
||||||
value={brief}
|
value={brief}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
rows={3}
|
rows={3}
|
||||||
|
@ -155,7 +243,7 @@ class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Target amount"
|
label='Target amount'
|
||||||
validateStatus={errors.target ? 'error' : undefined}
|
validateStatus={errors.target ? 'error' : undefined}
|
||||||
help={
|
help={
|
||||||
errors.target ||
|
errors.target ||
|
||||||
|
@ -163,13 +251,13 @@ class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
size="large"
|
size='large'
|
||||||
name="target"
|
name='target'
|
||||||
placeholder="1.5"
|
placeholder='1.5'
|
||||||
type="number"
|
type='number'
|
||||||
value={target}
|
value={target}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
addonBefore="$"
|
addonBefore='$'
|
||||||
maxLength={16}
|
maxLength={16}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
|
@ -80,17 +80,17 @@ class HeaderDrawer extends React.Component<Props> {
|
||||||
<Menu.Item key="/proposals">
|
<Menu.Item key="/proposals">
|
||||||
<Link to="/proposals">Browse proposals</Link>
|
<Link to="/proposals">Browse proposals</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{/*<Menu.Item key="/create">*/}
|
<Menu.Item key="/create">
|
||||||
{/* <Link to="/create">Start a proposal</Link>*/}
|
<Link to="/create">Start a proposal</Link>
|
||||||
{/*</Menu.Item>*/}
|
</Menu.Item>
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
<Menu.ItemGroup title="Requests">
|
<Menu.ItemGroup title="Requests">
|
||||||
<Menu.Item key="/requests">
|
<Menu.Item key="/requests">
|
||||||
<Link to="/requests">Browse requests</Link>
|
<Link to="/requests">Browse requests</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="/create-request">
|
{/*<Menu.Item key="/create-request">*/}
|
||||||
<Link to="/create-request">Create a Request</Link>
|
{/* <Link to="/create-request">Create a Request</Link>*/}
|
||||||
</Menu.Item>
|
{/*</Menu.Item>*/}
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -105,15 +105,6 @@ class Header extends React.Component<Props, State> {
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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/>
|
<HeaderAuth/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,11 +63,11 @@
|
||||||
&-logo {
|
&-logo {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
width: auto;
|
width: auto;
|
||||||
transform: translateY(-7%);
|
transform: scale(1.6) translateY(-7%);
|
||||||
transition: transform @header-transition ease;
|
transition: transform @header-transition ease;
|
||||||
|
|
||||||
.is-transparent & {
|
.is-transparent & {
|
||||||
transform: scale(1.4) translateY(18%);
|
transform: scale(2) translateY(18%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@ const HomeIntro: React.SFC<Props> = ({ t, authUser }) => (
|
||||||
{t('home.intro.signup')}
|
{t('home.intro.signup')}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<Link className="HomeIntro-content-buttons-button" to="/create-request">
|
<Link className="HomeIntro-content-buttons-button" to="/create">
|
||||||
{t('home.intro.ccr')}
|
Create a Proposal
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { UserProposal, STATUS } from 'types';
|
import { STATUS, UserProposal } from 'types';
|
||||||
import './ProfileProposal.less';
|
import './ProfileProposal.less';
|
||||||
import UserRow from 'components/UserRow';
|
import UserRow from 'components/UserRow';
|
||||||
import UnitDisplay from 'components/UnitDisplay';
|
import UnitDisplay from 'components/UnitDisplay';
|
||||||
|
@ -23,7 +23,8 @@ export default class Profile extends React.Component<OwnProps> {
|
||||||
isVersionTwo,
|
isVersionTwo,
|
||||||
acceptedWithFunding,
|
acceptedWithFunding,
|
||||||
status,
|
status,
|
||||||
changesRequestedDiscussionReason
|
changesRequestedDiscussionReason,
|
||||||
|
fundedByZomg,
|
||||||
} = this.props.proposal;
|
} = this.props.proposal;
|
||||||
|
|
||||||
// pulled from `variables.less`
|
// pulled from `variables.less`
|
||||||
|
@ -31,18 +32,24 @@ export default class Profile extends React.Component<OwnProps> {
|
||||||
const secondaryColor = '#2D2A26';
|
const secondaryColor = '#2D2A26';
|
||||||
|
|
||||||
const isOpenForDiscussion = status === STATUS.DISCUSSION;
|
const isOpenForDiscussion = status === STATUS.DISCUSSION;
|
||||||
const discussionColor = changesRequestedDiscussionReason ? 'red' : infoColor
|
const discussionColor = changesRequestedDiscussionReason ? 'red' : infoColor;
|
||||||
const discussionTag = changesRequestedDiscussionReason ? 'Changes Requested' : 'Open for Public Review'
|
const discussionTag = changesRequestedDiscussionReason
|
||||||
|
? 'Changes Requested'
|
||||||
|
: 'Open for Public Review';
|
||||||
|
|
||||||
let tagColor = infoColor
|
let tagColor = infoColor;
|
||||||
let tagMessage = 'Open for Contributions'
|
let tagMessage = 'Open for Contributions';
|
||||||
|
|
||||||
if (acceptedWithFunding) {
|
if (acceptedWithFunding) {
|
||||||
tagColor = secondaryColor
|
tagColor = secondaryColor;
|
||||||
tagMessage = 'Funded by ZF'
|
if (!fundedByZomg) {
|
||||||
|
tagMessage = 'Funded by ZF';
|
||||||
|
} else {
|
||||||
|
tagMessage = 'Funded by ZOMG';
|
||||||
|
}
|
||||||
} else if (isOpenForDiscussion) {
|
} else if (isOpenForDiscussion) {
|
||||||
tagColor = discussionColor
|
tagColor = discussionColor;
|
||||||
tagMessage = discussionTag
|
tagMessage = discussionTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Icon, Popover, Tooltip, Alert } from 'antd';
|
import { Alert, Icon, Popover, Tooltip } from 'antd';
|
||||||
import { Proposal, STATUS } from 'types';
|
import { Proposal, STATUS } from 'types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -12,6 +12,8 @@ import Loader from 'components/Loader';
|
||||||
import { PROPOSAL_STAGE } from 'api/constants';
|
import { PROPOSAL_STAGE } from 'api/constants';
|
||||||
import { formatUsd } from 'utils/formatters';
|
import { formatUsd } from 'utils/formatters';
|
||||||
import ZFGrantsLogo from 'static/images/logo-name-light.svg';
|
import ZFGrantsLogo from 'static/images/logo-name-light.svg';
|
||||||
|
import ZomgLogo from 'static/images/zomg-logo.png';
|
||||||
|
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
|
@ -134,7 +136,11 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
||||||
isAcceptedWithFunding && (
|
isAcceptedWithFunding && (
|
||||||
<div className="ProposalCampaignBlock-with-funding">
|
<div className="ProposalCampaignBlock-with-funding">
|
||||||
Funded through
|
Funded through
|
||||||
<ZFGrantsLogo style={{ height: '1.5rem' }} />
|
{proposal.fundedByZomg ? (
|
||||||
|
<img src={ZomgLogo} alt={'Zomg logo'} style={{ height: '1.5rem' }} />
|
||||||
|
) : (
|
||||||
|
<ZFGrantsLogo style={{ height: '1.5rem' }} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ export class ProposalCard extends React.Component<Proposal> {
|
||||||
percentFunded,
|
percentFunded,
|
||||||
acceptedWithFunding,
|
acceptedWithFunding,
|
||||||
status,
|
status,
|
||||||
|
fundedByZomg,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// pulled from `variables.less`
|
// pulled from `variables.less`
|
||||||
|
@ -46,7 +47,11 @@ export class ProposalCard extends React.Component<Proposal> {
|
||||||
if (isVersionTwo && status === STATUS.LIVE) {
|
if (isVersionTwo && status === STATUS.LIVE) {
|
||||||
if (acceptedWithFunding) {
|
if (acceptedWithFunding) {
|
||||||
tagColor = secondaryColor;
|
tagColor = secondaryColor;
|
||||||
tagMessage = 'Funded by ZF';
|
if (!fundedByZomg) {
|
||||||
|
tagMessage = 'Funded by ZF';
|
||||||
|
} else {
|
||||||
|
tagMessage = 'Funded by ZOMG';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tagColor = infoColor;
|
tagColor = infoColor;
|
||||||
tagMessage = 'Not Funded';
|
tagMessage = 'Not Funded';
|
||||||
|
|
|
@ -83,8 +83,8 @@ export function getCreateErrors(
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFP opt-in
|
// RFP opt-in
|
||||||
if (rfpOptIn === null) {
|
if (!rfpOptIn) {
|
||||||
errors.rfpOptIn = 'Please accept or decline KYC';
|
errors.rfpOptIn = 'Please accept KYC to submit.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
|
@ -260,6 +260,7 @@ export function makeProposalPreviewFromDraft(draft: ProposalDraft): ProposalDeta
|
||||||
authedLiked: false,
|
authedLiked: false,
|
||||||
likesCount: 0,
|
likesCount: 0,
|
||||||
isVersionTwo: true,
|
isVersionTwo: true,
|
||||||
|
fundedByZomg: false,
|
||||||
milestones: draft.milestones.map((m, idx) => ({
|
milestones: draft.milestones.map((m, idx) => ({
|
||||||
id: idx,
|
id: idx,
|
||||||
index: idx,
|
index: idx,
|
||||||
|
|
|
@ -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;
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 56 KiB |
|
@ -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 |
|
@ -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, driver’s 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).
|
|
@ -43,7 +43,6 @@
|
||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
font-size: 1.05rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
|
|
@ -190,6 +190,7 @@
|
||||||
"@types/storybook__react": "^3.0.9",
|
"@types/storybook__react": "^3.0.9",
|
||||||
"rimraf": "2.6.2",
|
"rimraf": "2.6.2",
|
||||||
"string-hash": "1.1.3",
|
"string-hash": "1.1.3",
|
||||||
"webapp-webpack-plugin": "2.3.1"
|
"webapp-webpack-plugin": "2.3.1",
|
||||||
|
"https-proxy-agent": "^2.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,7 @@ export function generateProposal({
|
||||||
liveDraftId: null,
|
liveDraftId: null,
|
||||||
tipJarAddress: null,
|
tipJarAddress: null,
|
||||||
tipJarViewKey: null,
|
tipJarViewKey: null,
|
||||||
|
fundedByZomg: false,
|
||||||
arbiter: {
|
arbiter: {
|
||||||
status: PROPOSAL_ARBITER_STATUS.ACCEPTED,
|
status: PROPOSAL_ARBITER_STATUS.ACCEPTED,
|
||||||
user: {
|
user: {
|
||||||
|
|
|
@ -81,6 +81,7 @@ export interface Proposal extends Omit<ProposalDraft, 'target' | 'invites'> {
|
||||||
liveDraftId: string | null;
|
liveDraftId: string | null;
|
||||||
isTeamMember?: boolean; // FE derived
|
isTeamMember?: boolean; // FE derived
|
||||||
isArbiter?: boolean; // FE derived
|
isArbiter?: boolean; // FE derived
|
||||||
|
fundedByZomg: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TeamInviteWithProposal extends TeamInvite {
|
export interface TeamInviteWithProposal extends TeamInvite {
|
||||||
|
@ -124,6 +125,7 @@ export interface UserProposal {
|
||||||
changesRequestedDiscussionReason: string | null;
|
changesRequestedDiscussionReason: string | null;
|
||||||
acceptedWithFunding: boolean | null;
|
acceptedWithFunding: boolean | null;
|
||||||
isVersionTwo: boolean;
|
isVersionTwo: boolean;
|
||||||
|
fundedByZomg: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: sync with backend/grant/proposal/models.py STATUSES
|
// NOTE: sync with backend/grant/proposal/models.py STATUSES
|
||||||
|
|
|
@ -2438,6 +2438,13 @@ agent-base@^4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promisify "^5.0.0"
|
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":
|
"airbnb-js-shims@^1 || ^2":
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.1.1.tgz#a509611480db7e6d9db62fe2acfaeb473b6842ac"
|
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"
|
agent-base "^4.1.0"
|
||||||
debug "^3.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:
|
https-proxy@0.0.2:
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy/-/https-proxy-0.0.2.tgz#9e7d542f1ce8d37c06e1f940a8a9a227bb48ddf0"
|
resolved "https://registry.yarnpkg.com/https-proxy/-/https-proxy-0.0.2.tgz#9e7d542f1ce8d37c06e1f940a8a9a227bb48ddf0"
|
||||||
|
|