From 6ab8f86b972a71c1d36575f8aa3a8abc9d4473c9 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Thu, 27 Jan 2022 06:40:14 -0800 Subject: [PATCH] Implement responsive order book --- api/logics.py | 18 ++++--- frontend/package-lock.json | 29 +++++++++++ frontend/package.json | 1 + frontend/src/components/BookPage.js | 74 ++++++++++++++++++++++++++--- setup.md | 1 + 5 files changed, 110 insertions(+), 13 deletions(-) diff --git a/api/logics.py b/api/logics.py index 86863f8c..4818d908 100644 --- a/api/logics.py +++ b/api/logics.py @@ -89,7 +89,7 @@ class Logics(): return int(satoshis_now) 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) if not order.is_explicit: premium = order.premium @@ -244,7 +244,8 @@ class Logics(): return True, None def dispute_statement(order, user, statement): - ''' Updates the dispute statements in DB''' + ''' Updates the dispute statements''' + if not order.status == Order.Status.DIS: 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): # only the buyer can post a buyer invoice + if not cls.is_buyer(order, user): return False, {'bad_request':'Only the buyer of this order can provide a buyer invoice.'} if not order.taker_bond: @@ -364,8 +366,8 @@ class Logics(): @classmethod def cancel_order(cls, order, user, state=None): - # Do not change order status if an order in any with - # any of these status is sent to expire here + # 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, Order.Status.DIS, Order.Status.CCA, @@ -377,7 +379,7 @@ class Logics(): # 1) When maker cancels before bond '''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: order.status = Order.Status.UCA order.save() @@ -744,7 +746,7 @@ class Logics(): def confirm_fiat(cls, order, user): ''' If Order is in the CHAT states: 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 @@ -786,9 +788,11 @@ class Logics(): @classmethod 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 order.status > Order.Status.PAY: + if order.status in rating_allowed_status: # if maker, rates taker if order.maker == user and order.maker_rated == False: cls.add_profile_rating(order.taker.profile, rating) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 62e583c3..2faa0fd2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3253,6 +3253,11 @@ "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": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", @@ -5040,6 +5045,14 @@ "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": { "version": "3.3.2", "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", "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": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", @@ -7076,6 +7100,11 @@ "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3a098c13..a82272b8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "react-native": "^0.66.4", "react-native-svg": "^12.1.1", "react-qr-code": "^2.0.3", + "react-responsive": "^9.0.0-beta.6", "react-router-dom": "^5.2.0", "websocket": "^1.0.34" } diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index 241c5273..8785ffd7 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -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 { Link } from 'react-router-dom' import { DataGrid } from '@mui/x-data-grid'; +import MediaQuery from 'react-responsive' + import getFlags from './getFlags' export default class BookPage extends Component { @@ -68,8 +70,8 @@ export default class BookPage extends Component { pn(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } - - bookListTable=()=>{ + + bookListTableDesktop=()=>{ return (
{ + return ( +
+ + ({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 ( + + + + ); + } }, + { 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 ( +
{params.row.currency + " " + getFlags(params.row.currency)}
+ )} }, + { field: 'payment_method', headerName: 'Payment Method', width: 180, hide:'true'}, + { field: 'price', headerName: 'Price', type: 'number', width: 140, hide:'true', + renderCell: (params) => {return ( +
{this.pn(params.row.price) + " " +params.row.currency+ "/BTC" }
+ )} }, + { field: 'premium', headerName: 'Premium', type: 'number', width: 85, + renderCell: (params) => {return ( +
{parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }
+ )} }, + ]} + + pageSize={6} + onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. + rowsPerPageOptions={[6]} + /> +
+ ); + } + render() { return ( @@ -206,11 +260,19 @@ export default class BookPage extends Component { ) : - + {/* Desktop Book */} + + + {this.state.loading ? null : this.bookListTableDesktop()} + + - {this.state.loading ? null : this.bookListTable()} - - + {/* Smartphone Book */} + + + {this.state.loading ? null : this.bookListTablePhone()} + + } diff --git a/setup.md b/setup.md index 1c691458..5db7ee37 100644 --- a/setup.md +++ b/setup.md @@ -126,6 +126,7 @@ npm install websocket npm install react-countdown npm install @mui/icons-material 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)