Implement responsive order book

This commit is contained in:
Reckless_Satoshi
2022-01-27 06:40:14 -08:00
parent 493684b8c9
commit 6ab8f86b97
5 changed files with 110 additions and 13 deletions

View File

@ -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)

View File

@ -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",

View File

@ -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"
} }

View File

@ -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">

View File

@ -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)