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 { RouteComponentProps, withRouter } from 'react-router';
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Collapse,
|
||||
Popconfirm,
|
||||
Input,
|
||||
Tag,
|
||||
message,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
import TextArea from 'antd/lib/input/TextArea';
|
||||
import store from 'src/store';
|
||||
import { formatDateSeconds, formatDurationSeconds } from 'util/time';
|
||||
import {
|
||||
PROPOSAL_STATUS,
|
||||
PROPOSAL_ARBITER_STATUS,
|
||||
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';
|
||||
import ArbiterControl from 'components/ArbiterControl';
|
||||
import { toZat, fromZat } from 'src/util/units';
|
||||
import { fromZat, toZat } from 'src/util/units';
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
import { formatUsd } from 'util/formatters';
|
||||
import './index.less';
|
||||
|
@ -45,9 +45,11 @@ type State = typeof STATE;
|
|||
class ProposalDetailNaked extends React.Component<Props, State> {
|
||||
state = STATE;
|
||||
rejectInput: null | TextArea = null;
|
||||
|
||||
componentDidMount() {
|
||||
this.loadDetail();
|
||||
}
|
||||
|
||||
render() {
|
||||
const id = this.getIdFromQuery();
|
||||
const { proposalDetail: p, proposalDetailFetching } = store;
|
||||
|
@ -183,7 +185,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
<Alert
|
||||
showIcon
|
||||
type={p.rfpOptIn ? 'success' : 'error'}
|
||||
message={p.rfpOptIn ? 'KYC accepted' : 'KYC rejected'}
|
||||
message={p.rfpOptIn ? 'KYC Accepted by user' : 'KYC rejected'}
|
||||
description={
|
||||
<div>
|
||||
{p.rfpOptIn ? (
|
||||
|
@ -272,24 +274,38 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
description={
|
||||
<div>
|
||||
<p>Please review this proposal and render your judgment.</p>
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
loading={store.proposalDetailAcceptingProposal}
|
||||
icon="check"
|
||||
type="primary"
|
||||
onClick={() => this.handleAcceptProposal(true, true)}
|
||||
>
|
||||
Approve With Funding
|
||||
</Button>
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
loading={store.proposalDetailAcceptingProposal}
|
||||
icon="check"
|
||||
type="default"
|
||||
onClick={() => this.handleAcceptProposal(true, false)}
|
||||
>
|
||||
Approve Without Funding
|
||||
</Button>
|
||||
{!p.kycApproved ? (
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
loading={store.proposalDetailApprovingKyc}
|
||||
icon="check"
|
||||
type="primary"
|
||||
onClick={() => this.handleApproveKYC()}
|
||||
>
|
||||
KYC Approved
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
loading={store.proposalDetailAcceptingProposal}
|
||||
icon="check"
|
||||
type="primary"
|
||||
onClick={() => this.handleAcceptProposal(true, true)}
|
||||
>
|
||||
Approve With Funding
|
||||
</Button>
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
loading={store.proposalDetailAcceptingProposal}
|
||||
icon="check"
|
||||
type="default"
|
||||
onClick={() => this.handleAcceptProposal(true, false)}
|
||||
>
|
||||
Approve Without Funding
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
className="ProposalDetail-review"
|
||||
loading={store.proposalDetailMarkingChangesAsResolved}
|
||||
|
@ -716,6 +732,11 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
message.info('Proposal rejected permanently');
|
||||
};
|
||||
|
||||
private handleApproveKYC = async () => {
|
||||
await store.approveProposalKYC();
|
||||
message.info(`Proposal KYC approved`);
|
||||
};
|
||||
|
||||
private handleAcceptProposal = async (
|
||||
isAccepted: boolean,
|
||||
withFunding: boolean,
|
||||
|
|
|
@ -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,11 @@ async function approveDiscussion(
|
|||
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 +350,7 @@ const app = store({
|
|||
proposalDetailApprovingDiscussion: false,
|
||||
proposalDetailMarkingChangesAsResolved: false,
|
||||
proposalDetailAcceptingProposal: false,
|
||||
proposalDetailApprovingKyc: false,
|
||||
proposalDetailMarkingMilestonePaid: false,
|
||||
proposalDetailCanceling: false,
|
||||
proposalDetailUpdating: false,
|
||||
|
@ -688,6 +694,25 @@ const app = store({
|
|||
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(
|
||||
isAccepted: boolean,
|
||||
withFunding: boolean,
|
||||
|
@ -975,6 +1000,7 @@ function createDefaultPageData<T>(sort: string): PageData<T> {
|
|||
}
|
||||
|
||||
type FNFetchPage = (params: PageQuery) => Promise<any>;
|
||||
|
||||
interface PageParent<T> {
|
||||
page: PageData<T>;
|
||||
}
|
||||
|
|
|
@ -123,6 +123,7 @@ export interface Proposal {
|
|||
isVersionTwo: boolean;
|
||||
changesRequestedDiscussion: boolean | null;
|
||||
changesRequestedDiscussionReason: string | null;
|
||||
kycApproved: null | boolean;
|
||||
}
|
||||
export interface Comment {
|
||||
id: number;
|
||||
|
|
|
@ -377,6 +377,19 @@ def open_proposal_for_discussion(proposal_id, is_open_for_discussion, reject_rea
|
|||
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'])
|
||||
@body({
|
||||
"isAccepted": fields.Bool(required=True),
|
||||
|
|
|
@ -391,6 +391,8 @@ 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)
|
||||
|
||||
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)
|
||||
|
@ -1096,7 +1098,8 @@ class ProposalSchema(ma.Schema):
|
|||
"tip_jar_view_key",
|
||||
"changes_requested_discussion",
|
||||
"changes_requested_discussion_reason",
|
||||
"live_draft_id"
|
||||
"live_draft_id",
|
||||
"kyc_approved"
|
||||
)
|
||||
|
||||
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>
|
||||
</div>
|
||||
<div className="Header-links-button is-desktop">
|
||||
{false && <div className="Header-links-button is-desktop">
|
||||
<Link to="/create-request">
|
||||
{Array.isArray(ccrDrafts) && ccrDrafts.length > 0 ? (
|
||||
<Button type={'primary'}>My Requests</Button>
|
||||
|
@ -113,7 +113,7 @@ class Header extends React.Component<Props, State> {
|
|||
<Button type={'primary'}>Create a Request</Button>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<HeaderAuth/>
|
||||
</div>
|
||||
|
|
|
@ -30,8 +30,8 @@ const HomeIntro: React.SFC<Props> = ({ t, authUser }) => (
|
|||
{t('home.intro.signup')}
|
||||
</Link>
|
||||
)}
|
||||
<Link className="HomeIntro-content-buttons-button" to="/create-request">
|
||||
{t('home.intro.ccr')}
|
||||
<Link className="HomeIntro-content-buttons-button" to="/create">
|
||||
Create a Proposal
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue