setup 'FUNDING BY ZOMG'
This commit is contained in:
parent
1a38eea631
commit
7f065b4163
|
@ -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,27 +2,11 @@ import React from 'react';
|
|||
import BN from 'bn.js';
|
||||
import { view } from 'react-easy-state';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Collapse,
|
||||
Input,
|
||||
message,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Tag,
|
||||
} 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 {
|
||||
MILESTONE_STAGE,
|
||||
PROPOSAL_ARBITER_STATUS,
|
||||
PROPOSAL_STAGE,
|
||||
PROPOSAL_STATUS,
|
||||
} 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';
|
||||
|
@ -30,6 +14,7 @@ import ArbiterControl from 'components/ArbiterControl';
|
|||
import { fromZat, toZat } from 'src/util/units';
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
import { formatUsd } from 'util/formatters';
|
||||
|
||||
import './index.less';
|
||||
|
||||
type Props = RouteComponentProps<any>;
|
||||
|
@ -58,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 &&
|
||||
|
@ -94,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,
|
||||
|
@ -105,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}
|
||||
|
@ -128,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,
|
||||
|
@ -139,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
|
||||
|
@ -170,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
|
||||
|
@ -206,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?',
|
||||
|
@ -237,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?',
|
||||
|
@ -269,27 +256,27 @@ 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"
|
||||
className='ProposalDetail-review'
|
||||
loading={store.proposalDetailAcceptingProposal}
|
||||
icon="check"
|
||||
type="primary"
|
||||
icon='check'
|
||||
type='primary'
|
||||
onClick={() => this.handleAcceptProposal(true, true)}
|
||||
>
|
||||
Approve With Funding
|
||||
</Button>
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
className='ProposalDetail-review'
|
||||
loading={store.proposalDetailAcceptingProposal}
|
||||
icon="check"
|
||||
type="default"
|
||||
icon='check'
|
||||
type='default'
|
||||
onClick={() => this.handleAcceptProposal(true, false)}
|
||||
>
|
||||
Approve Without Funding
|
||||
|
@ -297,10 +284,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
</>
|
||||
|
||||
<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?',
|
||||
|
@ -325,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>
|
||||
|
@ -346,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>
|
||||
|
@ -360,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
|
||||
|
@ -381,8 +368,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
{!p.kycApproved ? (
|
||||
<Alert
|
||||
showIcon
|
||||
type="error"
|
||||
message="KYC approval required"
|
||||
type='error'
|
||||
message='KYC approval required'
|
||||
description={
|
||||
<div>
|
||||
<p>
|
||||
|
@ -390,10 +377,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
with payouts.
|
||||
</p>
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
className='ProposalDetail-review'
|
||||
loading={store.proposalDetailApprovingKyc}
|
||||
icon="check"
|
||||
type="primary"
|
||||
icon='check'
|
||||
type='primary'
|
||||
onClick={() => this.handleApproveKYC()}
|
||||
>
|
||||
KYC Approved
|
||||
|
@ -404,8 +391,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
) : (
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message="No arbiter on live proposal"
|
||||
type='warning'
|
||||
message='No arbiter on live proposal'
|
||||
description={
|
||||
<div>
|
||||
<p>An arbiter is required to review milestone payout requests.</p>
|
||||
|
@ -422,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>
|
||||
|
@ -469,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>
|
||||
|
@ -487,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}
|
||||
/>
|
||||
|
@ -503,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'
|
||||
}
|
||||
|
@ -525,17 +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}
|
||||
</div>
|
||||
);
|
||||
|
||||
console.log(p);
|
||||
|
||||
// @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 */}
|
||||
|
@ -550,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>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
@ -590,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>
|
||||
|
@ -599,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(
|
||||
|
@ -630,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)}
|
||||
|
@ -662,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>
|
||||
|
@ -783,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));
|
||||
|
|
|
@ -142,6 +142,11 @@ 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;
|
||||
|
@ -351,6 +356,7 @@ const app = store({
|
|||
proposalDetailMarkingChangesAsResolved: false,
|
||||
proposalDetailAcceptingProposal: false,
|
||||
proposalDetailApprovingKyc: false,
|
||||
proposalDetailSwitchingFunder: false,
|
||||
proposalDetailMarkingMilestonePaid: false,
|
||||
proposalDetailCanceling: false,
|
||||
proposalDetailUpdating: false,
|
||||
|
@ -695,6 +701,24 @@ const app = store({
|
|||
}
|
||||
},
|
||||
|
||||
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!';
|
||||
|
|
|
@ -124,6 +124,7 @@ export interface Proposal {
|
|||
changesRequestedDiscussion: boolean | null;
|
||||
changesRequestedDiscussionReason: string | null;
|
||||
kycApproved: null | boolean;
|
||||
fundedByZomg: boolean;
|
||||
}
|
||||
export interface Comment {
|
||||
id: number;
|
||||
|
|
|
@ -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,
|
||||
|
@ -390,6 +390,22 @@ def approve_proposal_kyc(id):
|
|||
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),
|
||||
|
|
|
@ -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,7 +331,8 @@ 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
|
||||
|
||||
|
@ -392,6 +392,7 @@ class Proposal(db.Model):
|
|||
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)
|
||||
|
@ -422,21 +423,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,
|
||||
|
@ -527,7 +530,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")
|
||||
|
@ -535,7 +538,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:
|
||||
|
@ -612,11 +614,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,
|
||||
|
@ -923,8 +925,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
|
||||
|
@ -939,8 +941,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
|
||||
|
@ -1099,7 +1101,8 @@ class ProposalSchema(ma.Schema):
|
|||
"changes_requested_discussion",
|
||||
"changes_requested_discussion_reason",
|
||||
"live_draft_id",
|
||||
"kyc_approved"
|
||||
"kyc_approved",
|
||||
"funded_by_zomg"
|
||||
)
|
||||
|
||||
date_created = ma.Method("get_date_created")
|
||||
|
@ -1109,6 +1112,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)
|
||||
|
@ -1118,6 +1122,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
|
||||
|
||||
|
|
|
@ -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 ###
|
|
@ -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: {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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
|
||||
<ZFGrantsLogo style={{ height: '1.5rem' }} />
|
||||
{proposal.fundedByZomg ? (
|
||||
<img src={ZomgLogo} alt={'Zomg logo'} style={{ height: '1.5rem' }} />
|
||||
) : (
|
||||
<ZFGrantsLogo style={{ height: '1.5rem' }} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
Binary file not shown.
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 |
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue