robosats/frontend/src/components/OrderPage.js
Reckless_Satoshi 822281e467
Run npm run lint:fix
Still many linting errors to fix manually
2022-09-09 10:57:05 -07:00

1212 lines
40 KiB
JavaScript

import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import {
TextField,
Chip,
Tooltip,
IconButton,
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 } from 'react-countdown';
import { StoreTokenDialog, NoRobotDialog } from './Dialogs';
import currencyDict from '../../static/assets/currencies.json';
import PaymentText from './PaymentText';
import TradeBox from './TradeBox';
import FlagWithProps from './FlagWithProps';
import LinearDeterminate from './LinearDeterminate';
import MediaQuery from 'react-responsive';
import { t } from 'i18next';
// 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';
import HourglassTopIcon from '@mui/icons-material/HourglassTop';
import CheckIcon from '@mui/icons-material/Check';
import { SendReceiveIcon } from './Icons';
import { getCookie } from '../utils/cookies';
import { pn } from '../utils/prettyNumbers';
import { copyToClipboard } from '../utils/clipboard';
import { getWebln } from '../utils/webln';
class OrderPage extends Component {
constructor(props) {
super(props);
this.state = {
is_explicit: false,
delay: 60000, // Refresh every 60 seconds by default
total_secs_exp: 300,
loading: true,
openCancel: false,
openCollaborativeCancel: false,
openInactiveMaker: false,
openWeblnDialog: false,
waitingWebln: false,
openStoreToken: false,
tabValue: 1,
orderId: this.props.match.params.orderId,
};
// Refresh delays according to Order status
this.statusToDelay = {
0: 2000, // 'Waiting for maker bond'
1: 25000, // 'Public'
2: 90000, // 'Paused'
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.amount = this.state.amount;
newStateVars.status = this.state.status;
}
const otherStateVars = {
amount: newStateVars.amount ? newStateVars.amount : null,
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
};
const completeStateVars = Object.assign({}, newStateVars, otherStateVars);
this.setState(completeStateVars);
};
getOrderDetails = (id) => {
this.setState({ orderId: id });
fetch('/api/order' + '?order_id=' + id)
.then((response) => response.json())
.then(this.orderDetailsReceived);
};
orderDetailsReceived = (data) => {
if (data.status !== this.state.status) {
this.handleWebln(data);
}
this.completeSetState(data);
this.setState({ pauseLoading: false });
};
// These are used to refresh the data
componentDidMount() {
this.getOrderDetails(this.props.match.params.orderId);
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(this.state.orderId);
};
handleWebln = async (data) => {
const webln = await getWebln();
// If Webln implements locked payments compatibility, this logic might be simplier
if (data.is_maker & (data.status == 0)) {
webln.sendPayment(data.bond_invoice);
this.setState({ waitingWebln: true, openWeblnDialog: true });
} else if (data.is_taker & (data.status == 3)) {
webln.sendPayment(data.bond_invoice);
this.setState({ waitingWebln: true, openWeblnDialog: true });
} else if (data.is_seller & (data.status == 6 || data.status == 7)) {
webln.sendPayment(data.escrow_invoice);
this.setState({ waitingWebln: true, openWeblnDialog: true });
} else if (data.is_buyer & (data.status == 6 || data.status == 8)) {
this.setState({ waitingWebln: true, openWeblnDialog: true });
webln
.makeInvoice(data.trade_satoshis)
.then((invoice) => {
if (invoice) {
this.sendWeblnInvoice(invoice.paymentRequest);
this.setState({ waitingWebln: false, openWeblnDialog: false });
}
})
.catch(() => {
this.setState({ waitingWebln: false, openWeblnDialog: false });
});
} else {
this.setState({ waitingWebln: false });
}
};
sendWeblnInvoice = (invoice) => {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') },
body: JSON.stringify({
action: 'update_invoice',
invoice,
}),
};
fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions)
.then((response) => response.json())
.then((data) => this.completeSetState(data));
};
// Countdown Renderer callback with condition
countdownRenderer = ({ total, hours, minutes, seconds, completed }) => {
const { t } = this.props;
if (completed) {
// Render a completed state
return <span> {t('The order has expired')}</span>;
} else {
let col = 'inherit';
const 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 ? (
<b>
<span style={{ color: col }}>
{hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s{' '}
</span>
</b>
) : (
<span style={{ color: col }}>
{hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s{' '}
</span>
);
}
};
timerRenderer(seconds) {
const hours = parseInt(seconds / 3600);
const minutes = parseInt((seconds - hours * 3600) / 60);
return (
<span>
{hours > 0 ? hours + 'h' : ''} {minutes > 0 ? zeroPad(minutes) + 'm' : ''}{' '}
</span>
);
}
// Countdown Renderer callback with condition
countdownPenaltyRenderer = ({ minutes, seconds, completed }) => {
const { t } = this.props;
if (completed) {
// Render a completed state
return <span> {t('Penalty lifted, good to go!')}</span>;
} else {
return (
<span>
{' '}
{t('You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s', {
timeMin: zeroPad(minutes),
timeSec: zeroPad(seconds),
})}{' '}
</span>
);
}
};
handleTakeAmountChange = (e) => {
if ((e.target.value != '') & (e.target.value != null)) {
this.setState({ takeAmount: parseFloat(e.target.value) });
} else {
this.setState({ takeAmount: e.target.value });
}
};
amountHelperText = () => {
const { t } = this.props;
if ((this.state.takeAmount < this.state.min_amount) & (this.state.takeAmount != '')) {
return t('Too low');
} else if ((this.state.takeAmount > this.state.max_amount) & (this.state.takeAmount != '')) {
return t('Too high');
} else {
return null;
}
};
takeOrderButton = () => {
const { t } = this.props;
if (this.state.has_range) {
return (
<Grid
container
align='center'
alignItems='stretch'
justifyContent='center'
style={{ display: 'flex' }}
>
{this.InactiveMakerDialog()}
{this.tokenDialog()}
<div style={{ maxWidth: 120 }}>
<Tooltip
placement='top'
enterTouchDelay={500}
enterDelay={700}
enterNextDelay={2000}
title={t('Enter amount of fiat to exchange for bitcoin')}
>
<Paper elevation={5} sx={{ maxHeight: 40 }}>
<TextField
error={
(this.state.takeAmount < this.state.min_amount ||
this.state.takeAmount > this.state.max_amount) &
(this.state.takeAmount != '')
}
helperText={this.amountHelperText()}
label={t('Amount {{currencyCode}}', { currencyCode: this.state.currencyCode })}
size='small'
type='number'
required={true}
value={this.state.takeAmount}
inputProps={{
min: this.state.min_amount,
max: this.state.max_amount,
style: { textAlign: 'center' },
}}
onChange={this.handleTakeAmountChange}
/>
</Paper>
</Tooltip>
</div>
<div
style={{
height: 38,
top: '1px',
position: 'relative',
display:
this.state.takeAmount < this.state.min_amount ||
this.state.takeAmount > this.state.max_amount ||
this.state.takeAmount == '' ||
this.state.takeAmount == null
? ''
: 'none',
}}
>
<Tooltip
placement='top'
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={1200}
title={t('You must specify an amount first')}
>
<Paper elevation={4}>
<Button sx={{ height: 38 }} variant='contained' color='primary' disabled={true}>
{t('Take Order')}
</Button>
</Paper>
</Tooltip>
</div>
<div
style={{
height: 38,
top: '1px',
position: 'relative',
display:
this.state.takeAmount < this.state.min_amount ||
this.state.takeAmount > this.state.max_amount ||
this.state.takeAmount == '' ||
this.state.takeAmount == null
? 'none'
: '',
}}
>
<Paper elevation={4}>
<Button
sx={{ height: 38 }}
variant='contained'
color='primary'
onClick={
this.props.copiedToken
? this.state.maker_status == 'Inactive'
? this.handleClickOpenInactiveMakerDialog
: this.takeOrder
: () => this.setState({ openStoreToken: true })
}
>
{t('Take Order')}
</Button>
</Paper>
</div>
</Grid>
);
} else {
return (
<>
{this.InactiveMakerDialog()}
{this.tokenDialog()}
<Button
sx={{ height: 38 }}
variant='contained'
color='primary'
onClick={
this.props.copiedToken
? this.state.maker_status == 'Inactive'
? this.handleClickOpenInactiveMakerDialog
: this.takeOrder
: () => this.setState({ openStoreToken: true })
}
>
{t('Take Order')}
</Button>
</>
);
}
};
countdownTakeOrderRenderer = ({ seconds, completed }) => {
if (isNaN(seconds)) {
return this.takeOrderButton();
}
if (completed) {
// Render a completed state
return this.takeOrderButton();
} else {
return (
<Tooltip enterTouchDelay={0} title={t('Wait until you can take an order')}>
<div>
<Button disabled={true} variant='contained' color='primary'>
{t('Take Order')}
</Button>
</div>
</Tooltip>
);
}
};
takeOrder = () => {
this.setState({ loading: true });
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') },
body: JSON.stringify({
action: 'take',
amount: this.state.takeAmount,
}),
};
fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions)
.then((response) => response.json())
.then((data) => this.handleWebln(data) & this.completeSetState(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) {
const code = val ? currencyDict[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.state.orderId, requestOptions)
.then((response) => response.json())
.then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 }));
this.handleClickCloseConfirmCancelDialog();
};
handleClickOpenConfirmCancelDialog = () => {
this.setState({ openCancel: true });
};
handleClickCloseConfirmCancelDialog = () => {
this.setState({ openCancel: false });
};
CancelDialog = () => {
const { t } = this.props;
return (
<Dialog
open={this.state.openCancel}
onClose={this.handleClickCloseConfirmCancelDialog}
aria-labelledby='cancel-dialog-title'
aria-describedby='cancel-dialog-description'
>
<DialogTitle id='cancel-dialog-title'>{t('Cancel the order?')}</DialogTitle>
<DialogContent>
<DialogContentText id='cancel-dialog-description'>
{t('If the order is cancelled now you will lose your bond.')}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickCloseConfirmCancelDialog} autoFocus>
{t('Go back')}
</Button>
<Button onClick={this.handleClickConfirmCancelButton}>{t('Confirm Cancel')}</Button>
</DialogActions>
</Dialog>
);
};
handleClickOpenInactiveMakerDialog = () => {
this.setState({ openInactiveMaker: true });
};
handleClickCloseInactiveMakerDialog = () => {
this.setState({ openInactiveMaker: false });
};
InactiveMakerDialog = () => {
const { t } = this.props;
return (
<Dialog
open={this.state.openInactiveMaker}
onClose={this.handleClickCloseInactiveMakerDialog}
aria-labelledby='inactive-maker-dialog-title'
aria-describedby='inactive-maker-description'
>
<DialogTitle id='inactive-maker-dialog-title'>{t('The maker is away')}</DialogTitle>
<DialogContent>
<DialogContentText id='cancel-dialog-description'>
{t(
'By taking this order you risk wasting your time. If the maker does not proceed in time, you will be compensated in satoshis for 50% of the maker bond.',
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickCloseInactiveMakerDialog} autoFocus>
{t('Go back')}
</Button>
<Button onClick={this.takeOrder}>{t('Take Order')}</Button>
</DialogActions>
</Dialog>
);
};
tokenDialog = () => {
return getCookie('robot_token') ? (
<StoreTokenDialog
open={this.state.openStoreToken}
onClose={() => this.setState({ openStoreToken: false })}
onClickCopy={() =>
copyToClipboard(getCookie('robot_token')) & this.props.setAppState({ copiedToken: true })
}
copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'}
onClickBack={() => this.setState({ openStoreToken: false })}
onClickDone={() =>
this.setState({ openStoreToken: false }) &
(this.state.maker_status == 'Inactive'
? this.handleClickOpenInactiveMakerDialog()
: this.takeOrder())
}
/>
) : (
<NoRobotDialog
open={this.state.openStoreToken}
onClose={() => this.setState({ openStoreToken: false })}
/>
);
};
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.state.orderId, requestOptions)
.then((response) => response.json())
.then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 }));
this.handleClickCloseCollaborativeCancelDialog();
};
handleClickOpenCollaborativeCancelDialog = () => {
this.setState({ openCollaborativeCancel: true });
};
handleClickCloseCollaborativeCancelDialog = () => {
this.setState({ openCollaborativeCancel: false });
};
CollaborativeCancelDialog = () => {
const { t } = this.props;
return (
<Dialog
open={this.state.openCollaborativeCancel}
onClose={this.handleClickCloseCollaborativeCancelDialog}
aria-labelledby='collaborative-cancel-dialog-title'
aria-describedby='collaborative-cancel-dialog-description'
>
<DialogTitle id='cancel-dialog-title'>{t('Collaborative cancel the order?')}</DialogTitle>
<DialogContent>
<DialogContentText id='cancel-dialog-description'>
{t(
'The trade escrow has been posted. The order can be cancelled only if both, maker and taker, agree to cancel.',
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickCloseCollaborativeCancelDialog} autoFocus>
{t('Go back')}
</Button>
<Button onClick={this.handleClickConfirmCollaborativeCancelButton}>
{t('Ask for Cancel')}
</Button>
</DialogActions>
</Dialog>
);
};
BackButton = () => {
const { t } = this.props;
// If order has expired, show back button.
if (this.state.status == 5) {
return (
<Grid item xs={12} align='center'>
<Button variant='contained' color='secondary' onClick={this.props.history.goBack}>
{t('Back')}
</Button>
</Grid>
);
}
return null;
};
CancelButton = () => {
const { t } = this.props;
// 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, 2].includes(this.state.status) ||
this.state.is_taker & (this.state.status == 3)
) {
return (
<Grid item xs={12} align='center'>
<Button
variant='contained'
color='secondary'
onClick={this.handleClickConfirmCancelButton}
>
{t('Cancel')}
</Button>
</Grid>
);
}
// 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 (
<div id='openDialogCancelButton'>
<Grid item xs={12} align='center'>
{this.CancelDialog()}
<Button
variant='contained'
color='secondary'
onClick={this.handleClickOpenConfirmCancelDialog}
>
{t('Cancel')}
</Button>
</Grid>
</div>
);
}
// If the escrow is Locked, show the collaborative cancel button.
if ([8, 9].includes(this.state.status)) {
return (
<Grid item xs={12} align='center'>
{this.CollaborativeCancelDialog()}
<Button
variant='contained'
color='secondary'
onClick={this.handleClickOpenCollaborativeCancelDialog}
>
{t('Collaborative Cancel')}
</Button>
</Grid>
);
}
// 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 = () => {
const { t } = this.props;
return (
<Grid container spacing={1}>
<Grid item xs={12} align='center'>
<MediaQuery minWidth={920}>
<Typography component='h5' variant='h5'>
{t('Order Box')}
</Typography>
</MediaQuery>
<Paper elevation={12}>
<List dense={true}>
<ListItem>
<ListItemAvatar sx={{ width: 56, height: 56 }}>
<Tooltip placement='top' enterTouchDelay={0} title={t(this.state.maker_status)}>
<Badge
variant='dot'
overlap='circular'
badgeContent=''
color={this.statusBadgeColor(this.state.maker_status)}
>
<Badge
overlap='circular'
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
badgeContent={
<div style={{ position: 'relative', left: '5px', top: '2px' }}>
{' '}
{!this.state.type ? (
<SendReceiveIcon
sx={{ transform: 'scaleX(-1)', height: '18px', width: '18px' }}
color='secondary'
/>
) : (
<SendReceiveIcon
sx={{ height: '18px', width: '18px' }}
color='primary'
/>
)}
</div>
}
>
<Avatar
className='flippedSmallAvatar'
alt={this.state.maker_nick}
src={
window.location.origin +
'/static/assets/avatars/' +
this.state.maker_nick +
'.png'
}
/>
</Badge>
</Badge>
</Tooltip>
</ListItemAvatar>
<ListItemText
primary={
this.state.maker_nick +
(this.state.type ? ' ' + t('(Seller)') : ' ' + t('(Buyer)'))
}
secondary={t('Order maker')}
align='right'
/>
</ListItem>
{this.state.is_participant ? (
<>
{this.state.taker_nick != 'None' ? (
<>
<Divider />
<ListItem align='left'>
<ListItemText
primary={
this.state.taker_nick +
(this.state.type ? ' ' + t('(Buyer)') : ' ' + t('(Seller)'))
}
secondary={t('Order taker')}
/>
<ListItemAvatar>
<Tooltip enterTouchDelay={0} title={t(this.state.taker_status)}>
<Badge
variant='dot'
overlap='circular'
badgeContent=''
color={this.statusBadgeColor(this.state.taker_status)}
>
<Badge
overlap='circular'
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
badgeContent={
<div style={{ position: 'relative', right: '5px', top: '2px' }}>
{' '}
{this.state.type ? (
<SendReceiveIcon
sx={{ height: '18px', width: '18px' }}
color='secondary'
/>
) : (
<SendReceiveIcon
sx={{
transform: 'scaleX(-1)',
height: '18px',
width: '18px',
}}
color='primary'
/>
)}
</div>
}
>
<Avatar
className='smallAvatar'
alt={this.state.taker_nick}
src={
window.location.origin +
'/static/assets/avatars/' +
this.state.taker_nick +
'.png'
}
/>
</Badge>
</Badge>
</Tooltip>
</ListItemAvatar>
</ListItem>
</>
) : (
''
)}
<Divider>
<Chip label={t('Order Details')} />
</Divider>
<ListItem>
<ListItemIcon>
<ArticleIcon />
</ListItemIcon>
<ListItemText
primary={t(this.state.status_message)}
secondary={t('Order status')}
/>
</ListItem>
<Divider />
</>
) : (
<Divider>
<Chip label={t('Order Details')} />
</Divider>
)}
<ListItem>
<ListItemIcon>
<div
style={{
zoom: 1.25,
opacity: 0.7,
msZoom: 1.25,
WebkitZoom: 1.25,
MozTransform: 'scale(1.25,1.25)',
MozTransformOrigin: 'left center',
}}
>
<FlagWithProps code={this.state.currencyCode} />
</div>
</ListItemIcon>
{this.state.has_range & (this.state.amount == null) ? (
<ListItemText
primary={
pn(parseFloat(Number(this.state.min_amount).toPrecision(4))) +
'-' +
pn(parseFloat(Number(this.state.max_amount).toPrecision(4))) +
' ' +
this.state.currencyCode
}
secondary={t('Amount range')}
/>
) : (
<ListItemText
primary={
pn(
parseFloat(
parseFloat(this.state.amount).toFixed(
this.state.currency == 1000 ? 8 : 4,
),
),
) +
' ' +
this.state.currencyCode
}
secondary={t('Amount')}
/>
)}
</ListItem>
<Divider />
<ListItem>
<ListItemIcon>
<PaymentsIcon />
</ListItemIcon>
<ListItemText
primary={
<PaymentText
size={20}
othersText={t('Others')}
verbose={true}
text={this.state.payment_method}
/>
}
secondary={
this.state.currency == 1000
? t('Swap destination')
: t('Accepted payment methods')
}
/>
</ListItem>
<Divider />
{/* If there is live Price and Premium data, show it. Otherwise show the order maker settings */}
<ListItem>
<ListItemIcon>
<PriceChangeIcon />
</ListItemIcon>
{this.state.price_now ? (
<ListItemText
primary={t('{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%', {
price: pn(this.state.price_now),
currencyCode: this.state.currencyCode,
premium: this.state.premium_now,
})}
secondary={t('Price and Premium')}
/>
) : this.state.is_explicit ? (
<ListItemText
primary={pn(this.state.satoshis)}
secondary={t('Amount of Satoshis')}
/>
) : (
<ListItemText
primary={parseFloat(parseFloat(this.state.premium).toFixed(2)) + '%'}
secondary={t('Premium over market price')}
/>
)}
</ListItem>
<Divider />
<ListItem>
<ListItemIcon>
<NumbersIcon />
</ListItemIcon>
<Grid container>
<Grid item xs={4.5}>
<ListItemText primary={this.state.orderId} secondary={t('Order ID')} />
</Grid>
<Grid item xs={7.5}>
<Grid container>
<Grid item xs={2}>
<ListItemIcon sx={{ position: 'relative', top: '12px', left: '-5px' }}>
<HourglassTopIcon />
</ListItemIcon>
</Grid>
<Grid item xs={10}>
<ListItemText
primary={this.timerRenderer(this.state.escrow_duration)}
secondary={t('Deposit timer')}
></ListItemText>
</Grid>
</Grid>
</Grid>
</Grid>
</ListItem>
{/* if order is in a status that does not expire, do not show countdown */}
{[4, 12, 13, 14, 15, 16, 17, 18].includes(this.state.status) ? null : (
<>
<Divider />
<ListItem>
<ListItemIcon>
<AccessTimeIcon />
</ListItemIcon>
<ListItemText secondary={t('Expires in')}>
<Countdown
date={new Date(this.state.expires_at)}
renderer={this.countdownRenderer}
/>
</ListItemText>
</ListItem>
<LinearDeterminate
key={this.state.total_secs_exp}
totalSecsExp={this.state.total_secs_exp}
expiresAt={this.state.expires_at}
/>
</>
)}
</List>
{/* If the user has a penalty/limit */}
{this.state.penalty ? (
<>
<Divider />
<Grid item xs={12} align='center'>
<Alert severity='warning' sx={{ maxWidth: 360 }}>
<Countdown
date={new Date(this.state.penalty)}
renderer={this.countdownPenaltyRenderer}
/>
</Alert>
</Grid>
</>
) : null}
{/* If the counterparty asked for collaborative cancel */}
{this.state.pending_cancel ? (
<>
<Divider />
<Grid item xs={12} align='center'>
<Alert severity='warning' sx={{ maxWidth: 360 }}>
{t('{{nickname}} is asking for a collaborative cancel', {
nickname: this.state.is_maker ? this.state.taker_nick : this.state.maker_nick,
})}
</Alert>
</Grid>
</>
) : null}
{/* If the user has asked for a collaborative cancel */}
{this.state.asked_for_cancel ? (
<>
<Divider />
<Grid item xs={12} align='center'>
<Alert severity='warning' sx={{ maxWidth: 360 }}>
{t('You asked for a collaborative cancellation')}
</Alert>
</Grid>
</>
) : null}
</Paper>
</Grid>
<Grid item xs={12} align='center'>
{/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */}
{this.state.is_participant ? (
<>
{this.CancelButton()}
{this.BackButton()}
</>
) : (
<Grid container spacing={1}>
<Grid item xs={12} align='center'>
<Countdown
date={new Date(this.state.penalty)}
renderer={this.countdownTakeOrderRenderer}
/>
</Grid>
<Grid item xs={12} align='center'>
<Button variant='contained' color='secondary' onClick={this.props.history.goBack}>
{t('Back')}
</Button>
</Grid>
</Grid>
)}
</Grid>
</Grid>
);
};
doubleOrderPageDesktop = () => {
return (
<Grid container align='center' spacing={2}>
<Grid item xs={6} align='left' style={{ width: 330 }}>
{this.orderBox()}
</Grid>
<Grid item xs={6} align='left'>
<TradeBox
push={this.props.history.push}
getOrderDetails={this.getOrderDetails}
pauseLoading={this.state.pauseLoading}
width={330}
data={this.state}
completeSetState={this.completeSetState}
/>
</Grid>
</Grid>
);
};
a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
doubleOrderPagePhone = () => {
const { t } = this.props;
return (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={this.state.tabValue} variant='fullWidth'>
<Tab
label={t('Order')}
{...this.a11yProps(0)}
onClick={() => this.setState({ tabValue: 0 })}
/>
<Tab
label={t('Contract')}
{...this.a11yProps(1)}
onClick={() => this.setState({ tabValue: 1 })}
/>
</Tabs>
</Box>
<Grid container spacing={2}>
<Grid item>
<div style={{ width: 330, display: this.state.tabValue == 0 ? '' : 'none' }}>
{this.orderBox()}
</div>
<div style={{ display: this.state.tabValue == 1 ? '' : 'none' }}>
<TradeBox
push={this.props.history.push}
getOrderDetails={this.getOrderDetails}
pauseLoading={this.state.pauseLoading}
width={330}
data={this.state}
completeSetState={this.completeSetState}
/>
</div>
</Grid>
</Grid>
</Box>
);
};
orderDetailsPage() {
const { t } = this.props;
return this.state.bad_request ? (
<div align='center'>
<Typography variant='subtitle2' color='secondary'>
{/* IMPLEMENT I18N for bad_request */}
{t(this.state.bad_request)}
<br />
</Typography>
<Button variant='contained' color='secondary' onClick={this.props.history.goBack}>
{t('Back')}
</Button>
</div>
) : this.state.is_participant ? (
<>
{this.weblnDialog()}
{/* Desktop View */}
<MediaQuery minWidth={920}>{this.doubleOrderPageDesktop()}</MediaQuery>
{/* SmarPhone View */}
<MediaQuery maxWidth={919}>{this.doubleOrderPagePhone()}</MediaQuery>
</>
) : (
<Grid item xs={12} align='center' style={{ width: 330 }}>
{this.orderBox()}
</Grid>
);
}
handleCloseWeblnDialog = () => {
this.setState({ openWeblnDialog: false });
};
weblnDialog = () => {
const { t } = this.props;
return (
<Dialog
open={this.state.openWeblnDialog}
onClose={this.handleCloseWeblnDialog}
aria-labelledby='webln-dialog-title'
aria-describedby='webln-dialog-description'
>
<DialogTitle id='webln-dialog-title'>{t('WebLN')}</DialogTitle>
<DialogContent>
<DialogContentText id='webln-dialog-description'>
{this.state.waitingWebln ? (
<>
<CircularProgress size={16} thickness={5} style={{ marginRight: 10 }} />
{this.state.is_buyer
? t('Invoice not received, please check your WebLN wallet.')
: t('Payment not received, please check your WebLN wallet.')}
</>
) : (
<>
<CheckIcon color='success' />
{t('You can close now your WebLN wallet popup.')}
</>
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleCloseWeblnDialog} autoFocus>
{t('Done')}
</Button>
</DialogActions>
</Dialog>
);
};
render() {
return (
// Only so nothing shows while requesting the first batch of data
this.state.loading ? <CircularProgress /> : this.orderDetailsPage()
);
}
}
export default withTranslation()(OrderPage);