setup 'FUNDING BY ZOMG'

This commit is contained in:
Daniel Ternyak 2021-02-01 19:32:12 -06:00
parent 1a38eea631
commit 7f065b4163
No known key found for this signature in database
GPG Key ID: DF212D2DC5D0E245
15 changed files with 372 additions and 142 deletions

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

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

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

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

View File

@ -2,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} &nbsp;
</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));

View File

@ -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!';

View File

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

View File

@ -4,7 +4,7 @@ from functools import reduce
from flask import Blueprint, request
from marshmallow import fields, validate
from sqlalchemy import func, or_, text
from sqlalchemy import func, text
import grant.utils.admin as admin
import grant.utils.auth as auth
@ -25,7 +25,7 @@ from grant.proposal.models import (
admin_proposal_contributions_schema,
)
from grant.rfp.models import RFP, admin_rfp_schema, admin_rfps_schema
from grant.user.models import User, UserSettings, admin_users_schema, admin_user_schema
from grant.user.models import User, admin_users_schema, admin_user_schema
from grant.utils import pagination
from grant.utils.enums import (
ProposalStatus,
@ -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),

View File

@ -1,8 +1,8 @@
import datetime
import json
from typing import Optional
from decimal import Decimal, ROUND_DOWN
from functools import reduce
from typing import Optional
from marshmallow import post_dump
from sqlalchemy import func, or_, select, ForeignKey
@ -10,15 +10,14 @@ from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import column_property
from grant.comment.models import Comment
from grant.milestone.models import Milestone
from grant.email.send import send_email
from grant.extensions import ma, db
from grant.milestone.models import Milestone
from grant.settings import PROPOSAL_STAKING_AMOUNT, PROPOSAL_TARGET_MAX
from grant.task.jobs import ContributionExpired
from grant.utils.enums import (
ProposalStatus,
ProposalStage,
Category,
ContributionStatus,
ProposalArbiterStatus,
MilestoneStage,
@ -332,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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

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

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -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 {