@@ -340,8 +333,8 @@ class ProposalDetailNaked extends React.Component {
p.changesRequestedDiscussion && (
@@ -354,10 +347,10 @@ class ProposalDetailNaked extends React.Component {
- }
- />
+ <>
+ {!p.kycApproved ? (
+
+
+ Please wait until an Admin has marked KYC approved before proceeding
+ with payouts.
+
+
+
+ }
+ />
+ ) : (
+
+ An arbiter is required to review milestone payout requests.
+
+
+ }
+ />
+ )}
+ >
);
const renderNominatedArbiter = () =>
@@ -389,8 +409,8 @@ class ProposalDetailNaked extends React.Component {
p.status === PROPOSAL_STATUS.LIVE && (
@@ -436,9 +456,9 @@ class ProposalDetailNaked extends React.Component {
return (
@@ -454,9 +474,9 @@ class ProposalDetailNaked extends React.Component {
{' '}
{p.payoutAddress}
this.setState({ paidTxId: e.target.value })}
onSearch={this.handlePaidMilestone}
/>
@@ -470,7 +490,7 @@ class ProposalDetailNaked extends React.Component {
p.isFailed && (
{
);
const renderDeetItem = (name: string, val: any) => (
-
+
{name}
{val}
);
+ // @ts-ignore
return (
-
-
+
+
{p.title}
{/* MAIN */}
@@ -515,22 +536,22 @@ class ProposalDetailNaked extends React.Component {
{renderMilestoneAccepted()}
{renderFailed()}
-
+
{p.brief}
-
+
-
+
{p.milestones.map((milestone, i) => (
{milestone.title + ' '}
{milestone.immediatePayout && (
- Immediate Payout
+ Immediate Payout
)}
>
}
@@ -555,7 +576,7 @@ class ProposalDetailNaked extends React.Component {
))}
-
+
{JSON.stringify(p, null, 4)}
@@ -564,26 +585,38 @@ class ProposalDetailNaked extends React.Component {
{/* RIGHT SIDE */}
{p.isVersionTwo &&
- !p.acceptedWithFunding &&
- p.stage === PROPOSAL_STAGE.WIP && (
-
- )}
+ !p.acceptedWithFunding &&
+ p.stage === PROPOSAL_STAGE.WIP && (
+
+ )}
{/* ACTIONS */}
-
+
{renderCancelControl()}
{renderArbiterControl()}
+
+ {
+ p.acceptedWithFunding &&
+
+
+
+ }
+
{shouldShowChangeToAcceptedWithFunding &&
- renderChangeToAcceptedWithFundingControl()}
+ renderChangeToAcceptedWithFundingControl()}
{/* DETAILS */}
-
+
{renderDeetItem('id', p.proposalId)}
{renderDeetItem('created', formatDateSeconds(p.dateCreated))}
{renderDeetItem(
@@ -595,10 +628,10 @@ class ProposalDetailNaked extends React.Component {
formatDurationSeconds(p.deadlineDuration),
)}
{p.datePublished &&
- renderDeetItem(
- '(deadline)',
- formatDateSeconds(p.datePublished + p.deadlineDuration),
- )}
+ renderDeetItem(
+ '(deadline)',
+ formatDateSeconds(p.datePublished + p.deadlineDuration),
+ )}
{renderDeetItem('isFailed', JSON.stringify(p.isFailed))}
{renderDeetItem('status', p.status)}
{renderDeetItem('stage', p.stage)}
@@ -627,14 +660,14 @@ class ProposalDetailNaked extends React.Component {
>,
)}
{p.rfp &&
- renderDeetItem(
- 'rfp',
- {p.rfp.title},
- )}
+ renderDeetItem(
+ 'rfp',
+ {p.rfp.title},
+ )}
{/* TEAM */}
-
+
{p.team.map(t => (
{t.displayName}
@@ -716,6 +749,11 @@ class ProposalDetailNaked extends React.Component
{
message.info('Proposal rejected permanently');
};
+ private handleApproveKYC = async () => {
+ await store.approveProposalKYC();
+ message.info(`Proposal KYC approved`);
+ };
+
private handleAcceptProposal = async (
isAccepted: boolean,
withFunding: boolean,
@@ -743,6 +781,10 @@ class ProposalDetailNaked extends React.Component {
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));
diff --git a/admin/src/store.ts b/admin/src/store.ts
index 6ce3ec5d..489e3ae2 100644
--- a/admin/src/store.ts
+++ b/admin/src/store.ts
@@ -2,17 +2,17 @@ import { pick } from 'lodash';
import { store } from 'react-easy-state';
import axios, { AxiosError } from 'axios';
import {
- User,
- Proposal,
CCR,
+ CommentArgs,
Contribution,
ContributionArgs,
+ EmailExample,
+ PageData,
+ PageQuery,
+ Proposal,
RFP,
RFPArgs,
- EmailExample,
- PageQuery,
- PageData,
- CommentArgs,
+ User,
} from './types';
// API
@@ -142,6 +142,16 @@ async function approveDiscussion(
return data;
}
+async function switchProposalFunder(id: number, fundedByZomg: boolean) {
+ const { data } = await api.put(`/admin/proposals/${id}/adjust-funder`, {fundedByZomg});
+ return data;
+}
+
+async function approveProposalKYC(id: number) {
+ const { data } = await api.put(`/admin/proposals/${id}/approve-kyc`);
+ return data;
+}
+
async function acceptProposal(
id: number,
isAccepted: boolean,
@@ -345,6 +355,8 @@ const app = store({
proposalDetailApprovingDiscussion: false,
proposalDetailMarkingChangesAsResolved: false,
proposalDetailAcceptingProposal: false,
+ proposalDetailApprovingKyc: false,
+ proposalDetailSwitchingFunder: false,
proposalDetailMarkingMilestonePaid: false,
proposalDetailCanceling: false,
proposalDetailUpdating: false,
@@ -688,6 +700,43 @@ const app = store({
handleApiError(e);
}
},
+
+ async switchProposalFunder(fundedByZomg: boolean) {
+ if (!app.proposalDetail) {
+ const m = 'store.acceptProposal(): Expected proposalDetail to be populated!';
+ app.generalError.push(m);
+ console.error(m);
+ return;
+ }
+ app.proposalDetailSwitchingFunder = true;
+ try {
+ const { proposalId } = app.proposalDetail;
+ const res = await switchProposalFunder(proposalId, fundedByZomg);
+ app.updateProposalInStore(res);
+ } catch (e) {
+ handleApiError(e);
+ }
+ app.proposalDetailSwitchingFunder = false;
+ },
+
+ async approveProposalKYC() {
+ if (!app.proposalDetail) {
+ const m = 'store.acceptProposal(): Expected proposalDetail to be populated!';
+ app.generalError.push(m);
+ console.error(m);
+ return;
+ }
+ app.proposalDetailApprovingKyc = true;
+ try {
+ const { proposalId } = app.proposalDetail;
+ const res = await approveProposalKYC(proposalId);
+ app.updateProposalInStore(res);
+ } catch (e) {
+ handleApiError(e);
+ }
+ app.proposalDetailApprovingKyc = false;
+ },
+
async acceptProposal(
isAccepted: boolean,
withFunding: boolean,
@@ -975,6 +1024,7 @@ function createDefaultPageData(sort: string): PageData {
}
type FNFetchPage = (params: PageQuery) => Promise;
+
interface PageParent {
page: PageData;
}
diff --git a/admin/src/types.ts b/admin/src/types.ts
index 3e61b3ec..77669b33 100644
--- a/admin/src/types.ts
+++ b/admin/src/types.ts
@@ -123,6 +123,8 @@ export interface Proposal {
isVersionTwo: boolean;
changesRequestedDiscussion: boolean | null;
changesRequestedDiscussionReason: string | null;
+ kycApproved: null | boolean;
+ fundedByZomg: boolean;
}
export interface Comment {
id: number;
diff --git a/admin/yarn.lock b/admin/yarn.lock
index c32c5708..89cd5f1c 100644
--- a/admin/yarn.lock
+++ b/admin/yarn.lock
@@ -5414,7 +5414,7 @@ minimist@^1.2.0, minimist@~1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-minimist@^1.2.2:
+minimist@^1.2.3:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py
index b74b08fe..21545ec7 100644
--- a/backend/grant/admin/views.py
+++ b/backend/grant/admin/views.py
@@ -4,7 +4,7 @@ from functools import reduce
from flask import Blueprint, request
from marshmallow import fields, validate
-from sqlalchemy import func, or_, text
+from sqlalchemy import func, text
import grant.utils.admin as admin
import grant.utils.auth as auth
@@ -25,7 +25,7 @@ from grant.proposal.models import (
admin_proposal_contributions_schema,
)
from grant.rfp.models import RFP, admin_rfp_schema, admin_rfps_schema
-from grant.user.models import User, UserSettings, admin_users_schema, admin_user_schema
+from grant.user.models import User, admin_users_schema, admin_user_schema
from grant.utils import pagination
from grant.utils.enums import (
ProposalStatus,
@@ -377,6 +377,35 @@ def open_proposal_for_discussion(proposal_id, is_open_for_discussion, reject_rea
return proposal_schema.dump(proposal)
+@blueprint.route('/proposals//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//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//accept', methods=['PUT'])
@body({
"isAccepted": fields.Bool(required=True),
diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py
index 777bdd08..3cdacd6a 100644
--- a/backend/grant/proposal/models.py
+++ b/backend/grant/proposal/models.py
@@ -1,8 +1,8 @@
import datetime
import json
-from typing import Optional
from decimal import Decimal, ROUND_DOWN
from functools import reduce
+from typing import Optional
from marshmallow import post_dump
from sqlalchemy import func, or_, select, ForeignKey
@@ -10,15 +10,14 @@ from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import column_property
from grant.comment.models import Comment
-from grant.milestone.models import Milestone
from grant.email.send import send_email
from grant.extensions import ma, db
+from grant.milestone.models import Milestone
from grant.settings import PROPOSAL_STAKING_AMOUNT, PROPOSAL_TARGET_MAX
from grant.task.jobs import ContributionExpired
from grant.utils.enums import (
ProposalStatus,
ProposalStage,
- Category,
ContributionStatus,
ProposalArbiterStatus,
MilestoneStage,
@@ -332,43 +331,49 @@ class ProposalRevision(db.Model):
if old_proposal.title != new_proposal.title:
proposal_changes.append({"type": ProposalChange.PROPOSAL_EDIT_TITLE})
- milestone_changes = ProposalRevision.calculate_milestone_changes(old_proposal.milestones, new_proposal.milestones)
+ milestone_changes = ProposalRevision.calculate_milestone_changes(old_proposal.milestones,
+ new_proposal.milestones)
return proposal_changes + milestone_changes
def default_proposal_content():
- return """# Applicant background
+ return """### If you have any doubts about the questions below, please reach out to anyone on the ZOMG on the [Zcash forums](https://forum.zcashcommunity.com/).
-Summarize you and/or your 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
-
Dive into the _how_ of your project. Describe your approaches, components, workflows, methodology, etc. Bullet points and diagrams are appreciated!
+# How big of a problem would it be to not solve this problem?
+
# Execution risks
+What obstacles do you expect? What is most likely to go wrong? Which unknown factors or dependencies could jeopardize success? Who would have to incorporate your work in order for it to be usable?
-What obstacles do you expect? What is most likely to go wrong? Which unknown factors or dependencies could jeopardize success? What are your contingency plans? Will subsequent activities be required to maximize impact?
-
-# Downsides
+# Unintended Consequences Downsides
What are the negative ramifications if your project is successful? Consider usability, stability, privacy, integrity, availability, decentralization, interoperability, maintainability, technical debt, requisite education, etc.
# Evaluation plan
+What metrics for success can you share with the community once 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.
-# 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_published = db.Column(db.DateTime)
reject_reason = db.Column(db.String())
+ kyc_approved = db.Column(db.Boolean(), nullable=True, default=False)
+ funded_by_zomg = db.Column(db.Boolean(), nullable=True, default=False)
+
accepted_with_funding = db.Column(db.Boolean(), nullable=True)
changes_requested_discussion = db.Column(db.Boolean(), nullable=True)
changes_requested_discussion_reason = db.Column(db.String(255), nullable=True)
@@ -420,21 +428,23 @@ class Proposal(db.Model):
)
followers_count = column_property(
select([func.count(proposal_follower.c.proposal_id)])
- .where(proposal_follower.c.proposal_id == id)
- .correlate_except(proposal_follower)
+ .where(proposal_follower.c.proposal_id == id)
+ .correlate_except(proposal_follower)
)
likes = db.relationship(
"User", secondary=proposal_liker, back_populates="liked_proposals"
)
likes_count = column_property(
select([func.count(proposal_liker.c.proposal_id)])
- .where(proposal_liker.c.proposal_id == id)
- .correlate_except(proposal_liker)
+ .where(proposal_liker.c.proposal_id == id)
+ .correlate_except(proposal_liker)
)
live_draft_parent_id = db.Column(db.Integer, ForeignKey('proposal.id'))
- live_draft = db.relationship("Proposal", uselist=False, backref=db.backref('live_draft_parent', remote_side=[id], uselist=False))
+ live_draft = db.relationship("Proposal", uselist=False,
+ backref=db.backref('live_draft_parent', remote_side=[id], uselist=False))
- revisions = db.relationship(ProposalRevision, foreign_keys=[ProposalRevision.proposal_id], lazy=True, cascade="all, delete-orphan")
+ revisions = db.relationship(ProposalRevision, foreign_keys=[ProposalRevision.proposal_id], lazy=True,
+ cascade="all, delete-orphan")
def __init__(
self,
@@ -460,6 +470,7 @@ class Proposal(db.Model):
self.deadline_duration = deadline_duration
self.stage = stage
self.version = '2'
+ self.funded_by_zomg = True
@staticmethod
def simple_validate(proposal):
@@ -525,7 +536,7 @@ class Proposal(db.Model):
# Validate payout address
if not is_z_address_valid(self.payout_address):
raise ValidationException("Payout address is not a valid z address")
-
+
# Validate tip jar address
if self.tip_jar_address and not is_z_address_valid(self.tip_jar_address):
raise ValidationException("Tip address is not a valid z address")
@@ -533,7 +544,6 @@ class Proposal(db.Model):
# Then run through regular validation
Proposal.simple_validate(vars(self))
-
def validate_milestone_days(self):
for milestone in self.milestones:
if milestone.immediate_payout:
@@ -610,11 +620,11 @@ class Proposal(db.Model):
self.rfp_opt_in = opt_in
def create_contribution(
- self,
- amount,
- user_id: int = None,
- staking: bool = False,
- private: bool = True,
+ self,
+ amount,
+ user_id: int = None,
+ staking: bool = False,
+ private: bool = True,
):
contribution = ProposalContribution(
proposal_id=self.id,
@@ -921,8 +931,8 @@ class Proposal(db.Model):
return False
res = (
db.session.query(proposal_follower)
- .filter_by(user_id=authed.id, proposal_id=self.id)
- .count()
+ .filter_by(user_id=authed.id, proposal_id=self.id)
+ .count()
)
if res:
return True
@@ -937,8 +947,8 @@ class Proposal(db.Model):
return False
res = (
db.session.query(proposal_liker)
- .filter_by(user_id=authed.id, proposal_id=self.id)
- .count()
+ .filter_by(user_id=authed.id, proposal_id=self.id)
+ .count()
)
if res:
return True
@@ -1096,7 +1106,9 @@ class ProposalSchema(ma.Schema):
"tip_jar_view_key",
"changes_requested_discussion",
"changes_requested_discussion_reason",
- "live_draft_id"
+ "live_draft_id",
+ "kyc_approved",
+ "funded_by_zomg"
)
date_created = ma.Method("get_date_created")
@@ -1106,6 +1118,7 @@ class ProposalSchema(ma.Schema):
is_version_two = ma.Method("get_is_version_two")
tip_jar_view_key = ma.Method("get_tip_jar_view_key")
live_draft_id = ma.Method("get_live_draft_id")
+ funded_by_zomg = ma.Method("get_funded_by_zomg")
updates = ma.Nested("ProposalUpdateSchema", many=True)
team = ma.Nested("UserSchema", many=True)
@@ -1115,6 +1128,14 @@ class ProposalSchema(ma.Schema):
rfp = ma.Nested("RFPSchema", exclude=["accepted_proposals"])
arbiter = ma.Nested("ProposalArbiterSchema", exclude=["proposal"])
+ def get_funded_by_zomg(self, obj):
+ if obj.funded_by_zomg is None:
+ return False
+ elif obj.funded_by_zomg is False:
+ return False
+ else:
+ return True
+
def get_proposal_id(self, obj):
return obj.id
diff --git a/backend/grant/templates/emails/proposal_approved.html b/backend/grant/templates/emails/proposal_approved.html
index a1e46e87..f03724f9 100644
--- a/backend/grant/templates/emails/proposal_approved.html
+++ b/backend/grant/templates/emails/proposal_approved.html
@@ -1,5 +1,5 @@
- 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. Click here for instructions on documentation you need to submit before the Zcash Foundation can transfer funds.
{% if args.admin_note %}
diff --git a/backend/grant/templates/emails/template.html b/backend/grant/templates/emails/template.html
index dd5aa672..cd14340d 100644
--- a/backend/grant/templates/emails/template.html
+++ b/backend/grant/templates/emails/template.html
@@ -91,7 +91,7 @@
-
diff --git a/backend/migrations/versions/91b16dc2fd74_.py b/backend/migrations/versions/91b16dc2fd74_.py
new file mode 100644
index 00000000..7d44efd7
--- /dev/null
+++ b/backend/migrations/versions/91b16dc2fd74_.py
@@ -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 ###
diff --git a/backend/migrations/versions/d03c91f3038d_.py b/backend/migrations/versions/d03c91f3038d_.py
new file mode 100644
index 00000000..a1ad92dc
--- /dev/null
+++ b/backend/migrations/versions/d03c91f3038d_.py
@@ -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 ###
diff --git a/frontend/client/Routes.tsx b/frontend/client/Routes.tsx
index 84028567..ceaa65a6 100644
--- a/frontend/client/Routes.tsx
+++ b/frontend/client/Routes.tsx
@@ -37,6 +37,7 @@ const Tos = loadable(() => import('pages/tos'));
const ProposalTutorial = loadable(() => import('pages/proposal-tutorial'));
const About = loadable(() => import('pages/about'), opts);
const Privacy = loadable(() => import('pages/privacy'), opts);
+const Kyc = loadable(() => import('pages/kyc'), opts);
const Contact = loadable(() => import('pages/contact'), opts);
const CodeOfConduct = loadable(() => import('pages/code-of-conduct'), opts);
const VerifyEmail = loadable(() => import('pages/email-verify'), opts);
@@ -254,6 +255,18 @@ const routeConfigs: RouteConfig[] = [
},
onlyLoggedIn: false,
},
+ {
+ // Privacy page
+ route: {
+ path: '/kyc',
+ component: Kyc,
+ exact: true,
+ },
+ template: {
+ title: 'KYC',
+ },
+ onlyLoggedIn: false,
+ },
{
// Privacy page
route: {
diff --git a/frontend/client/api/constants.ts b/frontend/client/api/constants.ts
index 77d60521..19d88af9 100644
--- a/frontend/client/api/constants.ts
+++ b/frontend/client/api/constants.ts
@@ -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: {
diff --git a/frontend/client/components/CreateFlow/Basics.less b/frontend/client/components/CreateFlow/Basics.less
new file mode 100644
index 00000000..a8f25f89
--- /dev/null
+++ b/frontend/client/components/CreateFlow/Basics.less
@@ -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
+ }
+
+}
\ No newline at end of file
diff --git a/frontend/client/components/CreateFlow/Basics.tsx b/frontend/client/components/CreateFlow/Basics.tsx
index b4803adc..974b594b 100644
--- a/frontend/client/components/CreateFlow/Basics.tsx
+++ b/frontend/client/components/CreateFlow/Basics.tsx
@@ -1,16 +1,18 @@
import React from 'react';
import { connect } from 'react-redux';
-import { Input, Form, Alert, Popconfirm, message, Radio } from 'antd';
+import { Alert, Form, Input, message, Modal, Popconfirm, Radio } from 'antd';
import { RadioChangeEvent } from 'antd/lib/radio';
import { ProposalDraft, RFP } from 'types';
import { getCreateErrors } from 'modules/create/utils';
import { Link } from 'react-router-dom';
import { unlinkProposalRFP } from 'modules/create/actions';
import { AppState } from 'store/reducers';
+import './Basics.less';
interface OwnProps {
proposalId: number;
initialState?: Partial;
+
updateForm(form: Partial): void;
}
@@ -30,6 +32,7 @@ interface State extends Partial {
brief: string;
target: string;
rfp?: RFP;
+ visible: boolean
}
class CreateFlowBasics extends React.Component {
@@ -39,10 +42,29 @@ class CreateFlowBasics extends React.Component {
title: '',
brief: '',
target: '',
+ visible: false,
...(props.initialState || {}),
};
}
+ showModal = () => {
+ this.setState({
+ visible: true,
+ });
+ };
+
+ handleOk = () => {
+ this.setState({
+ visible: false,
+ });
+ };
+
+ handleCancel = () => {
+ this.setState({
+ visible: false,
+ });
+ };
+
componentDidUpdate(prevProps: Props) {
const { unlinkProposalRFPError, isUnlinkingProposalRFP } = this.props;
if (
@@ -72,21 +94,21 @@ class CreateFlowBasics extends React.Component {
}
return (
- |