mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-18 00:33:15 +00:00
Implement responsive order book
This commit is contained in:
@ -89,7 +89,7 @@ class Logics():
|
|||||||
return int(satoshis_now)
|
return int(satoshis_now)
|
||||||
|
|
||||||
def price_and_premium_now(order):
|
def price_and_premium_now(order):
|
||||||
''' computes order premium live '''
|
''' computes order price and premium with current rates '''
|
||||||
exchange_rate = float(order.currency.exchange_rate)
|
exchange_rate = float(order.currency.exchange_rate)
|
||||||
if not order.is_explicit:
|
if not order.is_explicit:
|
||||||
premium = order.premium
|
premium = order.premium
|
||||||
@ -244,7 +244,8 @@ class Logics():
|
|||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def dispute_statement(order, user, statement):
|
def dispute_statement(order, user, statement):
|
||||||
''' Updates the dispute statements in DB'''
|
''' Updates the dispute statements'''
|
||||||
|
|
||||||
if not order.status == Order.Status.DIS:
|
if not order.status == Order.Status.DIS:
|
||||||
return False, {'bad_request':'Only orders in dispute accept a dispute statements'}
|
return False, {'bad_request':'Only orders in dispute accept a dispute statements'}
|
||||||
|
|
||||||
@ -278,6 +279,7 @@ class Logics():
|
|||||||
def update_invoice(cls, order, user, invoice):
|
def update_invoice(cls, order, user, invoice):
|
||||||
|
|
||||||
# only the buyer can post a buyer invoice
|
# only the buyer can post a buyer invoice
|
||||||
|
|
||||||
if not cls.is_buyer(order, user):
|
if not cls.is_buyer(order, user):
|
||||||
return False, {'bad_request':'Only the buyer of this order can provide a buyer invoice.'}
|
return False, {'bad_request':'Only the buyer of this order can provide a buyer invoice.'}
|
||||||
if not order.taker_bond:
|
if not order.taker_bond:
|
||||||
@ -364,8 +366,8 @@ class Logics():
|
|||||||
@classmethod
|
@classmethod
|
||||||
def cancel_order(cls, order, user, state=None):
|
def cancel_order(cls, order, user, state=None):
|
||||||
|
|
||||||
# Do not change order status if an order in any with
|
# Do not change order status if an is in order
|
||||||
# any of these status is sent to expire here
|
# any of these status
|
||||||
do_not_cancel = [Order.Status.DEL, Order.Status.UCA,
|
do_not_cancel = [Order.Status.DEL, Order.Status.UCA,
|
||||||
Order.Status.EXP, Order.Status.TLD,
|
Order.Status.EXP, Order.Status.TLD,
|
||||||
Order.Status.DIS, Order.Status.CCA,
|
Order.Status.DIS, Order.Status.CCA,
|
||||||
@ -377,7 +379,7 @@ class Logics():
|
|||||||
|
|
||||||
# 1) When maker cancels before bond
|
# 1) When maker cancels before bond
|
||||||
'''The order never shows up on the book and order
|
'''The order never shows up on the book and order
|
||||||
status becomes "cancelled". That's it.'''
|
status becomes "cancelled" '''
|
||||||
if order.status == Order.Status.WFB and order.maker == user:
|
if order.status == Order.Status.WFB and order.maker == user:
|
||||||
order.status = Order.Status.UCA
|
order.status = Order.Status.UCA
|
||||||
order.save()
|
order.save()
|
||||||
@ -744,7 +746,7 @@ class Logics():
|
|||||||
def confirm_fiat(cls, order, user):
|
def confirm_fiat(cls, order, user):
|
||||||
''' If Order is in the CHAT states:
|
''' If Order is in the CHAT states:
|
||||||
If user is buyer: fiat_sent goes to true.
|
If user is buyer: fiat_sent goes to true.
|
||||||
If User is tseller and fiat_sent is true: settle the escrow and pay buyer invoice!'''
|
If User is seller and fiat_sent is true: settle the escrow and pay buyer invoice!'''
|
||||||
|
|
||||||
if order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Alternatively, if all collateral is locked? test out
|
if order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Alternatively, if all collateral is locked? test out
|
||||||
|
|
||||||
@ -787,8 +789,10 @@ class Logics():
|
|||||||
@classmethod
|
@classmethod
|
||||||
def rate_counterparty(cls, order, user, rating):
|
def rate_counterparty(cls, order, user, rating):
|
||||||
|
|
||||||
|
rating_allowed_status = [Order.Status.PAY, Order.Status.SUC, Order.Status.FAI, Order.Status.MLD, Order.Status.TLD]
|
||||||
|
|
||||||
# If the trade is finished
|
# If the trade is finished
|
||||||
if order.status > Order.Status.PAY:
|
if order.status in rating_allowed_status:
|
||||||
# if maker, rates taker
|
# if maker, rates taker
|
||||||
if order.maker == user and order.maker_rated == False:
|
if order.maker == user and order.maker_rated == False:
|
||||||
cls.add_profile_rating(order.taker.profile, rating)
|
cls.add_profile_rating(order.taker.profile, rating)
|
||||||
|
29
frontend/package-lock.json
generated
29
frontend/package-lock.json
generated
@ -3253,6 +3253,11 @@
|
|||||||
"which": "^2.0.1"
|
"which": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"css-mediaquery": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA="
|
||||||
|
},
|
||||||
"css-select": {
|
"css-select": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
||||||
@ -5040,6 +5045,14 @@
|
|||||||
"object-visit": "^1.0.0"
|
"object-visit": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"matchmediaquery": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==",
|
||||||
|
"requires": {
|
||||||
|
"css-mediaquery": "^0.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"material-ui-image": {
|
"material-ui-image": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/material-ui-image/-/material-ui-image-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/material-ui-image/-/material-ui-image-3.3.2.tgz",
|
||||||
@ -6444,6 +6457,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||||
"integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA=="
|
"integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA=="
|
||||||
},
|
},
|
||||||
|
"react-responsive": {
|
||||||
|
"version": "9.0.0-beta.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-9.0.0-beta.6.tgz",
|
||||||
|
"integrity": "sha512-Flk6UrnpBBByreva6ja/TsbXiXq4BXOlDEKL6Ur+nshUs3CcN5W0BpGe6ClFWrKcORkMZAAYy7A4N4xlMmpgVw==",
|
||||||
|
"requires": {
|
||||||
|
"hyphenate-style-name": "^1.0.0",
|
||||||
|
"matchmediaquery": "^0.3.0",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"shallow-equal": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-router": {
|
"react-router": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
||||||
@ -7076,6 +7100,11 @@
|
|||||||
"kind-of": "^6.0.2"
|
"kind-of": "^6.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shallow-equal": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"react-native": "^0.66.4",
|
"react-native": "^0.66.4",
|
||||||
"react-native-svg": "^12.1.1",
|
"react-native-svg": "^12.1.1",
|
||||||
"react-qr-code": "^2.0.3",
|
"react-qr-code": "^2.0.3",
|
||||||
|
"react-responsive": "^9.0.0-beta.6",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34"
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ import React, { Component } from "react";
|
|||||||
import { Paper, Button , CircularProgress, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, List, ListItem, ListItemText, Avatar, RouterLink, ListItemAvatar} from "@mui/material";
|
import { Paper, Button , CircularProgress, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, List, ListItem, ListItemText, Avatar, RouterLink, ListItemAvatar} from "@mui/material";
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { DataGrid } from '@mui/x-data-grid';
|
import { DataGrid } from '@mui/x-data-grid';
|
||||||
|
import MediaQuery from 'react-responsive'
|
||||||
|
|
||||||
import getFlags from './getFlags'
|
import getFlags from './getFlags'
|
||||||
|
|
||||||
export default class BookPage extends Component {
|
export default class BookPage extends Component {
|
||||||
@ -69,7 +71,7 @@ export default class BookPage extends Component {
|
|||||||
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||||
}
|
}
|
||||||
|
|
||||||
bookListTable=()=>{
|
bookListTableDesktop=()=>{
|
||||||
return (
|
return (
|
||||||
<div style={{ height: 475, width: '100%' }}>
|
<div style={{ height: 475, width: '100%' }}>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
@ -124,6 +126,58 @@ export default class BookPage extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bookListTablePhone=()=>{
|
||||||
|
return (
|
||||||
|
<div style={{ height: 425, width: '100%' }}>
|
||||||
|
<DataGrid
|
||||||
|
rows={
|
||||||
|
this.state.orders.map((order) =>
|
||||||
|
({id: order.id,
|
||||||
|
avatar: window.location.origin +'/static/assets/avatars/' + order.maker_nick + '.png',
|
||||||
|
robosat: order.maker_nick,
|
||||||
|
type: order.type ? "Sell": "Buy",
|
||||||
|
amount: parseFloat(parseFloat(order.amount).toFixed(4)),
|
||||||
|
currency: this.getCurrencyCode(order.currency),
|
||||||
|
payment_method: order.payment_method,
|
||||||
|
price: order.price,
|
||||||
|
premium: order.premium,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
|
||||||
|
columns={[
|
||||||
|
// { field: 'id', headerName: 'ID', width: 40 },
|
||||||
|
{ field: 'robosat', headerName: 'Robot', width: 80,
|
||||||
|
renderCell: (params) => {return (
|
||||||
|
<ListItemButton style={{ cursor: "pointer" }}>
|
||||||
|
<Avatar alt={params.row.robosat} src={params.row.avatar} />
|
||||||
|
</ListItemButton>
|
||||||
|
);
|
||||||
|
} },
|
||||||
|
{ field: 'type', headerName: 'Type', width: 60, hide:'true'},
|
||||||
|
{ field: 'amount', headerName: 'Amount', type: 'number', width: 80 },
|
||||||
|
{ field: 'currency', headerName: 'Currency', width: 100,
|
||||||
|
renderCell: (params) => {return (
|
||||||
|
<div style={{ cursor: "pointer" }}>{params.row.currency + " " + getFlags(params.row.currency)}</div>
|
||||||
|
)} },
|
||||||
|
{ field: 'payment_method', headerName: 'Payment Method', width: 180, hide:'true'},
|
||||||
|
{ field: 'price', headerName: 'Price', type: 'number', width: 140, hide:'true',
|
||||||
|
renderCell: (params) => {return (
|
||||||
|
<div style={{ cursor: "pointer" }}>{this.pn(params.row.price) + " " +params.row.currency+ "/BTC" }</div>
|
||||||
|
)} },
|
||||||
|
{ field: 'premium', headerName: 'Premium', type: 'number', width: 85,
|
||||||
|
renderCell: (params) => {return (
|
||||||
|
<div style={{ cursor: "pointer" }}>{parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }</div>
|
||||||
|
)} },
|
||||||
|
]}
|
||||||
|
|
||||||
|
pageSize={6}
|
||||||
|
onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
||||||
|
rowsPerPageOptions={[6]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Grid className='orderBook' container spacing={1}>
|
<Grid className='orderBook' container spacing={1}>
|
||||||
@ -206,11 +260,19 @@ export default class BookPage extends Component {
|
|||||||
</Grid>)
|
</Grid>)
|
||||||
:
|
:
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Paper elevation={0} style={{width: 910, maxHeight: 500, overflow: 'auto'}}>
|
{/* Desktop Book */}
|
||||||
|
<MediaQuery minWidth={920}>
|
||||||
|
<Paper elevation={0} style={{width: 910, maxHeight: 500, overflow: 'auto'}}>
|
||||||
|
{this.state.loading ? null : this.bookListTableDesktop()}
|
||||||
|
</Paper>
|
||||||
|
</MediaQuery>
|
||||||
|
|
||||||
{this.state.loading ? null : this.bookListTable()}
|
{/* Smartphone Book */}
|
||||||
|
<MediaQuery maxWidth={919}>
|
||||||
</Paper>
|
<Paper elevation={0} style={{width: 380, maxHeight: 450, overflow: 'auto'}}>
|
||||||
|
{this.state.loading ? null : this.bookListTablePhone()}
|
||||||
|
</Paper>
|
||||||
|
</MediaQuery>
|
||||||
</Grid>
|
</Grid>
|
||||||
}
|
}
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
|
1
setup.md
1
setup.md
@ -126,6 +126,7 @@ npm install websocket
|
|||||||
npm install react-countdown
|
npm install react-countdown
|
||||||
npm install @mui/icons-material
|
npm install @mui/icons-material
|
||||||
npm install @mui/x-data-grid
|
npm install @mui/x-data-grid
|
||||||
|
npm install react-responsive
|
||||||
```
|
```
|
||||||
Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4 (@material-ui/core) extentions (so both V4 and V5 are needed)
|
Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4 (@material-ui/core) extentions (so both V4 and V5 are needed)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user