Merge pull request #236 from grant-project/proposal-unlink
Proposal RFP unlinking
This commit is contained in:
commit
95d57983e3
|
@ -239,6 +239,16 @@ def update_proposal(milestones, proposal_id, **kwargs):
|
||||||
return proposal_schema.dump(g.current_proposal), 200
|
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"])
|
@blueprint.route("/<proposal_id>", methods=["DELETE"])
|
||||||
@requires_team_member_auth
|
@requires_team_member_auth
|
||||||
@endpoint.api()
|
@endpoint.api()
|
||||||
|
|
|
@ -229,6 +229,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(
|
export async function requestProposalPayout(
|
||||||
proposalId: number,
|
proposalId: number,
|
||||||
milestoneId: number,
|
milestoneId: number,
|
||||||
|
|
|
@ -1,11 +1,31 @@
|
||||||
import React from 'react';
|
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 { SelectValue } from 'antd/lib/select';
|
||||||
import { PROPOSAL_CATEGORY, CATEGORY_UI } from 'api/constants';
|
import { PROPOSAL_CATEGORY, CATEGORY_UI } from 'api/constants';
|
||||||
import { ProposalDraft, RFP } from 'types';
|
import { ProposalDraft, RFP } from 'types';
|
||||||
import { getCreateErrors } from 'modules/create/utils';
|
import { getCreateErrors } from 'modules/create/utils';
|
||||||
import { typedKeys } from 'utils/ts';
|
import { typedKeys } from 'utils/ts';
|
||||||
import { Link } from 'react-router-dom';
|
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> {
|
interface State extends Partial<ProposalDraft> {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -15,12 +35,7 @@ interface State extends Partial<ProposalDraft> {
|
||||||
rfp?: RFP;
|
rfp?: RFP;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
initialState?: Partial<State>;
|
|
||||||
updateForm(form: Partial<ProposalDraft>): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class CreateFlowBasics extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -32,22 +47,22 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange = (
|
componentDidUpdate(prevProps: Props) {
|
||||||
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
const { unlinkProposalRFPError, isUnlinkingProposalRFP } = this.props;
|
||||||
) => {
|
if (
|
||||||
const { value, name } = event.currentTarget;
|
unlinkProposalRFPError &&
|
||||||
this.setState({ [name]: value } as any, () => {
|
unlinkProposalRFPError !== prevProps.unlinkProposalRFPError
|
||||||
this.props.updateForm(this.state);
|
) {
|
||||||
});
|
console.error('Failed to unlink request:', unlinkProposalRFPError);
|
||||||
};
|
message.error('Failed to unlink request');
|
||||||
|
} else if (!isUnlinkingProposalRFP && prevProps.isUnlinkingProposalRFP) {
|
||||||
handleCategoryChange = (value: SelectValue) => {
|
this.setState({ rfp: undefined });
|
||||||
this.setState({ category: value as PROPOSAL_CATEGORY }, () => {
|
message.success('Unlinked proposal from request');
|
||||||
this.props.updateForm(this.state);
|
}
|
||||||
});
|
}
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { isUnlinkingProposalRFP } = this.props;
|
||||||
const { title, brief, category, target, rfp } = this.state;
|
const { title, brief, category, target, rfp } = this.state;
|
||||||
const errors = getCreateErrors(this.state, true);
|
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">
|
<Link to={`/requests/${rfp.id}`} target="_blank">
|
||||||
{rfp.title}
|
{rfp.title}
|
||||||
</Link>
|
</Link>
|
||||||
. If you didn’t mean to do this, you can delete this proposal and create a
|
. If you didn’t mean to do this, or want to unlink it,{' '}
|
||||||
new one.
|
<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' }}
|
style={{ marginBottom: '2rem' }}
|
||||||
|
@ -138,4 +160,31 @@ export default class CreateFlowBasics extends React.Component<Props, State> {
|
||||||
</Form>
|
</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);
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { ProposalDraft } from 'types';
|
import { ProposalDraft } from 'types';
|
||||||
import types, { CreateDraftOptions } 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) {
|
export function initializeForm(proposalId: number) {
|
||||||
return {
|
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;
|
publishedProposal: Proposal | null;
|
||||||
isPublishing: boolean;
|
isPublishing: boolean;
|
||||||
publishError: string | null;
|
publishError: string | null;
|
||||||
|
|
||||||
|
isUnlinkingProposalRFP: boolean;
|
||||||
|
unlinkProposalRFPError: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INITIAL_STATE: CreateState = {
|
export const INITIAL_STATE: CreateState = {
|
||||||
|
@ -57,6 +60,9 @@ export const INITIAL_STATE: CreateState = {
|
||||||
publishedProposal: null,
|
publishedProposal: null,
|
||||||
isPublishing: false,
|
isPublishing: false,
|
||||||
publishError: null,
|
publishError: null,
|
||||||
|
|
||||||
|
isUnlinkingProposalRFP: false,
|
||||||
|
unlinkProposalRFPError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function createReducer(
|
export default function createReducer(
|
||||||
|
@ -190,6 +196,24 @@ export default function createReducer(
|
||||||
submitError: action.payload,
|
submitError: action.payload,
|
||||||
isSubmitting: false,
|
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;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,11 @@ enum CreateTypes {
|
||||||
SUBMIT_PROPOSAL_PENDING = 'SUBMIT_PROPOSAL_PENDING',
|
SUBMIT_PROPOSAL_PENDING = 'SUBMIT_PROPOSAL_PENDING',
|
||||||
SUBMIT_PROPOSAL_FULFILLED = 'SUBMIT_PROPOSAL_FULFILLED',
|
SUBMIT_PROPOSAL_FULFILLED = 'SUBMIT_PROPOSAL_FULFILLED',
|
||||||
SUBMIT_PROPOSAL_REJECTED = 'SUBMIT_PROPOSAL_REJECTED',
|
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 {
|
export interface CreateDraftOptions {
|
||||||
|
|
Loading…
Reference in New Issue