KYC acceptance property and admin UI
This commit is contained in:
parent
97b0cbc4b3
commit
ed7a3343c9
|
@ -3,31 +3,31 @@ import BN from 'bn.js';
|
||||||
import { view } from 'react-easy-state';
|
import { view } from 'react-easy-state';
|
||||||
import { RouteComponentProps, withRouter } from 'react-router';
|
import { RouteComponentProps, withRouter } from 'react-router';
|
||||||
import {
|
import {
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Card,
|
|
||||||
Alert,
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
Collapse,
|
Collapse,
|
||||||
Popconfirm,
|
|
||||||
Input,
|
Input,
|
||||||
Tag,
|
|
||||||
message,
|
message,
|
||||||
|
Popconfirm,
|
||||||
|
Row,
|
||||||
|
Tag,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import TextArea from 'antd/lib/input/TextArea';
|
import TextArea from 'antd/lib/input/TextArea';
|
||||||
import store from 'src/store';
|
import store from 'src/store';
|
||||||
import { formatDateSeconds, formatDurationSeconds } from 'util/time';
|
import { formatDateSeconds, formatDurationSeconds } from 'util/time';
|
||||||
import {
|
import {
|
||||||
PROPOSAL_STATUS,
|
|
||||||
PROPOSAL_ARBITER_STATUS,
|
|
||||||
MILESTONE_STAGE,
|
MILESTONE_STAGE,
|
||||||
|
PROPOSAL_ARBITER_STATUS,
|
||||||
PROPOSAL_STAGE,
|
PROPOSAL_STAGE,
|
||||||
|
PROPOSAL_STATUS,
|
||||||
} from 'src/types';
|
} from 'src/types';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Back from 'components/Back';
|
import Back from 'components/Back';
|
||||||
import Markdown from 'components/Markdown';
|
import Markdown from 'components/Markdown';
|
||||||
import ArbiterControl from 'components/ArbiterControl';
|
import ArbiterControl from 'components/ArbiterControl';
|
||||||
import { toZat, fromZat } from 'src/util/units';
|
import { fromZat, toZat } from 'src/util/units';
|
||||||
import FeedbackModal from '../FeedbackModal';
|
import FeedbackModal from '../FeedbackModal';
|
||||||
import { formatUsd } from 'util/formatters';
|
import { formatUsd } from 'util/formatters';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
@ -45,9 +45,11 @@ type State = typeof STATE;
|
||||||
class ProposalDetailNaked extends React.Component<Props, State> {
|
class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
state = STATE;
|
state = STATE;
|
||||||
rejectInput: null | TextArea = null;
|
rejectInput: null | TextArea = null;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadDetail();
|
this.loadDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const id = this.getIdFromQuery();
|
const id = this.getIdFromQuery();
|
||||||
const { proposalDetail: p, proposalDetailFetching } = store;
|
const { proposalDetail: p, proposalDetailFetching } = store;
|
||||||
|
@ -183,7 +185,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
<Alert
|
<Alert
|
||||||
showIcon
|
showIcon
|
||||||
type={p.rfpOptIn ? 'success' : 'error'}
|
type={p.rfpOptIn ? 'success' : 'error'}
|
||||||
message={p.rfpOptIn ? 'KYC accepted' : 'KYC rejected'}
|
message={p.rfpOptIn ? 'KYC Accepted by user' : 'KYC rejected'}
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
{p.rfpOptIn ? (
|
{p.rfpOptIn ? (
|
||||||
|
@ -272,24 +274,38 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
description={
|
description={
|
||||||
<div>
|
<div>
|
||||||
<p>Please review this proposal and render your judgment.</p>
|
<p>Please review this proposal and render your judgment.</p>
|
||||||
<Button
|
{!p.kycApproved ? (
|
||||||
className="ProposalDetail-review"
|
<Button
|
||||||
loading={store.proposalDetailAcceptingProposal}
|
className="ProposalDetail-review"
|
||||||
icon="check"
|
loading={store.proposalDetailApprovingKyc}
|
||||||
type="primary"
|
icon="check"
|
||||||
onClick={() => this.handleAcceptProposal(true, true)}
|
type="primary"
|
||||||
>
|
onClick={() => this.handleApproveKYC()}
|
||||||
Approve With Funding
|
>
|
||||||
</Button>
|
KYC Approved
|
||||||
<Button
|
</Button>
|
||||||
className="ProposalDetail-review"
|
) : (
|
||||||
loading={store.proposalDetailAcceptingProposal}
|
<>
|
||||||
icon="check"
|
<Button
|
||||||
type="default"
|
className="ProposalDetail-review"
|
||||||
onClick={() => this.handleAcceptProposal(true, false)}
|
loading={store.proposalDetailAcceptingProposal}
|
||||||
>
|
icon="check"
|
||||||
Approve Without Funding
|
type="primary"
|
||||||
</Button>
|
onClick={() => this.handleAcceptProposal(true, true)}
|
||||||
|
>
|
||||||
|
Approve With Funding
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="ProposalDetail-review"
|
||||||
|
loading={store.proposalDetailAcceptingProposal}
|
||||||
|
icon="check"
|
||||||
|
type="default"
|
||||||
|
onClick={() => this.handleAcceptProposal(true, false)}
|
||||||
|
>
|
||||||
|
Approve Without Funding
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
className="ProposalDetail-review"
|
className="ProposalDetail-review"
|
||||||
loading={store.proposalDetailMarkingChangesAsResolved}
|
loading={store.proposalDetailMarkingChangesAsResolved}
|
||||||
|
@ -716,6 +732,11 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
||||||
message.info('Proposal rejected permanently');
|
message.info('Proposal rejected permanently');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleApproveKYC = async () => {
|
||||||
|
await store.approveProposalKYC();
|
||||||
|
message.info(`Proposal KYC approved`);
|
||||||
|
};
|
||||||
|
|
||||||
private handleAcceptProposal = async (
|
private handleAcceptProposal = async (
|
||||||
isAccepted: boolean,
|
isAccepted: boolean,
|
||||||
withFunding: boolean,
|
withFunding: boolean,
|
||||||
|
|
|
@ -2,17 +2,17 @@ import { pick } from 'lodash';
|
||||||
import { store } from 'react-easy-state';
|
import { store } from 'react-easy-state';
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import {
|
import {
|
||||||
User,
|
|
||||||
Proposal,
|
|
||||||
CCR,
|
CCR,
|
||||||
|
CommentArgs,
|
||||||
Contribution,
|
Contribution,
|
||||||
ContributionArgs,
|
ContributionArgs,
|
||||||
|
EmailExample,
|
||||||
|
PageData,
|
||||||
|
PageQuery,
|
||||||
|
Proposal,
|
||||||
RFP,
|
RFP,
|
||||||
RFPArgs,
|
RFPArgs,
|
||||||
EmailExample,
|
User,
|
||||||
PageQuery,
|
|
||||||
PageData,
|
|
||||||
CommentArgs,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// API
|
// API
|
||||||
|
@ -142,6 +142,11 @@ async function approveDiscussion(
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function approveProposalKYC(id: number) {
|
||||||
|
const { data } = await api.put(`/admin/proposals/${id}/approve-kyc`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async function acceptProposal(
|
async function acceptProposal(
|
||||||
id: number,
|
id: number,
|
||||||
isAccepted: boolean,
|
isAccepted: boolean,
|
||||||
|
@ -345,6 +350,7 @@ const app = store({
|
||||||
proposalDetailApprovingDiscussion: false,
|
proposalDetailApprovingDiscussion: false,
|
||||||
proposalDetailMarkingChangesAsResolved: false,
|
proposalDetailMarkingChangesAsResolved: false,
|
||||||
proposalDetailAcceptingProposal: false,
|
proposalDetailAcceptingProposal: false,
|
||||||
|
proposalDetailApprovingKyc: false,
|
||||||
proposalDetailMarkingMilestonePaid: false,
|
proposalDetailMarkingMilestonePaid: false,
|
||||||
proposalDetailCanceling: false,
|
proposalDetailCanceling: false,
|
||||||
proposalDetailUpdating: false,
|
proposalDetailUpdating: false,
|
||||||
|
@ -688,6 +694,25 @@ const app = store({
|
||||||
handleApiError(e);
|
handleApiError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async approveProposalKYC() {
|
||||||
|
if (!app.proposalDetail) {
|
||||||
|
const m = 'store.acceptProposal(): Expected proposalDetail to be populated!';
|
||||||
|
app.generalError.push(m);
|
||||||
|
console.error(m);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.proposalDetailApprovingKyc = true;
|
||||||
|
try {
|
||||||
|
const { proposalId } = app.proposalDetail;
|
||||||
|
const res = await approveProposalKYC(proposalId);
|
||||||
|
app.updateProposalInStore(res);
|
||||||
|
} catch (e) {
|
||||||
|
handleApiError(e);
|
||||||
|
}
|
||||||
|
app.proposalDetailApprovingKyc = false;
|
||||||
|
},
|
||||||
|
|
||||||
async acceptProposal(
|
async acceptProposal(
|
||||||
isAccepted: boolean,
|
isAccepted: boolean,
|
||||||
withFunding: boolean,
|
withFunding: boolean,
|
||||||
|
@ -975,6 +1000,7 @@ function createDefaultPageData<T>(sort: string): PageData<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FNFetchPage = (params: PageQuery) => Promise<any>;
|
type FNFetchPage = (params: PageQuery) => Promise<any>;
|
||||||
|
|
||||||
interface PageParent<T> {
|
interface PageParent<T> {
|
||||||
page: PageData<T>;
|
page: PageData<T>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,7 @@ export interface Proposal {
|
||||||
isVersionTwo: boolean;
|
isVersionTwo: boolean;
|
||||||
changesRequestedDiscussion: boolean | null;
|
changesRequestedDiscussion: boolean | null;
|
||||||
changesRequestedDiscussionReason: string | null;
|
changesRequestedDiscussionReason: string | null;
|
||||||
|
kycApproved: null | boolean;
|
||||||
}
|
}
|
||||||
export interface Comment {
|
export interface Comment {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -377,6 +377,19 @@ def open_proposal_for_discussion(proposal_id, is_open_for_discussion, reject_rea
|
||||||
return proposal_schema.dump(proposal)
|
return proposal_schema.dump(proposal)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/proposals/<id>/approve-kyc', methods=['PUT'])
|
||||||
|
@admin.admin_auth_required
|
||||||
|
def approve_proposal_kyc(id):
|
||||||
|
proposal = Proposal.query.get(id)
|
||||||
|
if not proposal:
|
||||||
|
return {"message": "No proposal found."}, 404
|
||||||
|
|
||||||
|
proposal.kyc_approved = True
|
||||||
|
db.session.add(proposal)
|
||||||
|
db.session.commit()
|
||||||
|
return proposal_schema.dump(proposal)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/proposals/<id>/accept', methods=['PUT'])
|
@blueprint.route('/proposals/<id>/accept', methods=['PUT'])
|
||||||
@body({
|
@body({
|
||||||
"isAccepted": fields.Bool(required=True),
|
"isAccepted": fields.Bool(required=True),
|
||||||
|
|
|
@ -391,6 +391,8 @@ class Proposal(db.Model):
|
||||||
date_approved = db.Column(db.DateTime)
|
date_approved = db.Column(db.DateTime)
|
||||||
date_published = db.Column(db.DateTime)
|
date_published = db.Column(db.DateTime)
|
||||||
reject_reason = db.Column(db.String())
|
reject_reason = db.Column(db.String())
|
||||||
|
kyc_approved = db.Column(db.Boolean(), nullable=True, default=False)
|
||||||
|
|
||||||
accepted_with_funding = db.Column(db.Boolean(), nullable=True)
|
accepted_with_funding = db.Column(db.Boolean(), nullable=True)
|
||||||
changes_requested_discussion = db.Column(db.Boolean(), nullable=True)
|
changes_requested_discussion = db.Column(db.Boolean(), nullable=True)
|
||||||
changes_requested_discussion_reason = db.Column(db.String(255), nullable=True)
|
changes_requested_discussion_reason = db.Column(db.String(255), nullable=True)
|
||||||
|
@ -1096,7 +1098,8 @@ class ProposalSchema(ma.Schema):
|
||||||
"tip_jar_view_key",
|
"tip_jar_view_key",
|
||||||
"changes_requested_discussion",
|
"changes_requested_discussion",
|
||||||
"changes_requested_discussion_reason",
|
"changes_requested_discussion_reason",
|
||||||
"live_draft_id"
|
"live_draft_id",
|
||||||
|
"kyc_approved"
|
||||||
)
|
)
|
||||||
|
|
||||||
date_created = ma.Method("get_date_created")
|
date_created = ma.Method("get_date_created")
|
||||||
|
|
|
@ -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 ###
|
|
@ -105,7 +105,7 @@ class Header extends React.Component<Props, State> {
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="Header-links-button is-desktop">
|
{false && <div className="Header-links-button is-desktop">
|
||||||
<Link to="/create-request">
|
<Link to="/create-request">
|
||||||
{Array.isArray(ccrDrafts) && ccrDrafts.length > 0 ? (
|
{Array.isArray(ccrDrafts) && ccrDrafts.length > 0 ? (
|
||||||
<Button type={'primary'}>My Requests</Button>
|
<Button type={'primary'}>My Requests</Button>
|
||||||
|
@ -113,7 +113,7 @@ class Header extends React.Component<Props, State> {
|
||||||
<Button type={'primary'}>Create a Request</Button>
|
<Button type={'primary'}>Create a Request</Button>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>}
|
||||||
|
|
||||||
<HeaderAuth/>
|
<HeaderAuth/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,8 +30,8 @@ const HomeIntro: React.SFC<Props> = ({ t, authUser }) => (
|
||||||
{t('home.intro.signup')}
|
{t('home.intro.signup')}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<Link className="HomeIntro-content-buttons-button" to="/create-request">
|
<Link className="HomeIntro-content-buttons-button" to="/create">
|
||||||
{t('home.intro.ccr')}
|
Create a Proposal
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue