feat: open dispute screen (#191)
* feat: open dispute screen * fix: code review * fix: tests
This commit is contained in:
parent
691f2931c3
commit
7a0aec96ef
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -1,4 +1,4 @@
|
|||
import {ARBITRATION_UNSOLVED, GET_DISPUTED_ESCROWS, RESOLVE_DISPUTE, RESOLVE_DISPUTE_FAILED, LOAD_ARBITRATION, GET_ARBITRATORS} from './constants';
|
||||
import {ARBITRATION_UNSOLVED, GET_DISPUTED_ESCROWS, RESOLVE_DISPUTE, RESOLVE_DISPUTE_FAILED, LOAD_ARBITRATION, GET_ARBITRATORS, OPEN_DISPUTE} from './constants';
|
||||
import Escrow from '../../../embarkArtifacts/contracts/Escrow';
|
||||
|
||||
export const getDisputedEscrows = () => ({type: GET_DISPUTED_ESCROWS});
|
||||
|
@ -17,6 +17,8 @@ export const resolveDispute = (escrowId, result) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const openDispute = (escrowId) => ({type: OPEN_DISPUTE, escrowId, toSend: Escrow.methods.openCase(escrowId)});
|
||||
|
||||
export const loadArbitration = (escrowId) => {
|
||||
return {type: LOAD_ARBITRATION, escrowId};
|
||||
};
|
||||
|
|
|
@ -7,6 +7,11 @@ export const RESOLVE_DISPUTE_PRE_SUCCESS = 'RESOLVE_DISPUTE_PRE_SUCCESS';
|
|||
export const RESOLVE_DISPUTE_SUCCEEDED = 'RESOLVE_DISPUTE_SUCCEEDED';
|
||||
export const RESOLVE_DISPUTE_FAILED = 'RESOLVE_DISPUTE_FAILED';
|
||||
|
||||
export const OPEN_DISPUTE = 'OPEN_DISPUTE';
|
||||
export const OPEN_DISPUTE_PRE_SUCCESS = 'OPEN_DISPUTE_PRE_SUCCESS';
|
||||
export const OPEN_DISPUTE_SUCCEEDED = 'OPEN_DISPUTE_SUCCEEDED';
|
||||
export const OPEN_DISPUTE_FAILED = 'OPEN_DISPUTE_FAILED';
|
||||
|
||||
export const ARBITRATION_UNSOLVED = "0";
|
||||
export const ARBITRATION_SOLVED_BUYER = "1";
|
||||
export const ARBITRATION_SOLVED_SELLER = "2";
|
||||
|
|
|
@ -8,10 +8,14 @@ import {
|
|||
RESOLVE_DISPUTE_FAILED,
|
||||
LOAD_ARBITRATION_SUCCEEDED,
|
||||
GET_ARBITRATORS_FAILED,
|
||||
GET_ARBITRATORS_SUCCEEDED
|
||||
GET_ARBITRATORS_SUCCEEDED,
|
||||
OPEN_DISPUTE_FAILED,
|
||||
OPEN_DISPUTE,
|
||||
OPEN_DISPUTE_SUCCEEDED,
|
||||
OPEN_DISPUTE_PRE_SUCCESS
|
||||
} from './constants';
|
||||
|
||||
const DEFAULT_STATE = {escrows: [], arbitration: null, arbitrators: []};
|
||||
const DEFAULT_STATE = {escrows: [], arbitration: null, arbitrators: [], receipt: null};
|
||||
|
||||
function reducer(state = DEFAULT_STATE, action) {
|
||||
let escrows = state.escrows;
|
||||
|
@ -19,9 +23,11 @@ function reducer(state = DEFAULT_STATE, action) {
|
|||
case GET_DISPUTED_ESCROWS:
|
||||
return {
|
||||
...state, ...{
|
||||
loading: true
|
||||
loading: true,
|
||||
receipt: null
|
||||
}
|
||||
};
|
||||
case OPEN_DISPUTE_PRE_SUCCESS:
|
||||
case RESOLVE_DISPUTE_PRE_SUCCESS:
|
||||
return {
|
||||
...state, ...{
|
||||
|
@ -35,6 +41,13 @@ function reducer(state = DEFAULT_STATE, action) {
|
|||
loading: false
|
||||
}
|
||||
};
|
||||
case OPEN_DISPUTE_SUCCEEDED:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
receipt: action.receipt
|
||||
};
|
||||
case OPEN_DISPUTE_FAILED:
|
||||
case GET_DISPUTED_ESCROWS_FAILED:
|
||||
case RESOLVE_DISPUTE_FAILED:
|
||||
case GET_ARBITRATORS_FAILED:
|
||||
|
@ -44,6 +57,7 @@ function reducer(state = DEFAULT_STATE, action) {
|
|||
loading: false
|
||||
}
|
||||
};
|
||||
case OPEN_DISPUTE:
|
||||
case RESOLVE_DISPUTE:
|
||||
return {
|
||||
...state, ...{
|
||||
|
|
|
@ -8,17 +8,18 @@ import {
|
|||
GET_DISPUTED_ESCROWS, GET_DISPUTED_ESCROWS_FAILED, GET_DISPUTED_ESCROWS_SUCCEEDED,
|
||||
RESOLVE_DISPUTE, RESOLVE_DISPUTE_FAILED, RESOLVE_DISPUTE_SUCCEEDED,
|
||||
RESOLVE_DISPUTE_PRE_SUCCESS, LOAD_ARBITRATION, LOAD_ARBITRATION_FAILED, LOAD_ARBITRATION_SUCCEEDED, GET_ARBITRATORS,
|
||||
GET_ARBITRATORS_SUCCEEDED, GET_ARBITRATORS_FAILED
|
||||
GET_ARBITRATORS_SUCCEEDED, GET_ARBITRATORS_FAILED, OPEN_DISPUTE, OPEN_DISPUTE_SUCCEEDED, OPEN_DISPUTE_FAILED, OPEN_DISPUTE_PRE_SUCCESS
|
||||
} from './constants';
|
||||
import {doTransaction} from "../../utils/saga";
|
||||
|
||||
window.Arbitration = Arbitration;
|
||||
|
||||
|
||||
export function *onResolveDispute() {
|
||||
yield takeEvery(RESOLVE_DISPUTE, doTransaction.bind(null, RESOLVE_DISPUTE_PRE_SUCCESS, RESOLVE_DISPUTE_SUCCEEDED, RESOLVE_DISPUTE_FAILED));
|
||||
}
|
||||
|
||||
export function *onOpenDispute() {
|
||||
yield takeEvery(OPEN_DISPUTE, doTransaction.bind(null, OPEN_DISPUTE_PRE_SUCCESS, OPEN_DISPUTE_SUCCEEDED, OPEN_DISPUTE_FAILED));
|
||||
}
|
||||
|
||||
export function *doGetArbitrators() {
|
||||
try {
|
||||
const cnt = yield call(Arbitration.methods.getNumLicenseOwners().call);
|
||||
|
@ -103,4 +104,4 @@ export function *onLoadArbitration() {
|
|||
yield takeEvery(LOAD_ARBITRATION, doLoadArbitration);
|
||||
}
|
||||
|
||||
export default [fork(onGetEscrows), fork(onResolveDispute), fork(onLoadArbitration), fork(onGetArbitrators)];
|
||||
export default [fork(onGetEscrows), fork(onResolveDispute), fork(onLoadArbitration), fork(onGetArbitrators), fork(onOpenDispute)];
|
||||
|
|
|
@ -14,6 +14,7 @@ import fourOFour from '../components/ErrorInformation/404';
|
|||
import Home from '../pages/Home';
|
||||
import Profile from '../pages/Profile';
|
||||
import Escrow from '../pages/Escrow';
|
||||
import OpenDispute from '../pages/OpenDispute';
|
||||
import Arbitration from '../pages/Arbitration';
|
||||
import MyProfile from '../pages/MyProfile';
|
||||
import EditMyContact from '../pages/EditMyContact';
|
||||
|
@ -98,7 +99,7 @@ class App extends Component {
|
|||
<Route exact path="/license" component={License}/>
|
||||
<Route exact path="/escrow/:id" component={Escrow}/>
|
||||
<Route exact path="/arbitration/:id" component={Arbitration}/>
|
||||
|
||||
<Route exact path="/openCase/:id" component={OpenDispute} />
|
||||
|
||||
<Route exact path="/offers/list" component={OffersList}/>
|
||||
<Route exact path="/offers/map" component={OffersMap}/>
|
||||
|
|
|
@ -4,17 +4,20 @@ import PropTypes from 'prop-types';
|
|||
import exclamationCircle from "../../../../images/exclamation-circle.png";
|
||||
import RoundedIcon from "../../../ui/RoundedIcon";
|
||||
import escrow from '../../../features/escrow';
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
const OpenDispute = ({trade}) => {
|
||||
const shouldDisplay = trade.status !== escrow.helpers.tradeStates.waiting && trade.status !== escrow.helpers.tradeStates.funded;
|
||||
return shouldDisplay && <Row className="mt-4 text-danger">
|
||||
<Col xs="2">
|
||||
<RoundedIcon image={exclamationCircle} bgColor="red"/>
|
||||
</Col>
|
||||
<Col xs="10" className="my-auto">
|
||||
<h6 className="m-0 font-weight-normal">Open dispute</h6>
|
||||
</Col>
|
||||
</Row>;
|
||||
return shouldDisplay && (
|
||||
<Row className="mt-4 text-danger" tag={Link} to={"/openCase/" + trade.escrowId}>
|
||||
<Col xs="2">
|
||||
<RoundedIcon image={exclamationCircle} bgColor="red"/>
|
||||
</Col>
|
||||
<Col xs="10" className="my-auto">
|
||||
<h6 className="m-0 font-weight-normal">Open dispute</h6>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
OpenDispute.propTypes = {
|
||||
|
|
|
@ -9,8 +9,18 @@ import Identicon from "../../../components/UserInformation/Identicon";
|
|||
import {formatBalance} from "../../../utils/numbers";
|
||||
import {tradeStates} from "../../../features/escrow/helpers";
|
||||
|
||||
const getTradeStyle = (tradeState) => {
|
||||
switch(tradeState){
|
||||
const getTradeStyle = (trade) => {
|
||||
if(trade.arbitration){
|
||||
if(trade.arbitration.open){
|
||||
trade.status = tradeStates.arbitration_open;
|
||||
} else {
|
||||
if(trade.arbitration.result !== '0'){
|
||||
trade.status = tradeStates.arbitration_closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(trade.status){
|
||||
case tradeStates.waiting:
|
||||
case tradeStates.funded:
|
||||
case tradeStates.paid:
|
||||
|
@ -26,7 +36,7 @@ const getTradeStyle = (tradeState) => {
|
|||
case tradeStates.arbitration_closed:
|
||||
return {text: 'Resolved', className: 'bg-primary'};
|
||||
default:
|
||||
return {text: tradeState, className: 'bg-secondary'};
|
||||
return {text: trade.status, className: 'bg-secondary'};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -37,7 +47,7 @@ class Trades extends Component {
|
|||
<Card body className="py-2 px-3 shadow-sm">
|
||||
{this.props.trades.map((trade, index) => {
|
||||
const isBuyer = trade.buyer === address;
|
||||
const tradeStyle = getTradeStyle(trade.status);
|
||||
const tradeStyle = getTradeStyle(trade);
|
||||
return <Link key={index} to={"/escrow/" + trade.escrowId}>
|
||||
<Row className="my-1 border-bottom">
|
||||
<Col className="align-self-center pr-0" xs="2">
|
||||
|
@ -50,7 +60,7 @@ class Trades extends Component {
|
|||
{isBuyer ? 'Buy' : 'Sell' } {formatBalance(trade.tokenAmount)} {trade.token.symbol}
|
||||
</Col>
|
||||
<Col className="align-self-center text-center text-success" xs="4">
|
||||
<span className={"p-1 text-uppercase d-inline text-white rounded-sm " + tradeStyle.className}>{tradeStyle.text}</span>
|
||||
<span className={"p-1 text-uppercase d-inline text-white rounded-sm text-small " + tradeStyle.className}>{tradeStyle.text}</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Link>;
|
||||
|
|
|
@ -137,7 +137,7 @@ exports[`Trades should render correctly 1`] = `
|
|||
xs="4"
|
||||
>
|
||||
<span
|
||||
className="p-1 text-uppercase d-inline text-white rounded-sm bg-secondary"
|
||||
className="p-1 text-uppercase d-inline text-white rounded-sm text-small bg-secondary"
|
||||
>
|
||||
open
|
||||
</span>
|
||||
|
|
|
@ -32,6 +32,15 @@ class MyProfile extends Component {
|
|||
|
||||
render() {
|
||||
const profile = this.props.profile;
|
||||
|
||||
const trades = this.props.trades.map(x => {
|
||||
const dispute = this.props.disputes.find(y => y.escrowId === x.escrowId);
|
||||
if(dispute){
|
||||
x.arbitration = dispute.arbitration;
|
||||
}
|
||||
return x;
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<UserInformation isArbitrator={profile.isArbitrator} reputation={profile.reputation} address={profile.address} username={profile.username}/>
|
||||
|
@ -42,7 +51,7 @@ class MyProfile extends Component {
|
|||
</Fragment>}
|
||||
|
||||
{ !profile.isArbitrator && <Fragment>
|
||||
<Trades trades={this.props.trades} address={this.props.address}/>
|
||||
<Trades trades={trades} address={this.props.address}/>
|
||||
<Offers offers={profile.offers} location={profile.location} />
|
||||
{profile.username && <StatusContactCode value={profile.statusContactCode} />}
|
||||
</Fragment> }
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
import React, {Component} from 'react';
|
||||
import Textarea from 'react-validation/build/textarea';
|
||||
import Form from 'react-validation/build/form';
|
||||
import {Button} from 'reactstrap';
|
||||
import Loading from '../../components/Loading';
|
||||
import ConfirmDialog from '../../components/ConfirmDialog';
|
||||
import PropTypes from 'prop-types';
|
||||
import {withRouter} from "react-router-dom";
|
||||
import {connect} from "react-redux";
|
||||
import arbitration from '../../features/arbitration';
|
||||
import successImage from '../../../images/success.png';
|
||||
|
||||
class OpenDispute extends Component {
|
||||
state = {
|
||||
value: '',
|
||||
displayDialog: false
|
||||
}
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
props.loadArbitration(props.escrowId);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.escrow.arbitration) {
|
||||
if(this.props.escrow.arbitration.open || this.props.escrow.arbitration.result !== "0"){
|
||||
this.props.history.push('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({value: e.target.value});
|
||||
}
|
||||
|
||||
displayDialog = show => () => {
|
||||
this.setState({displayDialog: show});
|
||||
};
|
||||
|
||||
goToProfile = () => {
|
||||
this.props.history.push('/profile');
|
||||
}
|
||||
|
||||
handleClickDialog = escrowId => () => {
|
||||
this.props.openDispute(escrowId);
|
||||
}
|
||||
|
||||
render(){
|
||||
const {escrow, loading, receipt} = this.props;
|
||||
|
||||
if(!escrow) return <Loading page />;
|
||||
if(loading) return <Loading mining />;
|
||||
|
||||
if(receipt) return (
|
||||
<div className="text-center p-5">
|
||||
<img src={successImage} alt="Success" width="160" height="160" className="mt-5" />
|
||||
<h2 className="mt-5">Your dispute was successfully open</h2>
|
||||
<p className="text-muted">Follow the progress of your dispute in the profile.</p>
|
||||
<p>
|
||||
<Button color="primary" onClick={this.goToProfile}>Okay</Button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="openDispute">
|
||||
<h2>Open dispute</h2>
|
||||
<p>Describe details of your trade</p>
|
||||
<Form>
|
||||
<Textarea
|
||||
type="text"
|
||||
name="disputeDetails"
|
||||
id="disputeDetails"
|
||||
rows="7"
|
||||
placeholder="What has happened?"
|
||||
className="form-control mb-2"
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
validations={[]}
|
||||
/>
|
||||
<p className="text-muted">The process of resolving your dispute could take up on 5 days. You will be sharing your chat logs with an arbiter.</p>
|
||||
<p className="text-center">
|
||||
<Button color="primary" disabled={!this.state.value} onClick={this.displayDialog(true)}>Send</Button>
|
||||
</p>
|
||||
</Form>
|
||||
<ConfirmDialog display={this.state.displayDialog} onConfirm={this.handleClickDialog(escrow.escrowId)} onCancel={this.displayDialog(false)} title="Open dispute" content="Are you sure?" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
OpenDispute.propTypes = {
|
||||
history: PropTypes.object,
|
||||
escrow: PropTypes.object,
|
||||
escrowId: PropTypes.string,
|
||||
loadArbitration: PropTypes.func,
|
||||
openDispute: PropTypes.func,
|
||||
loading: PropTypes.bool,
|
||||
receipt: PropTypes.object
|
||||
};
|
||||
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
return {
|
||||
escrowId: props.match.params.id.toString(),
|
||||
escrow: arbitration.selectors.getArbitration(state),
|
||||
loading: arbitration.selectors.loading(state),
|
||||
receipt: arbitration.selectors.receipt(state)
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
loadArbitration: arbitration.actions.loadArbitration,
|
||||
openDispute: arbitration.actions.openDispute
|
||||
}
|
||||
)(withRouter(OpenDispute));
|
||||
|
Loading…
Reference in New Issue