mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-18 12:11:35 +00:00
Advanced maker options v2 (#110)
* Add escrow/invoice time customization * Add accordion for Expiry times * Add current price on order maker * Add deposit timeout limit on order page * Minor aestetic fixes * Implement pause/unpause and expiry reasons * Add renew order * Add highlight buy/sell on maker page * Fix order renewal. Improve book visuals and response. * Fix double renew requests * Fix cancel orders. Fix paused status to delay * Fix paused order layout and loading spinner * Add telegram message: order is in chat
This commit is contained in:
parent
0b7d2754d1
commit
755874b100
@ -38,6 +38,7 @@ class Logics:
|
||||
active_order_status = [
|
||||
Order.Status.WFB,
|
||||
Order.Status.PUB,
|
||||
Order.Status.PAU,
|
||||
Order.Status.TAK,
|
||||
Order.Status.WF2,
|
||||
Order.Status.WFE,
|
||||
@ -230,7 +231,6 @@ class Logics:
|
||||
# Do not change order status if an order in any with
|
||||
# any of these status is sent to expire here
|
||||
does_not_expire = [
|
||||
Order.Status.DEL,
|
||||
Order.Status.UCA,
|
||||
Order.Status.EXP,
|
||||
Order.Status.TLD,
|
||||
@ -247,13 +247,15 @@ class Logics:
|
||||
|
||||
elif order.status == Order.Status.WFB:
|
||||
order.status = Order.Status.EXP
|
||||
order.expiry_reason = Order.ExpiryReasons.NMBOND
|
||||
cls.cancel_bond(order.maker_bond)
|
||||
order.save()
|
||||
return True
|
||||
|
||||
elif order.status == Order.Status.PUB:
|
||||
elif order.status in [Order.Status.PUB, Order.Status.PAU]:
|
||||
cls.return_bond(order.maker_bond)
|
||||
order.status = Order.Status.EXP
|
||||
order.expiry_reason = Order.ExpiryReasons.NTAKEN
|
||||
order.save()
|
||||
send_message.delay(order.id,'order_expired_untaken')
|
||||
return True
|
||||
@ -274,6 +276,7 @@ class Logics:
|
||||
cls.settle_bond(order.taker_bond)
|
||||
cls.cancel_escrow(order)
|
||||
order.status = Order.Status.EXP
|
||||
order.expiry_reason = Order.ExpiryReasons.NESINV
|
||||
order.save()
|
||||
return True
|
||||
|
||||
@ -289,6 +292,7 @@ class Logics:
|
||||
except:
|
||||
pass
|
||||
order.status = Order.Status.EXP
|
||||
order.expiry_reason = Order.ExpiryReasons.NESCRO
|
||||
order.save()
|
||||
# Reward taker with part of the maker bond
|
||||
cls.add_slashed_rewards(order.maker_bond, order.taker.profile)
|
||||
@ -324,6 +328,7 @@ class Logics:
|
||||
cls.return_bond(order.taker_bond)
|
||||
cls.return_escrow(order)
|
||||
order.status = Order.Status.EXP
|
||||
order.expiry_reason = Order.ExpiryReasons.NINVOI
|
||||
order.save()
|
||||
# Reward taker with part of the maker bond
|
||||
cls.add_slashed_rewards(order.maker_bond, order.taker.profile)
|
||||
@ -525,6 +530,7 @@ class Logics:
|
||||
order.status = Order.Status.CHA
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.CHA))
|
||||
send_message.delay(order.id,'fiat_exchange_starts')
|
||||
|
||||
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
|
||||
if order.status == Order.Status.WF2:
|
||||
@ -536,6 +542,7 @@ class Logics:
|
||||
order.status = Order.Status.CHA
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.CHA))
|
||||
send_message.delay(order.id,'fiat_exchange_starts')
|
||||
else:
|
||||
order.status = Order.Status.WFE
|
||||
|
||||
@ -590,7 +597,6 @@ class Logics:
|
||||
# Do not change order status if an is in order
|
||||
# any of these status
|
||||
do_not_cancel = [
|
||||
Order.Status.DEL,
|
||||
Order.Status.UCA,
|
||||
Order.Status.EXP,
|
||||
Order.Status.TLD,
|
||||
@ -618,7 +624,7 @@ class Logics:
|
||||
"""The order dissapears from book and goes to cancelled. If strict, maker is charged the bond
|
||||
to prevent DDOS on the LN node and order book. If not strict, maker is returned
|
||||
the bond (more user friendly)."""
|
||||
elif order.status == Order.Status.PUB and order.maker == user:
|
||||
elif order.status in [Order.Status.PUB, Order.Status.PAU] and order.maker == user:
|
||||
# Return the maker bond (Maker gets returned the bond for cancelling public order)
|
||||
if cls.return_bond(order.maker_bond): # strict cancellation: cls.settle_bond(order.maker_bond):
|
||||
order.status = Order.Status.UCA
|
||||
@ -925,6 +931,7 @@ class Logics:
|
||||
order.status = Order.Status.CHA
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.CHA))
|
||||
send_message.delay(order.id,'fiat_exchange_starts')
|
||||
order.save()
|
||||
|
||||
@classmethod
|
||||
@ -1132,8 +1139,30 @@ class Logics:
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
def pause_unpause_public_order(order,user):
|
||||
if not order.maker == user:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"You cannot pause or unpause an order you did not make"
|
||||
}
|
||||
else:
|
||||
if order.status == Order.Status.PUB:
|
||||
order.status = Order.Status.PAU
|
||||
elif order.status == Order.Status.PAU:
|
||||
order.status = Order.Status.PUB
|
||||
else:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"You can only pause/unpause an order that is either public or paused"
|
||||
}
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def rate_counterparty(cls, order, user, rating):
|
||||
'''
|
||||
Not in use
|
||||
'''
|
||||
|
||||
rating_allowed_status = [
|
||||
Order.Status.PAY,
|
||||
|
@ -91,6 +91,21 @@ class Telegram():
|
||||
self.send_message(user, text)
|
||||
return
|
||||
|
||||
def fiat_exchange_starts(self, order):
|
||||
user = order.maker
|
||||
if not user.profile.telegram_enabled:
|
||||
return
|
||||
|
||||
lang = user.profile.telegram_lang_code
|
||||
site = config('HOST_NAME')
|
||||
if lang == 'es':
|
||||
text = f'El depósito de garantía y el recibo del comprador han sido recibidos. Es hora de enviar el dinero fiat. Visita http://{site}/order/{order.id} para hablar con tu contraparte.'
|
||||
else:
|
||||
text = f'The escrow and invoice have been submitted. The fiat exchange starts now via the platform chat. Visit http://{site}/order/{order.id} to talk with your counterpart.'
|
||||
|
||||
self.send_message(user, text)
|
||||
return
|
||||
|
||||
def order_expired_untaken(self, order):
|
||||
user = order.maker
|
||||
if not user.profile.telegram_enabled:
|
||||
@ -99,9 +114,9 @@ class Telegram():
|
||||
lang = user.profile.telegram_lang_code
|
||||
site = config('HOST_NAME')
|
||||
if lang == 'es':
|
||||
text = f'Tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot. Visita http://{site} para crear una nueva.'
|
||||
text = f'Tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot. Visita http://{site}/order/{order.id} para renovarla.'
|
||||
else:
|
||||
text = f'Your order with ID {order.id} has expired untaken. Visit http://{site} to create a new one.'
|
||||
text = f'Your order with ID {order.id} has expired without a taker. Visit http://{site}/order/{order.id} to renew it.'
|
||||
|
||||
self.send_message(user, text)
|
||||
return
|
||||
|
@ -162,7 +162,7 @@ class Order(models.Model):
|
||||
class Status(models.IntegerChoices):
|
||||
WFB = 0, "Waiting for maker bond"
|
||||
PUB = 1, "Public"
|
||||
DEL = 2, "Deleted"
|
||||
PAU = 2, "Paused"
|
||||
TAK = 3, "Waiting for taker bond"
|
||||
UCA = 4, "Cancelled"
|
||||
EXP = 5, "Expired"
|
||||
@ -180,12 +180,23 @@ class Order(models.Model):
|
||||
MLD = 17, "Maker lost dispute"
|
||||
TLD = 18, "Taker lost dispute"
|
||||
|
||||
class ExpiryReasons(models.IntegerChoices):
|
||||
NTAKEN = 0, "Expired not taken"
|
||||
NMBOND = 1, "Maker bond not locked"
|
||||
NESCRO = 2, "Escrow not locked"
|
||||
NINVOI = 3, "Invoice not submitted"
|
||||
NESINV = 4, "Neither escrow locked or invoice submitted"
|
||||
|
||||
# order info
|
||||
status = models.PositiveSmallIntegerField(choices=Status.choices,
|
||||
null=False,
|
||||
default=Status.WFB)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
expires_at = models.DateTimeField()
|
||||
expiry_reason = models.PositiveSmallIntegerField(choices=ExpiryReasons.choices,
|
||||
null=True,
|
||||
blank=True,
|
||||
default=None)
|
||||
|
||||
# order details
|
||||
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False)
|
||||
@ -232,6 +243,18 @@ class Order(models.Model):
|
||||
],
|
||||
blank=False,
|
||||
)
|
||||
|
||||
# optionally makers can choose the escrow lock / invoice submission step length (seconds)
|
||||
escrow_duration = models.PositiveBigIntegerField(
|
||||
default=60 * int(config("INVOICE_AND_ESCROW_DURATION"))-1,
|
||||
null=False,
|
||||
validators=[
|
||||
MinValueValidator(60*30), # Min is 30 minutes
|
||||
MaxValueValidator(60*60*8), # Max is 8 Hours
|
||||
],
|
||||
blank=False,
|
||||
)
|
||||
|
||||
# optionally makers can choose the fidelity bond size of the maker and taker (%)
|
||||
bond_size = models.DecimalField(
|
||||
max_digits=4,
|
||||
@ -354,7 +377,7 @@ class Order(models.Model):
|
||||
3: int(config("EXP_TAKER_BOND_INVOICE")), # 'Waiting for taker bond'
|
||||
4: 0, # 'Cancelled'
|
||||
5: 0, # 'Expired'
|
||||
6: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting for trade collateral and buyer invoice'
|
||||
6: self.escrow_duration, # 'Waiting for trade collateral and buyer invoice'
|
||||
7: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting only for seller trade collateral'
|
||||
8: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting only for buyer invoice'
|
||||
9: 60 * 60 * int(config("FIAT_EXCHANGE_DURATION")), # 'Sending fiat - In chatroom'
|
||||
|
@ -24,6 +24,7 @@ class ListOrderSerializer(serializers.ModelSerializer):
|
||||
"bondless_taker",
|
||||
"maker",
|
||||
"taker",
|
||||
"escrow_duration",
|
||||
)
|
||||
|
||||
|
||||
@ -43,6 +44,7 @@ class MakeOrderSerializer(serializers.ModelSerializer):
|
||||
"premium",
|
||||
"satoshis",
|
||||
"public_duration",
|
||||
"escrow_duration",
|
||||
"bond_size",
|
||||
"bondless_taker",
|
||||
)
|
||||
@ -58,6 +60,7 @@ class UpdateOrderSerializer(serializers.Serializer):
|
||||
default=None)
|
||||
action = serializers.ChoiceField(
|
||||
choices=(
|
||||
"pause",
|
||||
"take",
|
||||
"update_invoice",
|
||||
"submit_statement",
|
||||
|
@ -235,4 +235,8 @@ def send_message(order_id, message):
|
||||
|
||||
elif message == 'order_taken_confirmed':
|
||||
telegram.order_taken_confirmed(order)
|
||||
|
||||
elif message == 'fiat_exchange_starts':
|
||||
telegram.fiat_exchange_starts(order)
|
||||
|
||||
return
|
33
api/views.py
33
api/views.py
@ -31,6 +31,7 @@ from decouple import config
|
||||
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
|
||||
RETRY_TIME = int(config("RETRY_TIME"))
|
||||
PUBLIC_DURATION = 60*60*int(config("DEFAULT_PUBLIC_ORDER_DURATION"))-1
|
||||
ESCROW_DURATION = 60 * int(config("INVOICE_AND_ESCROW_DURATION"))
|
||||
BOND_SIZE = int(config("DEFAULT_BOND_SIZE"))
|
||||
|
||||
avatar_path = Path(settings.AVATAR_ROOT)
|
||||
@ -82,11 +83,13 @@ class MakerView(CreateAPIView):
|
||||
satoshis = serializer.data.get("satoshis")
|
||||
is_explicit = serializer.data.get("is_explicit")
|
||||
public_duration = serializer.data.get("public_duration")
|
||||
escrow_duration = serializer.data.get("escrow_duration")
|
||||
bond_size = serializer.data.get("bond_size")
|
||||
bondless_taker = serializer.data.get("bondless_taker")
|
||||
|
||||
# Optional params
|
||||
if public_duration == None: public_duration = PUBLIC_DURATION
|
||||
if escrow_duration == None: escrow_duration = ESCROW_DURATION
|
||||
if bond_size == None: bond_size = BOND_SIZE
|
||||
if bondless_taker == None: bondless_taker = False
|
||||
if has_range == None: has_range = False
|
||||
@ -132,6 +135,7 @@ class MakerView(CreateAPIView):
|
||||
seconds=EXP_MAKER_BOND_INVOICE),
|
||||
maker=request.user,
|
||||
public_duration=public_duration,
|
||||
escrow_duration=escrow_duration,
|
||||
bond_size=bond_size,
|
||||
bondless_taker=bondless_taker,
|
||||
)
|
||||
@ -238,9 +242,9 @@ class OrderView(viewsets.ViewSet):
|
||||
if order.status >= Order.Status.PUB and order.status < Order.Status.WF2:
|
||||
data["price_now"], data["premium_now"] = Logics.price_and_premium_now(order)
|
||||
|
||||
# 3. c) If maker and Public, add num robots in book, premium percentile
|
||||
# 3. c) If maker and Public/Paused, add premium percentile
|
||||
# num similar orders, and maker information to enable telegram notifications.
|
||||
if data["is_maker"] and order.status == Order.Status.PUB:
|
||||
if data["is_maker"] and order.status in [Order.Status.PUB, Order.Status.PAU]:
|
||||
data["premium_percentile"] = compute_premium_percentile(order)
|
||||
data["num_similar_orders"] = len(
|
||||
Order.objects.filter(currency=order.currency,
|
||||
@ -372,8 +376,7 @@ class OrderView(viewsets.ViewSet):
|
||||
and order.payout.receiver == request.user
|
||||
): # might not be the buyer if after a dispute where winner wins
|
||||
data["retries"] = order.payout.routing_attempts
|
||||
data[
|
||||
"next_retry_time"] = order.payout.last_routing_time + timedelta(
|
||||
data["next_retry_time"] = order.payout.last_routing_time + timedelta(
|
||||
minutes=RETRY_TIME)
|
||||
|
||||
if order.payout.status == LNPayment.Status.EXPIRE:
|
||||
@ -381,6 +384,15 @@ class OrderView(viewsets.ViewSet):
|
||||
# Add invoice amount once again if invoice was expired.
|
||||
data["invoice_amount"] = Logics.payout_amount(order,request.user)[1]["invoice_amount"]
|
||||
|
||||
# 10) If status is 'Expired', add expiry reason.
|
||||
elif (order.status == Order.Status.EXP):
|
||||
data["expiry_reason"] = order.expiry_reason
|
||||
data["expiry_message"] = Order.ExpiryReasons(order.expiry_reason).label
|
||||
# other pieces of info useful to renew an identical order
|
||||
data["public_duration"] = order.public_duration
|
||||
data["bond_size"] = order.bond_size
|
||||
data["bondless_taker"] = order.bondless_taker
|
||||
|
||||
return Response(data, status.HTTP_200_OK)
|
||||
|
||||
def take_update_confirm_dispute_cancel(self, request, format=None):
|
||||
@ -390,10 +402,6 @@ class OrderView(viewsets.ViewSet):
|
||||
"""
|
||||
order_id = request.GET.get(self.lookup_url_kwarg)
|
||||
|
||||
import sys
|
||||
sys.stdout.write('AAAAAA')
|
||||
print('BBBBB1')
|
||||
|
||||
serializer = UpdateOrderSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -481,12 +489,18 @@ class OrderView(viewsets.ViewSet):
|
||||
if not valid:
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 6) If action is rate_platform
|
||||
# 7) If action is rate_platform
|
||||
elif action == "rate_platform" and rating:
|
||||
valid, context = Logics.rate_platform(request.user, rating)
|
||||
if not valid:
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 8) If action is rate_platform
|
||||
elif action == "pause":
|
||||
valid, context = Logics.pause_unpause_public_order(order, request.user)
|
||||
if not valid:
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# If nothing of the above... something else is going on. Probably not allowed!
|
||||
else:
|
||||
return Response(
|
||||
@ -824,6 +838,7 @@ class LimitView(ListAPIView):
|
||||
exchange_rate = float(currency.exchange_rate)
|
||||
payload[currency.currency] = {
|
||||
'code': code,
|
||||
'price': exchange_rate,
|
||||
'min_amount': min_trade * exchange_rate,
|
||||
'max_amount': max_trade * exchange_rate,
|
||||
'max_bondless_amount': max_bondless_trade * exchange_rate,
|
||||
|
16219
frontend/package-lock.json
generated
16219
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"babel-loader": "^8.2.3",
|
||||
"jest": "^27.5.1",
|
||||
"openpgp": "^5.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"webpack": "^5.65.0",
|
||||
|
@ -25,7 +25,7 @@ class BookPage extends Component {
|
||||
loading: true,
|
||||
pageSize: 6,
|
||||
};
|
||||
this.getOrderDetails(2, this.props.currency)
|
||||
this.getOrderDetails(2, 0)
|
||||
}
|
||||
|
||||
getOrderDetails(type, currency) {
|
||||
@ -158,7 +158,7 @@ class BookPage extends Component {
|
||||
),
|
||||
NoResultsOverlay: () => (
|
||||
<Stack height="100%" alignItems="center" justifyContent="center">
|
||||
{t("Local filter returns no result")}
|
||||
{t("Filter has no results")}
|
||||
</Stack>
|
||||
)
|
||||
}}
|
||||
@ -328,7 +328,7 @@ class BookPage extends Component {
|
||||
return (
|
||||
<Grid className='orderBook' container spacing={1} sx={{minWidth:400}}>
|
||||
|
||||
<IconButton sx={{position:'fixed',right:'0px', top:'30px'}} onClick={()=>this.setState({loading: true}) & this.getOrderDetails(this.props.type, this.props.currency)}>
|
||||
<IconButton sx={{position:'fixed',right:'0px', top:'30px'}} onClick={()=>this.setState({loading: true}) & this.getOrderDetails(2, 0)}>
|
||||
<RefreshIcon/>
|
||||
</IconButton>
|
||||
|
||||
@ -341,7 +341,15 @@ class BookPage extends Component {
|
||||
<div style={{position:"relative", left:"20px"}}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox defaultChecked={true} icon={<MoveToInboxIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<MoveToInboxIcon sx={{width:"30px",height:"30px"}} color="primary"/>}/>}
|
||||
label={<div style={{position:"relative",top:"-13px"}}><Typography style={{color:"#666666"}} variant="caption">{t("Buy")}</Typography></div>}
|
||||
label={
|
||||
<div style={{position:"relative",top:"-13px"}}>
|
||||
{this.props.buyChecked ?
|
||||
<Typography variant="caption" color="primary"><b>{t("Buy")}</b></Typography>
|
||||
:
|
||||
<Typography variant="caption" color="text.secondary">{t("Buy")}</Typography>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
labelPlacement="bottom"
|
||||
checked={this.props.buyChecked}
|
||||
onChange={this.handleClickBuy}
|
||||
@ -349,7 +357,15 @@ class BookPage extends Component {
|
||||
</div>
|
||||
<FormControlLabel
|
||||
control={<Checkbox defaultChecked={true} icon={<OutboxIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<OutboxIcon sx={{width:"30px",height:"30px"}} color="secondary"/>}/>}
|
||||
label={<div style={{position:"relative",top:"-13px"}}><Typography style={{color:"#666666"}} variant="caption">{t("Sell")}</Typography></div>}
|
||||
label={
|
||||
<div style={{position:"relative",top:"-13px"}}>
|
||||
{this.props.sellChecked ?
|
||||
<Typography variant="caption" color="secondary"><b>{t("Sell")}</b></Typography>
|
||||
:
|
||||
<Typography variant="caption" color="text.secondary">{t("Sell")}</Typography>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
labelPlacement="bottom"
|
||||
checked={this.props.sellChecked}
|
||||
onChange={this.handleClickSell}
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import { withTranslation, Trans} from "react-i18next";
|
||||
import {Button, Link, Badge, TextField, Grid, Container, Card, CardHeader, Paper, Avatar, FormHelperText, Typography} from "@mui/material";
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket';
|
||||
import * as openpgp from 'openpgp/lightweight';
|
||||
|
||||
class Chat extends Component {
|
||||
constructor(props) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withTranslation, Trans} from "react-i18next";
|
||||
import { InputAdornment, LinearProgress, Link, Checkbox, Slider, Box, Tab, Tabs, SliderThumb, Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material"
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { InputAdornment, LinearProgress, Accordion, AccordionDetails, AccordionSummary, Checkbox, Slider, Box, Tab, Tabs, SliderThumb, Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material"
|
||||
import { LocalizationProvider, TimePicker} from '@mui/lab';
|
||||
import DateFnsUtils from "@date-io/date-fns";
|
||||
import { Link as LinkRouter } from 'react-router-dom'
|
||||
@ -15,7 +15,7 @@ import MoveToInboxIcon from '@mui/icons-material/MoveToInbox';
|
||||
import OutboxIcon from '@mui/icons-material/Outbox';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import HourglassTopIcon from '@mui/icons-material/HourglassTop';
|
||||
import DoubleArrowIcon from '@mui/icons-material/DoubleArrow';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
import { getCookie } from "../utils/cookies";
|
||||
import { pn } from "../utils/prettyNumbers";
|
||||
@ -45,14 +45,16 @@ class MakerPage extends Component {
|
||||
showAdvanced: false,
|
||||
allowBondless: false,
|
||||
publicExpiryTime: new Date(0, 0, 0, 23, 59),
|
||||
escrowExpiryTime: new Date(0, 0, 0, 3, 0),
|
||||
enableAmountRange: false,
|
||||
minAmount: null,
|
||||
bondSize: 1,
|
||||
limits: null,
|
||||
minAmount: null,
|
||||
maxAmount: null,
|
||||
loadingLimits: false,
|
||||
loadingLimits: true,
|
||||
}
|
||||
this.getLimits()
|
||||
}
|
||||
|
||||
getLimits() {
|
||||
@ -219,6 +221,7 @@ class MakerPage extends Component {
|
||||
premium: this.state.is_explicit ? null: this.state.premium,
|
||||
satoshis: this.state.is_explicit ? this.state.satoshis: null,
|
||||
public_duration: this.state.publicDuration,
|
||||
escrow_duration: this.state.escrowDuration,
|
||||
bond_size: this.state.bondSize,
|
||||
bondless_taker: this.state.allowBondless,
|
||||
}),
|
||||
@ -237,6 +240,20 @@ class MakerPage extends Component {
|
||||
this.setState({bondSize: event.target.value === '' ? 1 : Number(event.target.value)});
|
||||
};
|
||||
|
||||
priceNow = () => {
|
||||
if (this.state.loadingLimits){
|
||||
return "...";
|
||||
}
|
||||
else if (this.state.is_explicit & this.state.amount > 0 & this.state.satoshis > 0){
|
||||
return parseFloat(Number(this.state.amount / (this.state.satoshis/100000000)).toPrecision(5));
|
||||
}
|
||||
else if (!this.state.is_explicit){
|
||||
var price = this.state.limits[this.state.currency]['price'];
|
||||
return parseFloat(Number(price*(1+this.state.premium/100)).toPrecision(5));
|
||||
}
|
||||
return "...";
|
||||
}
|
||||
|
||||
StandardMakerOptions = () => {
|
||||
const { t } = this.props;
|
||||
return(
|
||||
@ -244,38 +261,21 @@ class MakerPage extends Component {
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<div style={{position:'relative', left:'5px'}}>
|
||||
<FormControl component="fieldset">
|
||||
<FormHelperText sx={{align:"center"}}>
|
||||
<FormHelperText sx={{textAlign:"center"}}>
|
||||
{t("Buy or Sell Bitcoin?")}
|
||||
</FormHelperText>
|
||||
{/* <RadioGroup row>
|
||||
<div style={{position:"relative", left:"20px"}}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox defaultChecked={true} icon={<MoveToInboxIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<MoveToInboxIcon sx={{width:"30px",height:"30px"}} color="primary"/>}/>}
|
||||
label={<div style={{position:"relative",top:"-13px"}}><Typography style={{color:"#666666"}} variant="caption">{t("Buy")}</Typography></div>}
|
||||
labelPlacement="bottom"
|
||||
checked={this.state.buyChecked}
|
||||
onChange={this.handleClickBuy}
|
||||
/>
|
||||
</div>
|
||||
<FormControlLabel
|
||||
control={<Checkbox defaultChecked={true} icon={<OutboxIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<OutboxIcon sx={{width:"30px",height:"30px"}} color="secondary"/>}/>}
|
||||
label={<div style={{position:"relative",top:"-13px"}}><Typography style={{color:"#666666"}} variant="caption">{t("Sell")}</Typography></div>}
|
||||
labelPlacement="bottom"
|
||||
checked={this.state.sellChecked}
|
||||
onChange={this.handleClickSell}
|
||||
/> */}
|
||||
|
||||
<RadioGroup row defaultValue="0" onChange={this.handleTypeChange}>
|
||||
<FormControlLabel
|
||||
value="0"
|
||||
control={<Radio icon={<MoveToInboxIcon sx={{width:"26px",height:"26px"}} color="inherit"/>} checkedIcon={<MoveToInboxIcon sx={{width:"26px",height:"26px"}} color="primary"/>}/>}
|
||||
label={t("Buy")}
|
||||
label={this.state.type == 0 ? <Typography color="primary"><b>{t("Buy")}</b></Typography>: <Typography color="text.secondary">{t("Buy")}</Typography>}
|
||||
labelPlacement="end"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="1"
|
||||
control={<Radio icon={<OutboxIcon sx={{width:"26px",height:"26px"}} color="inherit"/>} checkedIcon={<OutboxIcon sx={{width:"26px",height:"26px"}} color="secondary"/>}/>}
|
||||
label={t("Sell")}
|
||||
label={this.state.type == 1 ? <Typography color="secondary"><b>{t("Sell")}</b></Typography>: <Typography color="text.secondary">{t("Sell")}</Typography>}
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</RadioGroup>
|
||||
@ -398,6 +398,13 @@ class MakerPage extends Component {
|
||||
onChange={this.handlePremiumChange}
|
||||
/>
|
||||
</div>
|
||||
<Grid item>
|
||||
<Tooltip placement="top" enterTouchDelay="0" enterDelay="1000" enterNextDelay="2000" title={this.state.is_explicit? t("Your order fixed exchange rate"): t("Your order's current exchange rate. Rate will move with the market.")}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{(this.state.is_explicit ? t("Order rate:"): t("Order current rate:"))+" "+pn(this.priceNow())+" "+this.state.currencyCode+"/BTC"}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
@ -411,12 +418,22 @@ class MakerPage extends Component {
|
||||
var total_secs = hours*60*60 + minutes * 60;
|
||||
|
||||
this.setState({
|
||||
changedPublicExpiryTime: true,
|
||||
publicExpiryTime: date,
|
||||
publicDuration: total_secs,
|
||||
badDuration: false,
|
||||
});
|
||||
}
|
||||
|
||||
handleChangeEscrowDuration = (date) => {
|
||||
let d = new Date(date),
|
||||
hours = d.getHours(),
|
||||
minutes = d.getMinutes();
|
||||
|
||||
var total_secs = hours*60*60 + minutes * 60;
|
||||
|
||||
this.setState({
|
||||
escrowExpiryTime: date,
|
||||
escrowDuration: total_secs,
|
||||
});
|
||||
}
|
||||
|
||||
getMaxAmount = () => {
|
||||
@ -530,7 +547,7 @@ class MakerPage extends Component {
|
||||
<FormHelperText>
|
||||
<Tooltip enterTouchDelay="0" placement="top" align="center" title={t("Let the taker chose an amount within the range")}>
|
||||
<div align="center" style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>
|
||||
<Checkbox onChange={(e)=>this.setState({enableAmountRange:e.target.checked, is_explicit: false}) & (e.target.checked ? this.getLimits() : null)}/>
|
||||
<Checkbox onChange={(e)=>this.setState({enableAmountRange:e.target.checked, is_explicit: false})}/>
|
||||
{this.state.enableAmountRange & this.state.minAmount != null? <this.rangeText/> : t("Enable Amount Range")}
|
||||
</div>
|
||||
</Tooltip>
|
||||
@ -562,34 +579,75 @@ class MakerPage extends Component {
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<LocalizationProvider dateAdapter={DateFnsUtils}>
|
||||
<TimePicker
|
||||
sx={{width:210, align:"center"}}
|
||||
ampm={false}
|
||||
openTo="hours"
|
||||
views={['hours', 'minutes']}
|
||||
inputFormat="HH:mm"
|
||||
mask="__:__"
|
||||
components={{
|
||||
OpenPickerIcon: HourglassTopIcon
|
||||
}}
|
||||
open={this.state.openTimePicker}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<HourglassTopIcon />
|
||||
</InputAdornment>)
|
||||
}}
|
||||
renderInput={(props) => <TextField {...props} />}
|
||||
label={t("Public Duration (HH:mm)")}
|
||||
value={this.state.publicExpiryTime}
|
||||
onChange={this.handleChangePublicDuration}
|
||||
minTime={new Date(0, 0, 0, 0, 10)}
|
||||
maxTime={new Date(0, 0, 0, 23, 59)}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
<Accordion elevation={0} sx={{width:'280px', position:'relative', left:'-12px'}}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon color="primary"/>}>
|
||||
<Typography sx={{flexGrow: 1, textAlign: "center"}} color="text.secondary">{t("Expiry Timers")}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container xs={12} spacing={1}>
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<LocalizationProvider dateAdapter={DateFnsUtils}>
|
||||
<TimePicker
|
||||
sx={{width:210, align:"center"}}
|
||||
ampm={false}
|
||||
openTo="hours"
|
||||
views={['hours', 'minutes']}
|
||||
inputFormat="HH:mm"
|
||||
mask="__:__"
|
||||
components={{
|
||||
OpenPickerIcon: HourglassTopIcon
|
||||
}}
|
||||
open={this.state.openTimePicker}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<HourglassTopIcon />
|
||||
</InputAdornment>)
|
||||
}}
|
||||
renderInput={(props) => <TextField {...props} />}
|
||||
label={t("Public Duration (HH:mm)")}
|
||||
value={this.state.publicExpiryTime}
|
||||
onChange={this.handleChangePublicDuration}
|
||||
minTime={new Date(0, 0, 0, 0, 10)}
|
||||
maxTime={new Date(0, 0, 0, 23, 59)}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<LocalizationProvider dateAdapter={DateFnsUtils}>
|
||||
<TimePicker
|
||||
sx={{width:210, align:"center"}}
|
||||
ampm={false}
|
||||
openTo="hours"
|
||||
views={['hours', 'minutes']}
|
||||
inputFormat="HH:mm"
|
||||
mask="__:__"
|
||||
components={{
|
||||
OpenPickerIcon: HourglassTopIcon
|
||||
}}
|
||||
open={this.state.openTimePicker}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<HourglassTopIcon />
|
||||
</InputAdornment>)
|
||||
}}
|
||||
renderInput={(props) => <TextField {...props} />}
|
||||
label={t("Escrow Deposit Time-Out (HH:mm)")}
|
||||
value={this.state.escrowExpiryTime}
|
||||
onChange={this.handleChangeEscrowDuration}
|
||||
minTime={new Date(0, 0, 0, 1, 0)}
|
||||
maxTime={new Date(0, 0, 0, 8, 0)}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Grid>
|
||||
|
||||
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<FormControl align="center">
|
||||
<Tooltip enterDelay="800" enterTouchDelay="0" placement="top" title={t("Set the skin-in-the-game, increase for higher safety assurance")}>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react";
|
||||
import { withTranslation, Trans} from "react-i18next";
|
||||
import { withTranslation} from "react-i18next";
|
||||
import {TextField,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'
|
||||
@ -17,6 +17,7 @@ import PriceChangeIcon from '@mui/icons-material/PriceChange';
|
||||
import PaymentsIcon from '@mui/icons-material/Payments';
|
||||
import ArticleIcon from '@mui/icons-material/Article';
|
||||
import DoubleArrowIcon from '@mui/icons-material/DoubleArrow';
|
||||
import HourglassTopIcon from '@mui/icons-material/HourglassTop';
|
||||
|
||||
import { getCookie } from "../utils/cookies";
|
||||
import { pn } from "../utils/prettyNumbers";
|
||||
@ -33,15 +34,15 @@ class OrderPage extends Component {
|
||||
openCollaborativeCancel: false,
|
||||
openInactiveMaker: false,
|
||||
showContractBox: 1,
|
||||
orderId: this.props.match.params.orderId,
|
||||
};
|
||||
this.orderId = this.props.match.params.orderId;
|
||||
this.getOrderDetails();
|
||||
this.getOrderDetails(this.props.match.params.orderId);
|
||||
|
||||
// Refresh delays according to Order status
|
||||
this.statusToDelay = {
|
||||
"0": 2000, //'Waiting for maker bond'
|
||||
"1": 25000, //'Public'
|
||||
"2": 999999, //'Deleted'
|
||||
"2": 90000, //'Paused'
|
||||
"3": 2000, //'Waiting for taker bond'
|
||||
"4": 999999, //'Cancelled'
|
||||
"5": 999999, //'Expired'
|
||||
@ -67,6 +68,7 @@ class OrderPage extends Component {
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -83,11 +85,12 @@ class OrderPage extends Component {
|
||||
this.setState(completeStateVars);
|
||||
}
|
||||
|
||||
getOrderDetails() {
|
||||
getOrderDetails =(id)=> {
|
||||
this.setState(null)
|
||||
fetch('/api/order' + '?order_id=' + this.orderId)
|
||||
this.setState({orderId:id})
|
||||
fetch('/api/order' + '?order_id=' + id)
|
||||
.then((response) => response.json())
|
||||
.then((data) => this.completeSetState(data));
|
||||
.then((data) => (this.completeSetState(data) & this.setState({pauseLoading:false})));
|
||||
}
|
||||
|
||||
// These are used to refresh the data
|
||||
@ -103,7 +106,7 @@ class OrderPage extends Component {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
tick = () => {
|
||||
this.getOrderDetails();
|
||||
this.getOrderDetails(this.state.orderId);
|
||||
}
|
||||
|
||||
// Countdown Renderer callback with condition
|
||||
@ -128,6 +131,14 @@ class OrderPage extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
timerRenderer(seconds){
|
||||
var hours = parseInt(seconds/3600);
|
||||
var 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;
|
||||
@ -267,7 +278,7 @@ class OrderPage extends Component {
|
||||
'amount':this.state.takeAmount,
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
|
||||
fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => this.completeSetState(data));
|
||||
}
|
||||
@ -291,9 +302,9 @@ class OrderPage extends Component {
|
||||
'action':'cancel',
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
|
||||
fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => this.getOrderDetails(data.id));
|
||||
.then(() => (this.getOrderDetails(this.state.orderId) & this.setState({status:4})));
|
||||
this.handleClickCloseConfirmCancelDialog();
|
||||
}
|
||||
|
||||
@ -368,9 +379,9 @@ class OrderPage extends Component {
|
||||
'action':'cancel',
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
|
||||
fetch('/api/order/' + '?order_id=' + this.state.state.orderId, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => this.getOrderDetails(data.id));
|
||||
.then(() => (this.getOrderDetails(this.state.orderId) & this.setState({status:4})));
|
||||
this.handleClickCloseCollaborativeCancelDialog();
|
||||
}
|
||||
|
||||
@ -531,10 +542,10 @@ class OrderPage extends Component {
|
||||
</div>
|
||||
</ListItemIcon>
|
||||
{this.state.has_range & this.state.amount == null ?
|
||||
<ListItemText primary={parseFloat(Number(this.state.min_amount).toPrecision(2))
|
||||
+"-" + parseFloat(Number(this.state.max_amount).toPrecision(2)) +" "+this.state.currencyCode} secondary={t("Amount range")}/>
|
||||
<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={parseFloat(parseFloat(this.state.amount).toFixed(4))
|
||||
<ListItemText primary={pn(parseFloat(parseFloat(this.state.amount).toFixed(4)))
|
||||
+" "+this.state.currencyCode} secondary={t("Amount")}/>
|
||||
}
|
||||
|
||||
@ -566,12 +577,30 @@ class OrderPage extends Component {
|
||||
</ListItem>
|
||||
<Divider />
|
||||
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<ListItem >
|
||||
<ListItemIcon>
|
||||
<NumbersIcon/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={this.orderId} secondary={t("Order ID")}/>
|
||||
<Grid container xs={12}>
|
||||
<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>
|
||||
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
@ -652,7 +681,7 @@ class OrderPage extends Component {
|
||||
{this.orderBox()}
|
||||
</Grid>
|
||||
<Grid item xs={6} align="left">
|
||||
<TradeBox push={this.props.history.push} width={330} data={this.state} completeSetState={this.completeSetState} />
|
||||
<TradeBox push={this.props.history.push} getOrderDetails={this.getOrderDetails} pauseLoading={this.state.pauseLoading} width={330} data={this.state} completeSetState={this.completeSetState} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
@ -688,7 +717,7 @@ class OrderPage extends Component {
|
||||
{this.orderBox()}
|
||||
</div>
|
||||
<div style={{display: this.state.showContractBox == 1 ? '':'none'}}>
|
||||
<TradeBox push={this.props.history.push} width={330} data={this.state} completeSetState={this.completeSetState} />
|
||||
<TradeBox push={this.props.history.push} getOrderDetails={this.getOrderDetails} pauseLoading={this.state.pauseLoading} width={330} data={this.state} completeSetState={this.completeSetState} />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -16,9 +16,12 @@ import LockIcon from '@mui/icons-material/Lock';
|
||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||
import BalanceIcon from '@mui/icons-material/Balance';
|
||||
import ContentCopy from "@mui/icons-material/ContentCopy";
|
||||
import PauseCircleIcon from '@mui/icons-material/PauseCircle';
|
||||
import PlayCircleIcon from '@mui/icons-material/PlayCircle';
|
||||
|
||||
import { getCookie } from "../utils/cookies";
|
||||
import { pn } from "../utils/prettyNumbers";
|
||||
import { t } from "i18next";
|
||||
|
||||
class TradeBox extends Component {
|
||||
invoice_escrow_duration = 3;
|
||||
@ -61,7 +64,7 @@ class TradeBox extends Component {
|
||||
if(this.props.data.is_maker){
|
||||
if (status == 0){
|
||||
x = 1
|
||||
} else if ([1,3].includes(status)){
|
||||
} else if ([1,2,3].includes(status)){
|
||||
x = 2
|
||||
} else if ([6,7,8].includes(status)){
|
||||
x = 3
|
||||
@ -358,6 +361,26 @@ class TradeBox extends Component {
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
depositHoursMinutes=()=>{
|
||||
var hours = parseInt(this.props.data.escrow_duration/3600);
|
||||
var minutes = parseInt((this.props.data.escrow_duration-hours*3600)/60);
|
||||
var dict = {deposit_timer_hours:hours, deposit_timer_minutes:minutes}
|
||||
return dict
|
||||
}
|
||||
|
||||
handleClickPauseOrder=()=>{
|
||||
this.props.completeSetState({pauseLoading:true})
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||
body: JSON.stringify({
|
||||
'action': "pause",
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => (this.props.getOrderDetails(data.id)));
|
||||
}
|
||||
|
||||
showMakerWait=()=>{
|
||||
const { t } = this.props;
|
||||
@ -377,10 +400,11 @@ class TradeBox extends Component {
|
||||
<Divider/>
|
||||
<ListItem>
|
||||
<Typography component="body2" variant="body2" align="left">
|
||||
<p>{t("Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{invoice_escrow_duration}} hours to reply. If you do not reply, you risk losing your bond.", {invoice_escrow_duration: pn(this.invoice_escrow_duration)})} </p>
|
||||
<p>{t("Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{deposit_timer_hours}}h {{deposit_timer_minutes}}m hours to reply. If you do not reply, you risk losing your bond.", this.depositHoursMinutes() )} </p>
|
||||
<p>{t("If the order expires untaken, your bond will return to you (no action needed).")}</p>
|
||||
</Typography>
|
||||
</ListItem>
|
||||
|
||||
<Grid item xs={12} align="center">
|
||||
{this.props.data.tg_enabled ?
|
||||
<Typography color='primary' component="h6" variant="h6" align="center">{t("Telegram enabled")}</Typography>
|
||||
@ -391,13 +415,32 @@ class TradeBox extends Component {
|
||||
}
|
||||
</Grid>
|
||||
<Divider/>
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<BookIcon/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={this.props.data.num_similar_orders} secondary={t("Public orders for {{currencyCode}}",{currencyCode: this.props.data.currencyCode})}/>
|
||||
</ListItem>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={10}>
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<BookIcon/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={this.props.data.num_similar_orders} secondary={t("Public orders for {{currencyCode}}",{currencyCode: this.props.data.currencyCode})}/>
|
||||
</ListItem>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={2}>
|
||||
<div style={{position:"relative", top:"7px", right:"14px"}}>
|
||||
{this.props.pauseLoading ?
|
||||
<CircularProgress sx={{width:"30px",height:"30px"}}/>
|
||||
:
|
||||
<Tooltip placement="top" enterTouchDelay="500" enterDelay="700" enterNextDelay="2000" title={t("Pause the public order")}>
|
||||
<Button color="primary" onClick={this.handleClickPauseOrder}>
|
||||
<PauseCircleIcon sx={{width:"36px",height:"36px"}}/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Divider/>
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
@ -415,6 +458,44 @@ class TradeBox extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
showPausedOrder=()=>{
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Grid container align="center" spacing={1}>
|
||||
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="subtitle1" variant="subtitle1">
|
||||
<b> {t("Your order is paused")} </b> {" " + this.stepXofY()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} align="center">
|
||||
<List dense="true">
|
||||
<Divider/>
|
||||
<ListItem>
|
||||
<Typography component="body2" variant="body2" align="left">
|
||||
{t("Your public order has been paused. At the moment it cannot be seen or taken by other robots. You can choose to unpause it at any time.")}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
|
||||
<Grid item xs={12} align="center">
|
||||
{this.props.pauseLoading ?
|
||||
<CircularProgress/>
|
||||
:
|
||||
<Button color="primary" onClick={this.handleClickPauseOrder}>
|
||||
<PlayCircleIcon sx={{width:"36px",height:"36px"}}/>{t("Unpause Order")}
|
||||
</Button>
|
||||
}
|
||||
</Grid>
|
||||
|
||||
<Divider/>
|
||||
</List>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
handleInputInvoiceChanged=(e)=>{
|
||||
this.setState({
|
||||
invoice: e.target.value,
|
||||
@ -773,8 +854,41 @@ handleRatingRobosatsChange=(e)=>{
|
||||
)
|
||||
}
|
||||
|
||||
showOrderExpired(){
|
||||
handleRenewOrderButtonPressed=()=>{
|
||||
this.setState({renewLoading:true})
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')},
|
||||
body: JSON.stringify({
|
||||
type: this.props.data.type,
|
||||
currency: this.props.data.currency,
|
||||
amount: this.props.data.has_range ? null : this.props.data.amount,
|
||||
has_range: this.props.data.has_range,
|
||||
min_amount: this.props.data.min_amount,
|
||||
max_amount: this.props.data.max_amount,
|
||||
payment_method: this.props.data.payment_method,
|
||||
is_explicit: this.props.data.is_explicit,
|
||||
premium: this.props.data.is_explicit ? null: this.props.data.premium,
|
||||
satoshis: this.props.data.is_explicit ? this.props.data.satoshis: null,
|
||||
public_duration: this.props.data.public_duration,
|
||||
escrow_duration: this.props.data.escrow_duration,
|
||||
bond_size: this.props.data.bond_size,
|
||||
bondless_taker: this.props.data.bondless_taker,
|
||||
}),
|
||||
};
|
||||
fetch("/api/make/",requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => (this.setState({badRequest:data.bad_request})
|
||||
& (data.id ? this.props.push('/order/' + data.id)
|
||||
& this.props.getOrderDetails(data.id)
|
||||
:"")
|
||||
));
|
||||
}
|
||||
|
||||
showOrderExpired=()=>{
|
||||
const { t } = this.props;
|
||||
var show_renew = this.props.data.is_maker;
|
||||
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
@ -782,6 +896,22 @@ handleRatingRobosatsChange=(e)=>{
|
||||
<b>{t("The order has expired")}</b>
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="body2" variant="body2">
|
||||
{t(this.props.data.expiry_message)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{show_renew ?
|
||||
<Grid item xs={12} align="center">
|
||||
{this.state.renewLoading ?
|
||||
<CircularProgress/>
|
||||
:
|
||||
<Button variant='contained' color='primary' onClick={this.handleRenewOrderButtonPressed}>{t("Renew Order")}</Button>
|
||||
}
|
||||
</Grid>
|
||||
: null}
|
||||
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@ -1023,6 +1153,7 @@ handleRatingRobosatsChange=(e)=>{
|
||||
{this.props.data.is_taker & this.props.data.status == 3 ? this.showQRInvoice() : ""}
|
||||
|
||||
{/* Waiting for taker and taker bond request */}
|
||||
{this.props.data.is_maker & this.props.data.status == 2 ? this.showPausedOrder() : ""}
|
||||
{this.props.data.is_maker & this.props.data.status == 1 ? this.showMakerWait() : ""}
|
||||
{this.props.data.is_maker & this.props.data.status == 3 ? this.showTakerFound() : ""}
|
||||
|
||||
|
@ -42,11 +42,17 @@
|
||||
"Explicit":"Explicit",
|
||||
"Set a fix amount of satoshis":"Set a fix amount of satoshis",
|
||||
"Satoshis":"Satoshis",
|
||||
"Fixed price:":"Fixed price:",
|
||||
"Order current rate:":"Order current rate:",
|
||||
"Your order fixed exchange rate":"Your order fixed exchange rate",
|
||||
"Your order's current exchange rate. Rate will move with the market.":"Your order's current exchange rate. Rate will move with the market.",
|
||||
"Let the taker chose an amount within the range":"Let the taker choose an amount within the range",
|
||||
"Enable Amount Range":"Enable Amount Range",
|
||||
"From": "From",
|
||||
"to":"to",
|
||||
"Expiry Timers":"Expiry Timers",
|
||||
"Public Duration (HH:mm)":"Public Duration (HH:mm)",
|
||||
"Escrow Deposit Time-Out (HH:mm)":"Escrow Deposit Time-Out (HH:mm)",
|
||||
"Set the skin-in-the-game, increase for higher safety assurance":"Set the skin-in-the-game, increase for higher safety assurance",
|
||||
"Fidelity Bond Size":"Fidelity Bond Size",
|
||||
"Allow bondless takers":"Allow bondless takers",
|
||||
@ -64,7 +70,6 @@
|
||||
"Must be more than {{min}}%":"Must be more than {{min}}%",
|
||||
"Must be less than {{maxSats}": "Must be less than {{maxSats}}",
|
||||
"Must be more than {{minSats}}": "Must be more than {{minSats}}",
|
||||
|
||||
|
||||
"PAYMENT METHODS - autocompletePayments.js": "Payment method strings",
|
||||
"not specified":"Not specified",
|
||||
@ -200,6 +205,7 @@
|
||||
"Amount of Satoshis":"Amount of Satoshis",
|
||||
"Premium over market price":"Premium over market price",
|
||||
"Order ID":"Order ID",
|
||||
"Deposit timer":"Deposit timer",
|
||||
"Expires in":"Expires in",
|
||||
"{{nickname}} is asking for a collaborative cancel":"{{nickname}} is asking for a collaborative cancel",
|
||||
"You asked for a collaborative cancellation":"You asked for a collaborative cancellation",
|
||||
@ -259,7 +265,7 @@
|
||||
"Your maker bond was unlocked":"Your maker bond was unlocked",
|
||||
"Your taker bond was unlocked":"Your taker bond was unlocked",
|
||||
"Your order is public":"Your order is public",
|
||||
"Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{invoice_escrow_duration}} hours to reply. If you do not reply, you risk losing your bond.":"Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{invoice_escrow_duration}} hours to reply. If you do not reply, you risk losing your bond.",
|
||||
"Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{deposit_timer_hours}}h {{deposit_timer_minutes}}m hours to reply. If you do not reply, you risk losing your bond.":"Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{deposit_timer_hours}}h {{deposit_timer_minutes}}m hours to reply. If you do not reply, you risk losing your bond.",
|
||||
"If the order expires untaken, your bond will return to you (no action needed).":"If the order expires untaken, your bond will return to you (no action needed).",
|
||||
"Enable Telegram Notifications":"Enable Telegram Notifications",
|
||||
"Enable TG Notifications":"Enable TG Notifications",
|
||||
@ -325,6 +331,13 @@
|
||||
"You can claim the dispute resolution amount (escrow and fidelity bond) from your profile rewards. If there is anything the staff can help with, do not hesitate to contact to robosats@protonmail.com (or via your provided burner contact method).":"You can claim the dispute resolution amount (escrow and fidelity bond) from your profile rewards. If there is anything the staff can help with, do not hesitate to contact to robosats@protonmail.com (or via your provided burner contact method).",
|
||||
"You have lost the dispute":"You have lost the dispute",
|
||||
"Unfortunately you have lost the dispute. If you think this is a mistake you can ask to re-open the case via email to robosats@protonmail.com. However, chances of it being investigated again are low.":"Unfortunately you have lost the dispute. If you think this is a mistake you can ask to re-open the case via email to robosats@protonmail.com. However, chances of it being investigated again are low.",
|
||||
"Expired not taken":"Expired not taken",
|
||||
"Maker bond not locked":"Maker bond not locked",
|
||||
"Escrow not locked":"Escrow not locked",
|
||||
"Invoice not submitted":"Invoice not submitted",
|
||||
"Neither escrow locked or invoice submitted":"Neither escrow locked or invoice submitted",
|
||||
"Renew Order":"Renew Order",
|
||||
|
||||
|
||||
"INFO DIALOG - InfoDiagog.js":"App information and clarifications and terms of use",
|
||||
"Close":"Close",
|
||||
|
@ -23,7 +23,7 @@
|
||||
"MAKER PAGE - MakerPage.js": "This is the page where users can create new orders",
|
||||
"Order":"Orden",
|
||||
"Customize":"Personalizar",
|
||||
"Buy or Sell Bitcoin?":"¿Comprar o Vender Bitcoin?",
|
||||
"Buy or Sell Bitcoin?":"¿Comprar o vender bitcoin?",
|
||||
"Buy":"Comprar",
|
||||
"Sell":"Vender",
|
||||
"Amount":"Monto",
|
||||
@ -31,22 +31,28 @@
|
||||
"Invalid":"No válido",
|
||||
"Enter your preferred fiat payment methods. Fast methods are highly recommended.": "Introduce tus métodos de pago. Se recomiendan encarecidamente métodos rápidos.",
|
||||
"Must be shorter than 65 characters":"Debe tener menos de 65 caracteres",
|
||||
"Swap Destination(s)": "Destino(s) del Swap",
|
||||
"Fiat Payment Method(s)":"Método(s) de Pago en Fiat",
|
||||
"Swap Destination(s)": "Destino(s) del fwap",
|
||||
"Fiat Payment Method(s)":"Método(s) de pago en fiat",
|
||||
"You can add new methods":"Puedes añadir nuevos métodos",
|
||||
"Add New":"Añadir nuevo",
|
||||
"Choose a Pricing Method":"Elige Cómo Establecer el Precio",
|
||||
"Choose a Pricing Method":"Elige cómo establecer el precio",
|
||||
"Relative":"Relativo",
|
||||
"Let the price move with the market":"EL precio se moverá relativo al mercado",
|
||||
"Premium over Market (%)": "Prima sobre el mercado (%)",
|
||||
"Explicit":"Fijo",
|
||||
"Set a fix amount of satoshis": "Establece un monto fijo de Sats",
|
||||
"Satoshis": "Satoshis",
|
||||
"Fixed price:":"Precio fijo:",
|
||||
"Order current rate:":"Precio actual:",
|
||||
"Your order fixed exchange rate":"La tasa de cambio fija de tu orden",
|
||||
"Your order's current exchange rate. Rate will move with the market.":"La tasa de cambio de tu orden justo en estos momentos. Se moverá relativo al mercado.",
|
||||
"Let the taker chose an amount within the range":"Permite que el tomador elija un monto dentro del rango.",
|
||||
"Enable Amount Range":"Activar Monto con Rango",
|
||||
"Enable Amount Range":"Activar monto con rngo",
|
||||
"From": "Desde",
|
||||
"to":"a ",
|
||||
"Expiry Timers":"Temporizadores",
|
||||
"Public Duration (HH:mm)": "Duración pública (HH:mm)",
|
||||
"Escrow Deposit Time-Out (HH:mm)":"Plazo límite depósito (HH:mm)",
|
||||
"Set the skin-in-the-game, increase for higher safety assurance": "Establece la implicación requerida (aumentar para mayor seguridad)",
|
||||
"Fidelity Bond Size": "Tamaño de la fianza",
|
||||
"Allow bondless takers":"Permitir tomadores sin fianza",
|
||||
@ -201,7 +207,8 @@
|
||||
"Price and Premium":"Precio y prima",
|
||||
"Amount of Satoshis": "Cantidad de Sats",
|
||||
"Premium over market price":"Prima sobre el mercado",
|
||||
"Order ID":"ID de la orden",
|
||||
"Order ID":"Orden ID",
|
||||
"Deposit timer":"Para depositar",
|
||||
"Expires in":"Expira en",
|
||||
"{{nickname}} is asking for a collaborative cancel":"{{nickname}} solicita cancelar colaborativamente",
|
||||
"You asked for a collaborative cancellation":"Solicitaste cancelar colaborativamente",
|
||||
@ -261,7 +268,7 @@
|
||||
"Your maker bond was unlocked": "Tu fianza se ha desbloqueado",
|
||||
"Your taker bond was unlocked": "Tu fianza se ha desbloqueado",
|
||||
"Your order is public": "Tu orden es pública",
|
||||
"Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{invoice_escrow_duration}} hours to reply. If you do not reply, you risk losing your bond.": "Se paciente hasta que un robot tome tu orden. Esta ventana sonará 🔊 una vez que algún Robot tome tu orden. Entonces tendrás {{invoice_escrow_duration}} horas para responder, si no respondes arriesgas perder tu fianza.",
|
||||
"Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{deposit_timer_hours}}h {{deposit_timer_minutes}}m hours to reply. If you do not reply, you risk losing your bond.": "Se paciente hasta que un robot tome tu orden. Esta ventana sonará 🔊 una vez que algún Robot tome tu orden. Entonces tendrás {{deposit_timer_hours}}h {{deposit_timer_minutes}}min para responder, si no respondes arriesgas perder tu fianza.",
|
||||
"If the order expires untaken, your bond will return to you (no action needed).": "Si tu oferta expira sin ser tomada, tu fianza será desbloqueada en tu cartera automáticamente.",
|
||||
"Enable Telegram Notifications": "Notificar en Telegram",
|
||||
"Enable TG Notifications": "Activar Notificaciones TG",
|
||||
@ -269,7 +276,7 @@
|
||||
"Go back": "Volver",
|
||||
"Enable": "Activar",
|
||||
"Telegram enabled": "Telegram activado",
|
||||
"Public orders for {{currencyCode}}": "Órdenes públicas para {{currencyCode}}",
|
||||
"Public orders for {{currencyCode}}": "Órdenes públicas por {{currencyCode}}",
|
||||
"Premium rank": "Percentil de la prima",
|
||||
"Among public {{currencyCode}} orders (higher is cheaper)": "Entre las órdenes públicas de {{currencyCode}} (más alto, más barato)",
|
||||
"A taker has been found!": "¡Un tomador ha sido encontrado!",
|
||||
@ -327,6 +334,17 @@
|
||||
"You can claim the dispute resolution amount (escrow and fidelity bond) from your profile rewards. If there is anything the staff can help with, do not hesitate to contact to robosats@protonmail.com (or via your provided burner contact method).": "Puedes retirar la cantidad de la resolución de la disputa (fianza y colateral) desde las recompensas de tu perfil. Si hay algo que el equipo pueda hacer, no dudes en contactar con robosats@protonmail.com (o a través del método de contacto de usar y tirar que especificaste).",
|
||||
"You have lost the dispute": "Has perdido la disputa",
|
||||
"Unfortunately you have lost the dispute. If you think this is a mistake you can ask to re-open the case via email to robosats@protonmail.com. However, chances of it being investigated again are low.": "Desafortunadamente has perdido la disputa. Si piensas que es un error también puedes pedir reabrir el caso por email a robosats@protonmail.com. De todas formas, las probabilidades de ser investigado de nuevo son bajas.",
|
||||
"Expired not taken":"Expiró sin ser tomada",
|
||||
"Maker bond not locked":"La fianza del creador no fue bloqueada",
|
||||
"Escrow not locked":"El depósito de garantía no fue bloqueado",
|
||||
"Invoice not submitted":"No se entregó factura del comprado",
|
||||
"Neither escrow locked or invoice submitted":"Ni el depósito de garantía fue bloqueado ni se entregó factura del comprador",
|
||||
"Renew Order":"Renovar Orden",
|
||||
"Pause the public order":"Pausar la orden pública",
|
||||
"Your order is paused":"Tu orden está en pausa",
|
||||
"Your public order has been paused. At the moment it cannot be seen or taken by other robots. You can choose to unpause it at any time.":"Tu orden pública fue pausada. Ahora mismo, la orden no puede ser vista ni tomada por otros robots. Puedes volver a activarla cuando desees.",
|
||||
"Unpause Order":"Activar Orden",
|
||||
|
||||
|
||||
"INFO DIALOG - InfoDiagog.js": "App information and clarifications and terms of use",
|
||||
"Close": "Cerrar",
|
||||
|
Loading…
Reference in New Issue
Block a user