Revamp UI

This commit is contained in:
Reckless_Satoshi 2022-01-09 17:12:58 -08:00
parent bb9cafadd8
commit 9bc6757ba3
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
7 changed files with 137 additions and 71 deletions

View File

@ -36,9 +36,9 @@ class Logics():
def validate_order_size(order): def validate_order_size(order):
'''Checks if order is withing limits at t0''' '''Checks if order is withing limits at t0'''
if order.t0_satoshis > MAX_TRADE: if order.t0_satoshis > MAX_TRADE:
return False, {'bad_request': f'Your order is too big. It is worth {order.t0_satoshis} now. But maximum is {MAX_TRADE}'} return False, {'bad_request': 'Your order is too big. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MAX_TRADE)+ ' Sats'}
if order.t0_satoshis < MIN_TRADE: if order.t0_satoshis < MIN_TRADE:
return False, {'bad_request': f'Your order is too small. It is worth {order.t0_satoshis} now. But minimum is {MIN_TRADE}'} return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'}
return True, None return True, None
def take(order, user): def take(order, user):
@ -66,7 +66,22 @@ class Logics():
satoshis_now = (float(order.amount) / premium_rate) * 100*1000*1000 satoshis_now = (float(order.amount) / premium_rate) * 100*1000*1000
return int(satoshis_now) return int(satoshis_now)
def price_and_premium_now(order):
''' computes order premium live '''
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
if not order.is_explicit:
premium = order.premium
price = exchange_rate
else:
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
order_rate = float(order.amount) / (float(order.satoshis) / 100000000)
premium = order_rate / exchange_rate - 1
price = order_rate
premium = int(premium*100) # 2 decimals left
return price, premium
def order_expires(order): def order_expires(order):
order.status = Order.Status.EXP order.status = Order.Status.EXP
order.maker = None order.maker = None

View File

@ -1,7 +1,11 @@
from decouple import config from decouple import config
import requests import requests
import ring
storage = {}
@ring.dict(storage, expire=60) #keeps in cache for 60 secs
def get_exchange_rate(currency): def get_exchange_rate(currency):
# TODO Add fallback Public APIs and error handling # TODO Add fallback Public APIs and error handling
# Think about polling price data in a different way (e.g. store locally every t seconds) # Think about polling price data in a different way (e.g. store locally every t seconds)

View File

@ -369,6 +369,9 @@ class BookView(ListAPIView):
data = ListOrderSerializer(order).data data = ListOrderSerializer(order).data
data['maker_nick'] = str(order.maker) data['maker_nick'] = str(order.maker)
# Compute current premium for those orders that are explicitly priced.
data['price'], data['premium'] = Logics.price_and_premium_now(order)
for key in ('status','taker'): # Non participants should not see the status or who is the taker for key in ('status','taker'): # Non participants should not see the status or who is the taker
del data[key] del data[key]

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Button , Divider, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, List, ListItem, ListItemText, Avatar, RouterLink, ListItemAvatar} from "@mui/material"; import { Box, Button , Divider, 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'
export default class BookPage extends Component { export default class BookPage extends Component {
@ -65,14 +65,51 @@ export default class BookPage extends Component {
bookListItems=()=>{ bookListItems=()=>{
return (this.state.orders.map((order) => return (this.state.orders.map((order) =>
<ListItemButton> <>
<ListItemAvatar > <ListItemButton value={order.id} onClick={() => this.handleCardClick(order.id)}>
<Avatar
alt={order.maker_nick} <ListItemAvatar >
src={window.location.origin +'/static/assets/avatars/' + order.maker_nick + '.png'} <Avatar
/> alt={order.maker_nick}
</ListItemAvatar> src={window.location.origin +'/static/assets/avatars/' + order.maker_nick + '.png'}
</ListItemButton> />
</ListItemAvatar>
<ListItemText>
<Typography variant="h6">
{order.maker_nick+" "}
</Typography>
</ListItemText>
<ListItemText align='left'>
<Typography variant="subtitle1">
<b>{order.type ? " Sells ": " Buys "} BTC </b> for {parseFloat(
parseFloat(order.amount).toFixed(4))+" "+ this.getCurrencyCode(order.currency)+" "}
</Typography>
</ListItemText>
<ListItemText align='left'>
<Typography variant="subtitle1">
via <b>{order.payment_method}</b>
</Typography>
</ListItemText>
<ListItemText align='right'>
<Typography variant="subtitle1">
at <b>{this.pn(order.price) + " " + this.getCurrencyCode(order.currency)}/BTC</b>
</Typography>
</ListItemText>
<ListItemText align='right'>
<Typography variant="subtitle1">
{order.premium > 1 ? "🔴" : "🔵" } <b>{parseFloat(parseFloat(order.premium).toFixed(4))}%</b>
</Typography>
</ListItemText>
</ListItemButton>
<Divider/>
</>
)); ));
} }
@ -133,7 +170,7 @@ export default class BookPage extends Component {
return ( return (
<Grid className='orderBook' container spacing={1}> <Grid className='orderBook' container spacing={1}>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Typography component="h4" variant="h4"> <Typography component="h2" variant="h2">
Order Book Order Book
</Typography> </Typography>
</Grid> </Grid>
@ -202,11 +239,9 @@ export default class BookPage extends Component {
</Typography> </Typography>
</Grid>) </Grid>)
: :
<Grid item> <List sx={{ width: '120%', overflow: 'auto',}} >
<List> {this.bookListItems()}
{this.bookListItems()}
</List> </List>
</Grid>
} }
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button color="secondary" variant="contained" to="/" component={Link}> <Button color="secondary" variant="contained" to="/" component={Link}>

View File

@ -23,8 +23,10 @@ export default class MakerPage extends Component {
defaultCurrency = 1; defaultCurrency = 1;
defaultCurrencyCode = 'USD'; defaultCurrencyCode = 'USD';
defaultAmount = 0 ; defaultAmount = 0 ;
defaultPaymentMethod = "Not specified"; defaultPaymentMethod = "not specified";
defaultPremium = 0; defaultPremium = 0;
minTradeSats = 10000;
maxTradeSats = 500000;
constructor(props) { constructor(props) {
super(props); super(props);
@ -69,9 +71,16 @@ export default class MakerPage extends Component {
}); });
} }
handleSatoshisChange=(e)=>{ handleSatoshisChange=(e)=>{
var bad_sats = e.target.value > this.maxTradeSats ?
("Must be less than "+this.maxTradeSats):
(e.target.value < this.minTradeSats ?
("Must be more than "+this.minTradeSats): null)
this.setState({ this.setState({
satoshis: e.target.value, satoshis: e.target.value,
}); badSatoshis: bad_sats,
})
;
} }
handleClickRelative=(e)=>{ handleClickRelative=(e)=>{
this.setState({ this.setState({
@ -125,21 +134,19 @@ export default class MakerPage extends Component {
render() { render() {
return ( return (
<Grid container spacing={1}> <Grid container xs={12} align="center" spacing={1}>
<Grid item xs={12} align="center">
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Typography component="h4" variant="h4"> <Typography component="h2" variant="h2">
Make an Order Order Maker
</Typography> </Typography>
</Grid> </Grid>
<Paper elevation={12} style={{ padding: 8,}}> <Grid item xs={12} align="center" spacing={1}>
<Grid item xs={12} align="center"> <Paper elevation={12} style={{ padding: 8, width:350, align:'center'}}>
<Grid item xs={12} align="center" spacing={1}>
<FormControl component="fieldset"> <FormControl component="fieldset">
<FormHelperText> <FormHelperText>
<div align='center'> Buy or Sell Bitcoin?
Choose Buy or Sell Bitcoin </FormHelperText>
</div>
</FormHelperText>
<RadioGroup row defaultValue="0" onChange={this.handleTypeChange}> <RadioGroup row defaultValue="0" onChange={this.handleTypeChange}>
<FormControlLabel <FormControlLabel
value="0" value="0"
@ -156,36 +163,35 @@ export default class MakerPage extends Component {
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
</Grid> </Grid>
<Grid item xs={12} align="center"> <Grid container xs={11} align="center">
<FormControl > <TextField
<TextField label="Amount"
label="Amount of Fiat to Trade" type="number"
type="number" required="true"
required="true" defaultValue={this.defaultAmount}
defaultValue={this.defaultAmount} inputProps={{
inputProps={{ min:0 ,
min:0 , style: {textAlign:"center"}
style: {textAlign:"center"} }}
}} onChange={this.handleAmountChange}
onChange={this.handleAmountChange} />
/> <Select
<Select label="Select Payment Currency"
label="Select Payment Currency" required="true"
required="true" defaultValue={this.defaultCurrency}
defaultValue={this.defaultCurrency} inputProps={{
inputProps={{ style: {textAlign:"center"}
style: {textAlign:"center"} }}
}} onChange={this.handleCurrencyChange}
onChange={this.handleCurrencyChange} >
> {
{ Object.entries(this.state.currencies_dict)
Object.entries(this.state.currencies_dict) .map( ([key, value]) => <MenuItem value={parseInt(key)}>{value}</MenuItem> )
.map( ([key, value]) => <MenuItem value={parseInt(key)}>{value}</MenuItem> ) }
} </Select>
</Select>
</FormControl>
</Grid>
</Grid>
<br/>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<FormControl > <FormControl >
<TextField <TextField
@ -200,8 +206,14 @@ export default class MakerPage extends Component {
/> />
</FormControl> </FormControl>
</Grid> </Grid>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<FormControl component="fieldset"> <FormControl component="fieldset">
<FormHelperText >
<div align='center'>
Choose a Pricing Method
</div>
</FormHelperText>
<RadioGroup row defaultValue="relative"> <RadioGroup row defaultValue="relative">
<FormControlLabel <FormControlLabel
value="relative" value="relative"
@ -218,24 +230,20 @@ export default class MakerPage extends Component {
onClick={this.handleClickExplicit} onClick={this.handleClickExplicit}
/> />
</RadioGroup> </RadioGroup>
<FormHelperText >
<div align='center'>
Choose a Pricing Method
</div>
</FormHelperText>
</FormControl> </FormControl>
</Grid> </Grid>
{/* conditional shows either Premium % field or Satoshis field based on pricing method */} {/* conditional shows either Premium % field or Satoshis field based on pricing method */}
{ this.state.isExplicit { this.state.isExplicit
? <Grid item xs={12} align="center"> ? <Grid item xs={12} align="center">
<TextField <TextField
label="Explicit Amount in Satoshis" label="Satoshis"
error={this.state.badSatoshis}
type="number" type="number"
required="true" required="true"
inputProps={{ inputProps={{
// TODO read these from .env file // TODO read these from .env file
min:10000 , min:this.minTradeSats ,
max:500000 , max:this.maxTradeSats ,
style: {textAlign:"center"} style: {textAlign:"center"}
}} }}
onChange={this.handleSatoshisChange} onChange={this.handleSatoshisChange}
@ -255,7 +263,7 @@ export default class MakerPage extends Component {
</Grid> </Grid>
} }
</Paper> </Paper>
</Grid> </Grid>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button color="primary" variant="contained" onClick={this.handleCreateOfferButtonPressed} > <Button color="primary" variant="contained" onClick={this.handleCreateOfferButtonPressed} >
Create Order Create Order

View File

@ -24,7 +24,7 @@ export default class UserGenPage extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
token: this.genBase62Token(32), token: this.genBase62Token(34),
}; };
this.getGeneratedUser(this.state.token); this.getGeneratedUser(this.state.token);
} }
@ -72,7 +72,7 @@ export default class UserGenPage extends Component {
handleAnotherButtonPressed=(e)=>{ handleAnotherButtonPressed=(e)=>{
this.delGeneratedUser() this.delGeneratedUser()
this.setState({ this.setState({
token: this.genBase62Token(32), token: this.genBase62Token(34),
}) })
this.reload_for_csrf_to_work(); this.reload_for_csrf_to_work();
} }

View File

@ -7,6 +7,7 @@
``` ```
pip install virtualenvwrapper pip install virtualenvwrapper
pip install python-decouple pip install python-decouple
pip install ring
``` ```
### Add to .bashrc ### Add to .bashrc