Add user profile bottom left icon with active order reminder

This commit is contained in:
Reckless_Satoshi 2022-01-29 11:51:26 -08:00
parent 292addc081
commit 5ab97453f0
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
9 changed files with 166 additions and 56 deletions

View File

@ -40,12 +40,12 @@ class Logics():
'''Checks if the user is already partipant of an active order''' '''Checks if the user is already partipant of an active order'''
queryset = Order.objects.filter(maker=user, status__in=active_order_status) queryset = Order.objects.filter(maker=user, status__in=active_order_status)
if queryset.exists(): if queryset.exists():
return False, {'bad_request':'You are already maker of an active order'} return False, {'bad_request':'You are already maker of an active order'}, queryset[0]
queryset = Order.objects.filter(taker=user, status__in=active_order_status) queryset = Order.objects.filter(taker=user, status__in=active_order_status)
if queryset.exists(): if queryset.exists():
return False, {'bad_request':'You are already taker of an active order'} return False, {'bad_request':'You are already taker of an active order'}, queryset[0]
return True, None return True, None, None
def validate_order_size(order): def validate_order_size(order):
'''Validates if order is withing limits in satoshis at t0''' '''Validates if order is withing limits in satoshis at t0'''
@ -769,13 +769,12 @@ class Logics():
# Double check the escrow is settled. # Double check the escrow is settled.
if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash): if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
is_payed, context = follow_send_payment(order.payout) ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
if is_payed:
# RETURN THE BONDS // Probably best also do it even if payment failed # RETURN THE BONDS // Probably best also do it even if payment failed
cls.return_bond(order.taker_bond) cls.return_bond(order.taker_bond)
cls.return_bond(order.maker_bond) cls.return_bond(order.maker_bond)
is_payed, context = follow_send_payment(order.payout) ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
if is_payed:
order.save() order.save()
return True, context return True, context
else: else:
# error handling here # error handling here

View File

@ -49,7 +49,7 @@ class MakerView(CreateAPIView):
satoshis = serializer.data.get('satoshis') satoshis = serializer.data.get('satoshis')
is_explicit = serializer.data.get('is_explicit') is_explicit = serializer.data.get('is_explicit')
valid, context = Logics.validate_already_maker_or_taker(request.user) valid, context, _ = Logics.validate_already_maker_or_taker(request.user)
if not valid: return Response(context, status.HTTP_409_CONFLICT) if not valid: return Response(context, status.HTTP_409_CONFLICT)
# Creates a new order # Creates a new order
@ -270,7 +270,7 @@ class OrderView(viewsets.ViewSet):
# 1) If action is take, it is a taker request! # 1) If action is take, it is a taker request!
if action == 'take': if action == 'take':
if order.status == Order.Status.PUB: if order.status == Order.Status.PUB:
valid, context = Logics.validate_already_maker_or_taker(request.user) valid, context, _ = Logics.validate_already_maker_or_taker(request.user)
if not valid: return Response(context, status=status.HTTP_409_CONFLICT) if not valid: return Response(context, status=status.HTTP_409_CONFLICT)
valid, context = Logics.take(order, request.user) valid, context = Logics.take(order, request.user)
if not valid: return Response(context, status=status.HTTP_403_FORBIDDEN) if not valid: return Response(context, status=status.HTTP_403_FORBIDDEN)
@ -345,7 +345,7 @@ class UserView(APIView):
# If an existing user opens the main page by mistake, we do not want it to create a new nickname/profile for him # If an existing user opens the main page by mistake, we do not want it to create a new nickname/profile for him
if request.user.is_authenticated: if request.user.is_authenticated:
context = {'nickname': request.user.username} context = {'nickname': request.user.username}
not_participant, _ = Logics.validate_already_maker_or_taker(request.user) not_participant, _, _ = Logics.validate_already_maker_or_taker(request.user)
# Does not allow this 'mistake' if an active order # Does not allow this 'mistake' if an active order
if not not_participant: if not not_participant:
@ -449,7 +449,6 @@ class BookView(ListAPIView):
if len(queryset)== 0: if len(queryset)== 0:
return Response({'not_found':'No orders found, be the first to make one'}, status=status.HTTP_404_NOT_FOUND) return Response({'not_found':'No orders found, be the first to make one'}, status=status.HTTP_404_NOT_FOUND)
# queryset = queryset.order_by('created_at')
book_data = [] book_data = []
for order in queryset: for order in queryset:
data = ListOrderSerializer(order).data data = ListOrderSerializer(order).data
@ -509,6 +508,11 @@ class InfoView(ListAPIView):
context['robosats_running_commit_hash'] = get_commit_robosats() context['robosats_running_commit_hash'] = get_commit_robosats()
context['fee'] = FEE context['fee'] = FEE
context['bond_size'] = float(config('BOND_SIZE')) context['bond_size'] = float(config('BOND_SIZE'))
if request.user.is_authenticated:
context['nickname'] = request.user.username
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(request.user)
if not has_no_active_order:
context['active_order_id'] = order.id
return Response(context, status.HTTP_200_OK) return Response(context, status.HTTP_200_OK)

View File

@ -7,16 +7,23 @@ import BottomBar from "./BottomBar";
export default class App extends Component { export default class App extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
nickname: null,
}
}
setAppState=(newState)=>{
this.setState(newState)
} }
render() { render() {
return ( return (
<> <>
<div className='appCenter'> <div className='appCenter'>
<HomePage /> <HomePage setAppState={this.setAppState}/>
</div> </div>
<div className='bottomBar'> <div className='bottomBar'>
<BottomBar /> <BottomBar nickname={this.state.nickname} setAppState={this.setAppState} />
</div> </div>
</> </>
); );

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import {Badge, Paper, Grid, IconButton, Typography, Select, MenuItem, List, ListItemText, ListItem, ListItemIcon, ListItemButton, Divider, Dialog, DialogContent} from "@mui/material"; import {Badge, ListItemAvatar, Avatar,Paper, Grid, IconButton, Typography, Select, MenuItem, List, ListItemText, ListItem, ListItemIcon, ListItemButton, Divider, Dialog, DialogContent} from "@mui/material";
import MediaQuery from 'react-responsive' import MediaQuery from 'react-responsive'
// Icons // Icons
@ -15,6 +15,7 @@ import GitHubIcon from '@mui/icons-material/GitHub';
import EqualizerIcon from '@mui/icons-material/Equalizer'; import EqualizerIcon from '@mui/icons-material/Equalizer';
import SendIcon from '@mui/icons-material/Send'; import SendIcon from '@mui/icons-material/Send';
import PublicIcon from '@mui/icons-material/Public'; import PublicIcon from '@mui/icons-material/Public';
import NumbersIcon from '@mui/icons-material/Numbers';
// pretty numbers // pretty numbers
function pn(x) { function pn(x) {
@ -36,6 +37,8 @@ export default class BottomBar extends Component {
today_total_volume: 0, today_total_volume: 0,
lifetime_satoshis_settled: 0, lifetime_satoshis_settled: 0,
robosats_running_commit_hash: '000000000000000', robosats_running_commit_hash: '000000000000000',
openProfile: false,
profileShown: false,
}; };
this.getInfo(); this.getInfo();
} }
@ -48,7 +51,8 @@ export default class BottomBar extends Component {
this.setState(null) this.setState(null)
fetch('/api/info/') fetch('/api/info/')
.then((response) => response.json()) .then((response) => response.json())
.then((data) => this.setState(data)); .then((data) => this.setState(data) &
this.props.setAppState({nickname:data.nickname}));
} }
handleClickOpenStatsForNerds = () => { handleClickOpenStatsForNerds = () => {
@ -166,20 +170,82 @@ export default class BottomBar extends Component {
) )
} }
handleClickOpenProfile = () => {
this.getInfo();
this.setState({openProfile: true, profileShown: true});
};
handleClickCloseProfile = () => {
this.setState({openProfile: false});
};
dialogProfile =() =>{
return(
<Dialog
open={this.state.openProfile}
onClose={this.handleClickCloseProfile}
aria-labelledby="profile-title"
aria-describedby="profile-description"
>
<DialogContent>
<Typography component="h5" variant="h5">Your Profile</Typography>
<List>
<Divider/>
<ListItem className="profileNickname">
<ListItemText secondary="Your robot pseudonymous">
<Typography component="h6" variant="h6">
{this.props.nickname ? "⚡"+this.props.nickname+"⚡" : ""}
</Typography>
</ListItemText>
<ListItemAvatar>
<Avatar className='avatar'
sx={{ width: 65, height:65 }}
alt={this.props.nickname}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
/>
</ListItemAvatar>
</ListItem>
<Divider/>
{this.state.active_order_id ?
// TODO Link to router and do this.props.history.push
<ListItemButton component="a" href={window.location.origin +'/order/'+this.state.active_order_id}>
<ListItemIcon>
<NumbersIcon color="primary"/>
</ListItemIcon>
<ListItemText color="primary" primary={'One active order #'+this.state.active_order_id} secondary="Your current order"/>
</ListItemButton>
:
<ListItem>
<ListItemIcon><NumbersIcon/></ListItemIcon>
<ListItemText primary="No active orders" secondary="Your current order"/>
</ListItem>
}
</List>
</DialogContent>
</Dialog>
)
}
bottomBarDesktop =()=>{ bottomBarDesktop =()=>{
return( return(
<Paper elevation={6} style={{height:40}}> <Paper elevation={6} style={{height:40}}>
<this.StatsDialog/> <this.StatsDialog/>
<this.CommunityDialog/> <this.CommunityDialog/>
<this.dialogProfile/>
<Grid container xs={12}> <Grid container xs={12}>
<Grid item xs={1}> <Grid item xs={2}>
<IconButton color="primary" <ListItemButton onClick={this.handleClickOpenProfile} >
aria-label="Stats for Nerds" <ListItemAvatar sx={{ width: 30, height: 30 }} >
onClick={this.handleClickOpenStatsForNerds} > <Badge badgeContent={(this.state.active_order_id > 0 & !this.state.profileShown) ? "1": null} color="primary">
<SettingsIcon /> <Avatar className='rotatedAvatar' sx={{margin: 0, top: -13}}
</IconButton> alt={this.props.nickname}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
/>
</Badge>
</ListItemAvatar>
<ListItemText primary={this.props.nickname}/>
</ListItemButton>
</Grid> </Grid>
<Grid item xs={2}> <Grid item xs={2}>
@ -234,7 +300,7 @@ bottomBarDesktop =()=>{
</ListItem> </ListItem>
</Grid> </Grid>
<Grid item xs={2}> <Grid item xs={1}>
<ListItem className="bottomItem"> <ListItem className="bottomItem">
<ListItemIcon size="small"> <ListItemIcon size="small">
<PercentIcon/> <PercentIcon/>
@ -243,12 +309,12 @@ bottomBarDesktop =()=>{
primaryTypographyProps={{fontSize: '14px'}} primaryTypographyProps={{fontSize: '14px'}}
secondaryTypographyProps={{fontSize: '12px'}} secondaryTypographyProps={{fontSize: '12px'}}
primary={this.state.fee*100} primary={this.state.fee*100}
secondary="Trading Fee" /> secondary="Trade Fee" />
</ListItem> </ListItem>
</Grid> </Grid>
<Grid container item xs={1}> <Grid container item xs={1}>
<Grid item xs={4}> <Grid item xs={6}>
<Select <Select
size = 'small' size = 'small'
defaultValue={1} defaultValue={1}
@ -258,15 +324,21 @@ bottomBarDesktop =()=>{
<MenuItem value={1}>EN</MenuItem> <MenuItem value={1}>EN</MenuItem>
</Select> </Select>
</Grid> </Grid>
<Grid item xs={4}/> <Grid item xs={3}>
<Grid item xs={4}>
<IconButton <IconButton
color="primary" color="primary"
aria-label="Telegram" aria-label="Community"
onClick={this.handleClickOpenCommunity} > onClick={this.handleClickOpenCommunity} >
<PeopleIcon /> <PeopleIcon />
</IconButton> </IconButton>
</Grid> </Grid>
<Grid item xs={3}>
<IconButton color="primary"
aria-label="Stats for Nerds"
onClick={this.handleClickOpenStatsForNerds} >
<SettingsIcon />
</IconButton>
</Grid>
</Grid> </Grid>
</Grid> </Grid>
@ -275,13 +347,14 @@ bottomBarDesktop =()=>{
} }
handleClickOpenExchangeSummary = () => { handleClickOpenExchangeSummary = () => {
this.getInfo();
this.setState({openExchangeSummary: true}); this.setState({openExchangeSummary: true});
}; };
handleClickCloseExchangeSummary = () => { handleClickCloseExchangeSummary = () => {
this.setState({openExchangeSummary: false}); this.setState({openExchangeSummary: false});
}; };
phoneExchangeSummaryDialog =() =>{ exchangeSummaryDialogPhone =() =>{
return( return(
<Dialog <Dialog
open={this.state.openExchangeSummary} open={this.state.openExchangeSummary}
@ -357,24 +430,27 @@ bottomBarDesktop =()=>{
) )
} }
bottomBarPhone =()=>{ bottomBarPhone =()=>{
return( return(
<Paper elevation={6} style={{height:40}}> <Paper elevation={6} style={{height:40}}>
<this.StatsDialog/> <this.StatsDialog/>
<this.CommunityDialog/> <this.CommunityDialog/>
<this.phoneExchangeSummaryDialog/> <this.exchangeSummaryDialogPhone/>
<this.dialogProfile/>
<Grid container xs={12}> <Grid container xs={12}>
<Grid item xs={1}> <Grid item xs={1.6}>
<IconButton color="primary" <IconButton onClick={this.handleClickOpenProfile} sx={{margin: 0, top: -13, }} >
aria-label="Stats for Nerds" <Badge badgeContent={(this.state.active_order_id >0 & !this.state.profileShown) ? "1": null} color="primary">
onClick={this.handleClickOpenStatsForNerds} > <Avatar className='rotatedAvatar'
<SettingsIcon /> alt={this.props.nickname}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
/>
</Badge>
</IconButton> </IconButton>
</Grid> </Grid>
<Grid item xs={2} align="center"> <Grid item xs={1.6} align="center">
<IconButton onClick={this.handleClickOpenExchangeSummary} > <IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.num_public_buy_orders} color="action"> <Badge badgeContent={this.state.num_public_buy_orders} color="action">
<InventoryIcon /> <InventoryIcon />
@ -382,7 +458,7 @@ bottomBarPhone =()=>{
</IconButton> </IconButton>
</Grid> </Grid>
<Grid item xs={2} align="center"> <Grid item xs={1.6} align="center">
<IconButton onClick={this.handleClickOpenExchangeSummary} > <IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.num_public_sell_orders} color="action"> <Badge badgeContent={this.state.num_public_sell_orders} color="action">
<SellIcon /> <SellIcon />
@ -390,7 +466,7 @@ bottomBarPhone =()=>{
</IconButton> </IconButton>
</Grid> </Grid>
<Grid item xs={2} align="center"> <Grid item xs={1.6} align="center">
<IconButton onClick={this.handleClickOpenExchangeSummary} > <IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.active_robots_today} color="action"> <Badge badgeContent={this.state.active_robots_today} color="action">
<SmartToyIcon /> <SmartToyIcon />
@ -398,7 +474,7 @@ bottomBarPhone =()=>{
</IconButton> </IconButton>
</Grid> </Grid>
<Grid item xs={2} align="center"> <Grid item xs={1.8} align="center">
<IconButton onClick={this.handleClickOpenExchangeSummary} > <IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.today_avg_nonkyc_btc_premium+"%"} color="action"> <Badge badgeContent={this.state.today_avg_nonkyc_btc_premium+"%"} color="action">
<PriceChangeIcon /> <PriceChangeIcon />
@ -406,8 +482,8 @@ bottomBarPhone =()=>{
</IconButton> </IconButton>
</Grid> </Grid>
<Grid container item xs={3}> <Grid container item xs={3.8}>
<Grid item xs={4}> <Grid item xs={6}>
<Select <Select
size = 'small' size = 'small'
defaultValue={1} defaultValue={1}
@ -417,11 +493,17 @@ bottomBarPhone =()=>{
<MenuItem value={1}>EN</MenuItem> <MenuItem value={1}>EN</MenuItem>
</Select> </Select>
</Grid> </Grid>
<Grid item xs={4}/> <Grid item xs={3}>
<Grid item xs={4}> <IconButton color="primary"
aria-label="Stats for Nerds"
onClick={this.handleClickOpenStatsForNerds} >
<SettingsIcon />
</IconButton>
</Grid>
<Grid item xs={3}>
<IconButton <IconButton
color="primary" color="primary"
aria-label="Telegram" aria-label="Community"
onClick={this.handleClickOpenCommunity} > onClick={this.handleClickOpenCommunity} >
<PeopleIcon /> <PeopleIcon />
</IconButton> </IconButton>

View File

@ -14,8 +14,7 @@ export default class HomePage extends Component {
return ( return (
<Router > <Router >
<Switch> <Switch>
<Route exact path='/' component={UserGenPage}/> <Route exact path='/' render={(props) => <UserGenPage setAppState={this.props.setAppState}/>}/>
<Route path='/home'><p>You are at the start page</p></Route>
<Route path='/make' component={MakerPage}/> <Route path='/make' component={MakerPage}/>
<Route path='/book' component={BookPage}/> <Route path='/book' component={BookPage}/>
<Route path="/order/:orderId" component={OrderPage}/> <Route path="/order/:orderId" component={OrderPage}/>

View File

@ -27,10 +27,10 @@ export default class InfoDialog extends Component {
received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, received the fiat, then the satoshis are released to Bob. Enjoy your satoshis,
Bob!</p> Bob!</p>
<p>At no point, AdequateAlice01 and BafflingBob02 have to trust the <p>At no point, AnonymousAlice01 and BafflingBob02 have to trust the
bitcoin to each other. In case they have a conflict, <i>RoboSats</i> staff bitcoin funds to each other. In case they have a conflict, <i>RoboSats</i> staff
will help resolving the dispute. You can find a step-by-step will help resolving the dispute. You can find a step-by-step
description of the trade pipeline in <a href='https://github.com/Reckless-Satoshi/robosats/blob/main/README.md#how-it-works'>'How it works'</a></p> description of the trade pipeline in <a href='https://github.com/Reckless-Satoshi/robosats/blob/main/README.md#how-it-works'>How it works</a></p>
</Typography> </Typography>
<Typography component="h5" variant="h5">What payment methods are accepted?</Typography> <Typography component="h5" variant="h5">What payment methods are accepted?</Typography>

View File

@ -1,7 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material" import { Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material"
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import getFlags from './getFlags' import getFlags from './getFlags'
function getCookie(name) { function getCookie(name) {

View File

@ -45,7 +45,7 @@ export default class UserGenPage extends Component {
.substring(0, length); .substring(0, length);
} }
getGeneratedUser(token) { getGeneratedUser=(token)=>{
fetch('/api/user' + '?token=' + token) fetch('/api/user' + '?token=' + token)
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
@ -57,6 +57,10 @@ export default class UserGenPage extends Component {
bad_request: data.bad_request, bad_request: data.bad_request,
found: data.found, found: data.found,
showRobosat:true, showRobosat:true,
})
&
this.props.setAppState({
nickname: data.nickname,
}); });
}); });
} }
@ -112,11 +116,10 @@ export default class UserGenPage extends Component {
) )
} }
render() { render() {
return ( return (
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} align="center" sx={{width:380}}> <Grid item xs={12} align="center" sx={{width:370}}>
{this.state.showRobosat ? {this.state.showRobosat ?
<div> <div>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">

View File

@ -37,6 +37,11 @@ body {
top: -14px; top: -14px;
} }
.profileNickname {
margin: 0;
left: -22px;
}
.newAvatar { .newAvatar {
background-color:white; background-color:white;
border-radius: 50%; border-radius: 50%;
@ -45,3 +50,15 @@ body {
height: 200px; height: 200px;
width: 200px; width: 200px;
} }
.avatar {
border: 0.5px solid #555;
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
left: 35px;
}
.rotatedAvatar {
transform: scaleX(-1);
border: 0.5px solid #555;
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
}