import React, { Component } from "react"; import { Chip, Tooltip, Badge, Tab, Tabs, Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material" import Countdown, { zeroPad, calcTimeDelta } from 'react-countdown'; import MediaQuery from 'react-responsive' import TradeBox from "./TradeBox"; import getFlags from './getFlags' // icons import AccessTimeIcon from '@mui/icons-material/AccessTime'; import NumbersIcon from '@mui/icons-material/Numbers'; import PriceChangeIcon from '@mui/icons-material/PriceChange'; import PaymentsIcon from '@mui/icons-material/Payments'; import ArticleIcon from '@mui/icons-material/Article'; function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } const csrftoken = getCookie('csrftoken'); // pretty numbers function pn(x) { var parts = x.toString().split("."); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); return parts.join("."); } export default class OrderPage extends Component { constructor(props) { super(props); this.state = { is_explicit: false, delay: 60000, // Refresh every 60 seconds by default currencies_dict: {"1":"USD"}, total_secs_exp: 300, loading: true, openCancel: false, openCollaborativeCancel: false, openInactiveMaker: false, showContractBox: 1, }; this.orderId = this.props.match.params.orderId; this.getCurrencyDict(); this.getOrderDetails(); // Refresh delays according to Order status this.statusToDelay = { "0": 2000, //'Waiting for maker bond' "1": 25000, //'Public' "2": 999999, //'Deleted' "3": 2000, //'Waiting for taker bond' "4": 999999, //'Cancelled' "5": 999999, //'Expired' "6": 6000, //'Waiting for trade collateral and buyer invoice' "7": 8000, //'Waiting only for seller trade collateral' "8": 8000, //'Waiting only for buyer invoice' "9": 10000, //'Sending fiat - In chatroom' "10": 10000, //'Fiat sent - In chatroom' "11": 30000, //'In dispute' "12": 999999, //'Collaboratively cancelled' "13": 3000, //'Sending satoshis to buyer' "14": 999999, //'Sucessful trade' "15": 10000, //'Failed lightning network routing' "16": 180000, //'Wait for dispute resolution' "17": 180000, //'Maker lost dispute' "18": 180000, //'Taker lost dispute' } } completeSetState=(newStateVars)=>{ // In case the reply only has "bad_request" // Do not substitute these two for "undefined" as // otherStateVars will fail to assign values if (newStateVars.currency == null){ newStateVars.currency = this.state.currency newStateVars.status = this.state.status } var otherStateVars = { loading: false, delay: this.setDelay(newStateVars.status), currencyCode: this.getCurrencyCode(newStateVars.currency), penalty: newStateVars.penalty, // in case penalty time has finished, it goes back to null invoice_expired: newStateVars.invoice_expired // in case invoice had expired, it goes back to null when it is valid again }; var completeStateVars = Object.assign({}, newStateVars, otherStateVars); this.setState(completeStateVars); } getOrderDetails() { this.setState(null) fetch('/api/order' + '?order_id=' + this.orderId) .then((response) => response.json()) .then((data) => this.completeSetState(data)); } // These are used to refresh the data componentDidMount() { this.interval = setInterval(this.tick, this.state.delay); } componentDidUpdate() { clearInterval(this.interval); this.interval = setInterval(this.tick, this.state.delay); } componentWillUnmount() { clearInterval(this.interval); } tick = () => { this.getOrderDetails(); } // Countdown Renderer callback with condition countdownRenderer = ({ total, hours, minutes, seconds, completed }) => { if (completed) { // Render a completed state return ( The order has expired); } else { var col = 'black' var fraction_left = (total/1000) / this.state.total_secs_exp // Make orange at 25% of time left if (fraction_left < 0.25){col = 'orange'} // Make red at 10% of time left if (fraction_left < 0.1){col = 'red'} // Render a countdown, bold when less than 25% return ( fraction_left < 0.25 ? {hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s :{hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s ); } }; // Countdown Renderer callback with condition countdownPenaltyRenderer = ({ minutes, seconds, completed }) => { if (completed) { // Render a completed state return ( Penalty lifted, good to go!); } else { return ( You cannot take an order yet! Wait {zeroPad(minutes)}m {zeroPad(seconds)}s ); } }; countdownTakeOrderRenderer = ({ seconds, completed }) => { if(isNaN(seconds)){ return ( <> ) } if (completed) { // Render a completed state return ( <> ); } else{ return(
) } }; LinearDeterminate =()=> { const [progress, setProgress] = React.useState(0); React.useEffect(() => { const timer = setInterval(() => { setProgress((oldProgress) => { var left = calcTimeDelta( new Date(this.state.expires_at)).total /1000; return (left / this.state.total_secs_exp) * 100; }); }, 1000); return () => { clearInterval(timer); }; }, []); return ( ); } takeOrder=()=>{ this.setState({loading:true}) const requestOptions = { method: 'POST', headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, body: JSON.stringify({ 'action':'take', }), }; fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions) .then((response) => response.json()) .then((data) => this.completeSetState(data)); } getCurrencyDict() { fetch('/static/assets/currencies.json') .then((response) => response.json()) .then((data) => this.setState({ currencies_dict: data })); } // set delay to the one matching the order status. If null order status, delay goes to 9999999. setDelay = (status)=>{ return status >= 0 ? this.statusToDelay[status.toString()] : 99999999; } getCurrencyCode(val){ let code = val ? this.state.currencies_dict[val.toString()] : "" return code } handleClickConfirmCancelButton=()=>{ this.setState({loading:true}) const requestOptions = { method: 'POST', headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, body: JSON.stringify({ 'action':'cancel', }), }; fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions) .then((response) => response.json()) .then((data) => this.getOrderDetails(data.id)); this.handleClickCloseConfirmCancelDialog(); } handleClickOpenConfirmCancelDialog = () => { this.setState({openCancel: true}); }; handleClickCloseConfirmCancelDialog = () => { this.setState({openCancel: false}); }; CancelDialog =() =>{ return( {"Cancel the order?"} If the order is cancelled now you will lose your bond. ) } handleClickOpenInactiveMakerDialog = () => { this.setState({openInactiveMaker: true}); }; handleClickCloseInactiveMakerDialog = () => { this.setState({openInactiveMaker: false}); }; InactiveMakerDialog =() =>{ return( {"The maker is away"} By taking this order you risk wasting your time. If the maker does not proceed in time, you will be compensated with 50% of the satoshis of maker bond. ) } handleClickConfirmCollaborativeCancelButton=()=>{ const requestOptions = { method: 'POST', headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, body: JSON.stringify({ 'action':'cancel', }), }; fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions) .then((response) => response.json()) .then((data) => this.getOrderDetails(data.id)); this.handleClickCloseCollaborativeCancelDialog(); } handleClickOpenCollaborativeCancelDialog = () => { this.setState({openCollaborativeCancel: true}); }; handleClickCloseCollaborativeCancelDialog = () => { this.setState({openCollaborativeCancel: false}); }; CollaborativeCancelDialog =() =>{ return( {"Collaborative cancel the order?"} The trade escrow has been posted. The order can be cancelled only if both, maker and taker, agree to cancel. ) } CancelButton = () => { // If maker and Waiting for Bond. Or if taker and Waiting for bond. // Simply allow to cancel without showing the cancel dialog. if ((this.state.is_maker & [0,1].includes(this.state.status)) || this.state.is_taker & this.state.status == 3){ return( )} // If the order does not yet have an escrow deposited. Show dialog // to confirm forfeiting the bond if ([3,6,7].includes(this.state.status)){ return(
)} // If the escrow is Locked, show the collaborative cancel button. if ([8,9].includes(this.state.status)){ return( )} // If none of the above do not return a cancel button. return(null) } // Colors for the status badges statusBadgeColor(status){ if(status=='Active'){return("success")} if(status=='Seen recently'){return("warning")} if(status=='Inactive'){return('error')} } orderBox=()=>{ return( Order Box {this.state.is_participant ? <> {this.state.taker_nick!='None' ? <> : "" } : } {getFlags(this.state.currencyCode)} {/* If there is live Price and Premium data, show it. Otherwise show the order maker settings */} {this.state.price_now? : (this.state.is_explicit ? : ) } {/* If the user has a penalty/limit */} {this.state.penalty ? <> : null} {/* If the counterparty asked for collaborative cancel */} {this.state.pending_cancel ? <> {this.state.is_maker ? this.state.taker_nick : this.state.maker_nick} is asking for a collaborative cancel : null} {/* If the user has asked for a collaborative cancel */} {this.state.asked_for_cancel ? <> You asked for a collaborative cancellation : null} {/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */} {this.state.is_participant ? : } ) } doubleOrderPageDesktop=()=>{ return( {this.orderBox()} ) } a11yProps(index) { return { id: `simple-tab-${index}`, 'aria-controls': `simple-tabpanel-${index}`, }; } doubleOrderPagePhone=()=>{ const [value, setValue] = React.useState(this.state.showContractBox); const handleChange = (event, newValue) => { this.setState({showContractBox:newValue}) setValue(newValue); }; return(
{this.orderBox()}
); } orderDetailsPage (){ return( this.state.bad_request ?
{this.state.bad_request}
: (this.state.is_participant ? <> {/* Desktop View */} {/* SmarPhone View */} : {this.orderBox()} ) ) } render (){ return ( // Only so nothing shows while requesting the first batch of data this.state.loading ? : this.orderDetailsPage() ); } }