Merge branch 'develop' into comment-moderation
This commit is contained in:
commit
5a7235ab49
|
@ -257,6 +257,16 @@ def update_proposal(milestones, proposal_id, **kwargs):
|
|||
return proposal_schema.dump(g.current_proposal), 200
|
||||
|
||||
|
||||
@blueprint.route("/<proposal_id>/rfp", methods=["DELETE"])
|
||||
@requires_team_member_auth
|
||||
@endpoint.api()
|
||||
def unlink_proposal_from_rfp(proposal_id):
|
||||
g.current_proposal.rfp_id = None
|
||||
db.session.add(g.current_proposal)
|
||||
db.session.commit()
|
||||
return proposal_schema.dump(g.current_proposal), 200
|
||||
|
||||
|
||||
@blueprint.route("/<proposal_id>", methods=["DELETE"])
|
||||
@requires_team_member_auth
|
||||
@endpoint.api()
|
||||
|
|
|
@ -233,6 +233,10 @@ export async function putProposalPublish(
|
|||
});
|
||||
}
|
||||
|
||||
export async function deleteProposalRFPLink(proposalId: number): Promise<any> {
|
||||
return axios.delete(`/api/v1/proposals/${proposalId}/rfp`);
|
||||
}
|
||||
|
||||
export async function requestProposalPayout(
|
||||
proposalId: number,
|
||||
milestoneId: number,
|
||||
|
|
|
@ -47,13 +47,11 @@ export default class PaymentInfo extends React.Component<Props, State> {
|
|||
return (
|
||||
<Form className="PaymentInfo" layout="vertical">
|
||||
<div className="PaymentInfo-text">
|
||||
{text || (
|
||||
<>
|
||||
Thank you for contributing! Just send using whichever method works best for
|
||||
you, and we'll let you know when your contribution has been confirmed.
|
||||
</>
|
||||
)}
|
||||
{/* TODO: Help / FAQ page for sending */} Need help sending? <a>Click here</a>.
|
||||
{text ||
|
||||
`
|
||||
Thank you for contributing! Just send using whichever method works best for
|
||||
you, and we'll let you know when your contribution has been confirmed.
|
||||
`}
|
||||
</div>
|
||||
|
||||
<Radio.Group
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
import React from 'react';
|
||||
import { Input, Form, Icon, Select, Alert } from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import { Input, Form, Icon, Select, Alert, Popconfirm, message } from 'antd';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
import { PROPOSAL_CATEGORY, CATEGORY_UI } from 'api/constants';
|
||||
import { ProposalDraft, RFP } from 'types';
|
||||
import { getCreateErrors } from 'modules/create/utils';
|
||||
import { typedKeys } from 'utils/ts';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { unlinkProposalRFP } from 'modules/create/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
||||
interface OwnProps {
|
||||
proposalId: number;
|
||||
initialState?: Partial<State>;
|
||||
updateForm(form: Partial<ProposalDraft>): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
isUnlinkingProposalRFP: AppState['create']['isUnlinkingProposalRFP'];
|
||||
unlinkProposalRFPError: AppState['create']['unlinkProposalRFPError'];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
unlinkProposalRFP: typeof unlinkProposalRFP;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
interface State extends Partial<ProposalDraft> {
|
||||
title: string;
|
||||
|
@ -15,12 +35,7 @@ interface State extends Partial<ProposalDraft> {
|
|||
rfp?: RFP;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
initialState?: Partial<State>;
|
||||
updateForm(form: Partial<ProposalDraft>): void;
|
||||
}
|
||||
|
||||
export default class CreateFlowBasics extends React.Component<Props, State> {
|
||||
class CreateFlowBasics extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -32,22 +47,22 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
|
|||
};
|
||||
}
|
||||
|
||||
handleInputChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
const { value, name } = event.currentTarget;
|
||||
this.setState({ [name]: value } as any, () => {
|
||||
this.props.updateForm(this.state);
|
||||
});
|
||||
};
|
||||
|
||||
handleCategoryChange = (value: SelectValue) => {
|
||||
this.setState({ category: value as PROPOSAL_CATEGORY }, () => {
|
||||
this.props.updateForm(this.state);
|
||||
});
|
||||
};
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { unlinkProposalRFPError, isUnlinkingProposalRFP } = this.props;
|
||||
if (
|
||||
unlinkProposalRFPError &&
|
||||
unlinkProposalRFPError !== prevProps.unlinkProposalRFPError
|
||||
) {
|
||||
console.error('Failed to unlink request:', unlinkProposalRFPError);
|
||||
message.error('Failed to unlink request');
|
||||
} else if (!isUnlinkingProposalRFP && prevProps.isUnlinkingProposalRFP) {
|
||||
this.setState({ rfp: undefined });
|
||||
message.success('Unlinked proposal from request');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isUnlinkingProposalRFP } = this.props;
|
||||
const { title, brief, category, target, rfp } = this.state;
|
||||
const errors = getCreateErrors(this.state, true);
|
||||
|
||||
|
@ -63,8 +78,15 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
|
|||
<Link to={`/requests/${rfp.id}`} target="_blank">
|
||||
{rfp.title}
|
||||
</Link>
|
||||
. If you didn’t mean to do this, you can delete this proposal and create a
|
||||
new one.
|
||||
. If you didn’t mean to do this, or want to unlink it,{' '}
|
||||
<Popconfirm
|
||||
title="Are you sure? This cannot be undone."
|
||||
onConfirm={this.unlinkRfp}
|
||||
okButtonProps={{ loading: isUnlinkingProposalRFP }}
|
||||
>
|
||||
<a>click here</a>
|
||||
</Popconfirm>{' '}
|
||||
to do so.
|
||||
</>
|
||||
}
|
||||
style={{ marginBottom: '2rem' }}
|
||||
|
@ -138,4 +160,31 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
|
|||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
private handleInputChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
const { value, name } = event.currentTarget;
|
||||
this.setState({ [name]: value } as any, () => {
|
||||
this.props.updateForm(this.state);
|
||||
});
|
||||
};
|
||||
|
||||
private handleCategoryChange = (value: SelectValue) => {
|
||||
this.setState({ category: value as PROPOSAL_CATEGORY }, () => {
|
||||
this.props.updateForm(this.state);
|
||||
});
|
||||
};
|
||||
|
||||
private unlinkRfp = () => {
|
||||
this.props.unlinkProposalRFP(this.props.proposalId);
|
||||
};
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, OwnProps, AppState>(
|
||||
state => ({
|
||||
isUnlinkingProposalRFP: state.create.isUnlinkingProposalRFP,
|
||||
unlinkProposalRFPError: state.create.unlinkProposalRFPError,
|
||||
}),
|
||||
{ unlinkProposalRFP },
|
||||
)(CreateFlowBasics);
|
||||
|
|
|
@ -49,7 +49,6 @@ interface State {
|
|||
isBodyOverflowing: boolean;
|
||||
isUpdateOpen: boolean;
|
||||
isCancelOpen: boolean;
|
||||
bodyId: string;
|
||||
}
|
||||
|
||||
export class ProposalDetail extends React.Component<Props, State> {
|
||||
|
@ -58,9 +57,10 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
isBodyOverflowing: false,
|
||||
isUpdateOpen: false,
|
||||
isCancelOpen: false,
|
||||
bodyId: `body-${Math.floor(Math.random() * 1000000)}`,
|
||||
};
|
||||
|
||||
bodyEl: HTMLElement | null = null;
|
||||
|
||||
componentDidMount() {
|
||||
// always refresh from server
|
||||
this.props.fetchProposal(this.props.proposalId);
|
||||
|
@ -87,13 +87,7 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
|
||||
render() {
|
||||
const { user, detail: proposal, isPreview, detailError } = this.props;
|
||||
const {
|
||||
isBodyExpanded,
|
||||
isBodyOverflowing,
|
||||
isCancelOpen,
|
||||
isUpdateOpen,
|
||||
bodyId,
|
||||
} = this.state;
|
||||
const { isBodyExpanded, isBodyOverflowing, isCancelOpen, isUpdateOpen } = this.state;
|
||||
const showExpand = !isBodyExpanded && isBodyOverflowing;
|
||||
const wrongProposal = proposal && proposal.proposalId !== this.props.proposalId;
|
||||
|
||||
|
@ -209,7 +203,7 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
</h1>
|
||||
<div className="Proposal-top-main-block" style={{ flexGrow: 1 }}>
|
||||
<div
|
||||
id={bodyId}
|
||||
ref={el => (this.bodyEl = el)}
|
||||
className={classnames({
|
||||
['Proposal-top-main-block-bodyText']: true,
|
||||
['is-expanded']: isBodyExpanded,
|
||||
|
@ -291,20 +285,17 @@ export class ProposalDetail extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
private checkBodyOverflow = () => {
|
||||
const { isBodyExpanded, bodyId, isBodyOverflowing } = this.state;
|
||||
if (isBodyExpanded) {
|
||||
const { isBodyExpanded, isBodyOverflowing } = this.state;
|
||||
if (isBodyExpanded || !this.bodyEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use id instead of ref because styled component ref doesn't return html element
|
||||
const bodyEl = document.getElementById(bodyId);
|
||||
if (!bodyEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBodyOverflowing && bodyEl.scrollHeight <= bodyEl.clientHeight) {
|
||||
if (isBodyOverflowing && this.bodyEl.scrollHeight <= this.bodyEl.clientHeight) {
|
||||
this.setState({ isBodyOverflowing: false });
|
||||
} else if (!isBodyOverflowing && bodyEl.scrollHeight > bodyEl.clientHeight) {
|
||||
} else if (
|
||||
!isBodyOverflowing &&
|
||||
this.bodyEl.scrollHeight > this.bodyEl.clientHeight
|
||||
) {
|
||||
this.setState({ isBodyOverflowing: true });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { Dispatch } from 'redux';
|
||||
import { ProposalDraft } from 'types';
|
||||
import types, { CreateDraftOptions } from './types';
|
||||
import { putProposal, putProposalSubmitForApproval } from 'api/api';
|
||||
import {
|
||||
putProposal,
|
||||
putProposalSubmitForApproval,
|
||||
deleteProposalRFPLink,
|
||||
} from 'api/api';
|
||||
|
||||
export function initializeForm(proposalId: number) {
|
||||
return {
|
||||
|
@ -68,3 +72,20 @@ export function submitProposal(form: ProposalDraft) {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function unlinkProposalRFP(proposalId: number) {
|
||||
return async (dispatch: Dispatch<any>) => {
|
||||
dispatch({ type: types.UNLINK_PROPOSAL_RFP_PENDING });
|
||||
try {
|
||||
await deleteProposalRFPLink(proposalId);
|
||||
dispatch({ type: types.UNLINK_PROPOSAL_RFP_FULFILLED });
|
||||
dispatch(fetchDrafts());
|
||||
} catch (err) {
|
||||
dispatch({
|
||||
type: types.UNLINK_PROPOSAL_RFP_REJECTED,
|
||||
payload: err.message || err.toString(),
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ export interface CreateState {
|
|||
publishedProposal: Proposal | null;
|
||||
isPublishing: boolean;
|
||||
publishError: string | null;
|
||||
|
||||
isUnlinkingProposalRFP: boolean;
|
||||
unlinkProposalRFPError: string | null;
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: CreateState = {
|
||||
|
@ -57,6 +60,9 @@ export const INITIAL_STATE: CreateState = {
|
|||
publishedProposal: null,
|
||||
isPublishing: false,
|
||||
publishError: null,
|
||||
|
||||
isUnlinkingProposalRFP: false,
|
||||
unlinkProposalRFPError: null,
|
||||
};
|
||||
|
||||
export default function createReducer(
|
||||
|
@ -190,6 +196,24 @@ export default function createReducer(
|
|||
submitError: action.payload,
|
||||
isSubmitting: false,
|
||||
};
|
||||
|
||||
case types.UNLINK_PROPOSAL_RFP_PENDING:
|
||||
return {
|
||||
...state,
|
||||
isUnlinkingProposalRFP: true,
|
||||
unlinkProposalRFPError: null,
|
||||
};
|
||||
case types.UNLINK_PROPOSAL_RFP_FULFILLED:
|
||||
return {
|
||||
...state,
|
||||
isUnlinkingProposalRFP: false,
|
||||
};
|
||||
case types.UNLINK_PROPOSAL_RFP_REJECTED:
|
||||
return {
|
||||
...state,
|
||||
isUnlinkingProposalRFP: false,
|
||||
unlinkProposalRFPError: action.payload,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ enum CreateTypes {
|
|||
SUBMIT_PROPOSAL_PENDING = 'SUBMIT_PROPOSAL_PENDING',
|
||||
SUBMIT_PROPOSAL_FULFILLED = 'SUBMIT_PROPOSAL_FULFILLED',
|
||||
SUBMIT_PROPOSAL_REJECTED = 'SUBMIT_PROPOSAL_REJECTED',
|
||||
|
||||
UNLINK_PROPOSAL_RFP = 'UNLINK_PROPOSAL_RFP',
|
||||
UNLINK_PROPOSAL_RFP_PENDING = 'UNLINK_PROPOSAL_RFP_PENDING',
|
||||
UNLINK_PROPOSAL_RFP_FULFILLED = 'UNLINK_PROPOSAL_RFP_FULFILLED',
|
||||
UNLINK_PROPOSAL_RFP_REJECTED = 'UNLINK_PROPOSAL_RFP_REJECTED',
|
||||
}
|
||||
|
||||
export interface CreateDraftOptions {
|
||||
|
|
|
@ -145,6 +145,12 @@ export function massageSerializedState(state: AppState) {
|
|||
(state.proposal.detail.funded as any) as string,
|
||||
16,
|
||||
);
|
||||
if (state.proposal.detail.rfp && state.proposal.detail.rfp.bounty) {
|
||||
state.proposal.detail.rfp.bounty = new BN(
|
||||
(state.proposal.detail.rfp.bounty as any) as string,
|
||||
16,
|
||||
);
|
||||
}
|
||||
}
|
||||
// proposals
|
||||
state.proposal.page.items = state.proposal.page.items.map(p => ({
|
||||
|
|
Loading…
Reference in New Issue