Merge branch 'add-typescript-support' into main (#103)

This commit is contained in:
Reckless_Satoshi 2022-05-10 11:57:51 -07:00
commit ce0f5f8f74
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
33 changed files with 2971 additions and 1443 deletions

26
frontend/.eslintrc.json Normal file
View File

@ -0,0 +1,26 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"react-hooks"
],
"extends": [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/prop-types": "off"
},
"settings": {
"react": {
"pragma": "React",
"version": "detect"
}
}
}

View File

@ -1,14 +1,15 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
],
"@babel/preset-react"
],
"plugins": ["@babel/plugin-proposal-class-properties"]
}
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack --mode development --watch",
"dev": "webpack --watch --progress --mode development",
"test": "jest",
"build": "webpack --mode production"
},
@ -12,16 +12,27 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.16.7",
"@babel/preset-env": "^7.16.7",
"@babel/core": "^7.17.9",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"babel-loader": "^8.2.3",
"@babel/preset-typescript": "^7.16.7",
"@babel/runtime": "^7.17.9",
"@types/jest": "^27.4.1",
"@types/react": "^17.0.44",
"@types/react-dom": "^18.0.1",
"@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"babel-loader": "^8.2.5",
"eslint": "^8.13.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
"jest": "^27.5.1",
"openpgp": "^5.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
"ts-node": "^10.7.0",
"typescript": "^4.6.3",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.16.7",
@ -29,6 +40,7 @@
"@emotion/styled": "^11.6.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@mui/base": "^5.0.0-alpha.77",
"@mui/icons-material": "^5.2.5",
"@mui/lab": "^5.0.0-alpha.73",
"@mui/material": "^5.2.7",
@ -41,7 +53,10 @@
"i18next-http-backend": "^1.4.0",
"i18next-xhr-backend": "^3.2.2",
"material-ui-image": "^3.3.2",
"openpgp": "^5.2.1",
"react": "^18.0.0",
"react-countdown": "^2.3.2",
"react-dom": "^18.1.0",
"react-i18next": "^11.16.2",
"react-native": "^0.66.4",
"react-native-svg": "^12.3.0",

View File

@ -1,5 +1,5 @@
import React, { Component } from "react";
import { render } from "react-dom";
import ReactDOM from 'react-dom/client';
import HomePage from "./HomePage";
import { CssBaseline, IconButton} from "@mui/material";
import { ThemeProvider, createTheme } from '@mui/material/styles';
@ -46,5 +46,8 @@ export default class App extends Component {
}
}
const appDiv = document.getElementById("app");
render(<App />, appDiv);
const root = ReactDOM.createRoot(
document.getElementById("app")
);
root.render(<App />);

View File

@ -2,15 +2,16 @@ import React, { useState } from "react";
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import { styled } from '@mui/material/styles';
import PaymentIcon from './payment-methods/Icons'
import {Button} from "@mui/material";
import { Button, Tooltip } from "@mui/material";
import { paymentMethods, swapDestinations } from "./payment-methods/Methods";
// Icons
import DashboardCustomizeIcon from '@mui/icons-material/DashboardCustomize';
import AddIcon from '@mui/icons-material/Add';
import PaymentIcon from './payment-methods/Icons'
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
const Root = styled('div')(
({ theme }) => `
@ -44,7 +45,7 @@ const InputWrapper = styled('div')(
display: flex;
flex-wrap: wrap;
overflow-y:auto;
&:hover {
border-color: ${theme.palette.mode === 'dark' ? (error? '#f44336':'#ffffff') : (error? '#dd0000' :'#2f2f2f')};
}
@ -191,7 +192,7 @@ const Listbox = styled('ul')(
);
export default function AutocompletePayments(props) {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const {
getRootProps,
getInputLabelProps,
@ -233,26 +234,28 @@ export default function AutocompletePayments(props) {
if(a || a == null){props.onAutocompleteChange(optionsToString(value))}
return false
};
return (
<Root>
<div style={{height:'5px'}}></div>
<div {...getRootProps()} >
<Label {...getInputLabelProps()} error={props.error}>{props.label}</Label>
<InputWrapper ref={setAnchorEl} error={props.error} className={focused ? 'focused' : ''}>
{value.map((option, index) => (
<StyledTag label={t(option.name)} icon={option.icon} {...getTagProps({ index })} />
))}
<input {...getInputProps()} value={val}/>
</InputWrapper>
</div>
<Tooltip placement="top" enterTouchDelay={300} enterDelay={700} enterNextDelay={2000} title={props.tooltipTitle}>
<div {...getRootProps()} >
<Label {...getInputLabelProps()} error={props.error ? "error" : null}> {props.label}</Label>
<InputWrapper ref={setAnchorEl} error={props.error ? "error" : null} className={focused ? 'focused' : ''}>
{value.map((option, index) => (
<StyledTag label={t(option.name)} icon={option.icon} {...getTagProps({ index })} />
))}
<input {...getInputProps()} value={val ? val :""}/>
</InputWrapper>
</div>
</Tooltip>
{groupedOptions.length > 0 ? (
<Listbox {...getListboxProps()}>
<div style={{position:'fixed', minHeight:'20px', marginLeft: 120-props.listHeaderText.length*3, marginTop: '-13px'}}>
<ListHeader ><i>{props.listHeaderText+""} </i> </ListHeader>
</div>
{groupedOptions.map((option, index) => (
<li {...getOptionProps({ option, index })}>
<li key={option.name} {...getOptionProps({ option, index })}>
<Button fullWidth={true} color='inherit' size="small" sx={{textTransform: "none"}} style={{justifyContent: "flex-start"}}>
<div style={{position:'relative', right: '4px', top:'4px'}}>
<AddIcon style={{color : '#1976d2'}} sx={{width:18,height:18}} />
@ -268,7 +271,7 @@ export default function AutocompletePayments(props) {
:null)
:null}
</Listbox>
) :
) :
//Here goes what happens if there is no groupedOptions
(getInputProps().value.length > 0 ?
<Listbox {...getListboxProps()}>
@ -278,4 +281,4 @@ export default function AutocompletePayments(props) {
}
</Root>
);
}
}

View File

@ -1,5 +1,5 @@
import React, { Component } from "react";
import { withTranslation, Trans} from "react-i18next";
import { withTranslation } from "react-i18next";
import { Badge, Tooltip, Stack, Paper, Button, FormControlLabel, Checkbox, RadioGroup, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, ListItemText, ListItemAvatar, IconButton, CircularProgress} from "@mui/material";
import { Link } from 'react-router-dom'
import { DataGrid } from '@mui/x-data-grid';
@ -7,7 +7,7 @@ import currencyDict from '../../static/assets/currencies.json';
import MediaQuery from 'react-responsive'
import Image from 'material-ui-image'
import getFlags from './getFlags'
import FlagWithProps from './FlagWithProps'
import { pn } from "../utils/prettyNumbers";
import PaymentText from './PaymentText'
@ -63,7 +63,7 @@ class BookPage extends Component {
}
}
// Colors for the status badges
statusBadgeColor(status){
if(status=='Active'){return("success")}
@ -90,7 +90,7 @@ class BookPage extends Component {
.map((order) =>
({id: order.id,
avatar: window.location.origin +'/static/assets/avatars/' + order.maker_nick + '.png',
robot: order.maker_nick,
robot: order.maker_nick,
robot_status: order.maker_status,
type: order.type ? t("Seller"): t("Buyer"),
amount: order.amount,
@ -106,17 +106,17 @@ class BookPage extends Component {
loading={this.props.bookLoading}
columns={[
// { field: 'id', headerName: 'ID', width: 40 },
{ field: 'robot', headerName: t("Robot"), width: 240,
{ field: 'robot', headerName: t("Robot"), width: 240,
renderCell: (params) => {return (
<ListItemButton style={{ cursor: "pointer" }}>
<ListItemAvatar>
<Tooltip placement="right" enterTouchDelay="0" title={t(params.row.robot_status)}>
<Tooltip placement="right" enterTouchDelay={0} title={t(params.row.robot_status)}>
<Badge variant="dot" overlap="circular" badgeContent="" color={this.statusBadgeColor(params.row.robot_status)}>
<Badge overlap="circular" anchorOrigin={{horizontal: 'right', vertical: 'bottom'}} badgeContent={<div style={{position:"relative", left:"11px", top:"2px"}}> {params.row.type == t("Buyer") ? <SendReceiveIcon sx={{transform: "scaleX(-1)",height:"20px",width:"20px"}} color="secondary"/> : <SendReceiveIcon sx={{height:"20px",width:"20px"}} color="primary"/>}</div>}>
<div style={{ width: 45, height: 45 }}>
<Image className='bookAvatar'
disableError='true'
disableSpinner='true'
<Image className='bookAvatar'
disableError={true}
disableSpinner={true}
color='null'
alt={params.row.robot}
src={params.row.avatar}
@ -135,9 +135,13 @@ class BookPage extends Component {
renderCell: (params) => {return (
<div style={{ cursor: "pointer" }}>{this.amountToString(params.row.amount,params.row.has_range, params.row.min_amount, params.row.max_amount)}</div>
)}},
{ field: 'currency', headerName: t("Currency"), width: 100,
{ field: 'currency', headerName: t("Currency"), width: 100,
renderCell: (params) => {return (
<div style={{ cursor: "pointer", display:'flex',alignItems:'center', flexWrap:'wrap'}}>{params.row.currency+" "}{getFlags(params.row.currency)}</div>)
<div style={{ cursor: "pointer", display:'flex',alignItems:'center', flexWrap:'wrap'}}>
{params.row.currency+" "}
<FlagWithProps code={params.row.currency} />
</div>
)
}},
{ field: 'payment_method', headerName: t("Payment Method"), width: 180 ,
renderCell: (params) => {return (
@ -152,12 +156,12 @@ class BookPage extends Component {
<div style={{ cursor: "pointer" }}>{parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }</div>
)} },
]}
components={{
NoRowsOverlay: () => (
<Stack height="100%" alignItems="center" justifyContent="center">
<div style={{ height:"220px"}}/>
<this.NoOrdersFound/>
{this.NoOrdersFound()}
</Stack>
),
NoResultsOverlay: () => (
@ -167,7 +171,7 @@ class BookPage extends Component {
)
}}
pageSize={this.props.bookLoading ? 0 : this.state.pageSize}
rowsPerPageOptions={[6,20,50]}
rowsPerPageOptions={[0,6,20,50]}
onPageSizeChange={(newPageSize) => this.setState({pageSize:newPageSize})}
onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
/>
@ -187,7 +191,7 @@ class BookPage extends Component {
.map((order) =>
({id: order.id,
avatar: window.location.origin +'/static/assets/avatars/' + order.maker_nick + '.png',
robot: order.maker_nick,
robot: order.maker_nick,
robot_status: order.maker_status,
type: order.type ? t("Seller"): t("Buyer"),
amount: order.amount,
@ -203,17 +207,17 @@ class BookPage extends Component {
columns={[
// { field: 'id', headerName: 'ID', width: 40 },
{ field: 'robot', headerName: t("Robot"), width: 64,
{ field: 'robot', headerName: t("Robot"), width: 64,
renderCell: (params) => {return (
<div style={{ position: "relative", left: "-5px" }}>
<Tooltip placement="right" enterTouchDelay="0" title={params.row.robot+" ("+t(params.row.robot_status)+")"}>
<Tooltip placement="right" enterTouchDelay={0} title={params.row.robot+" ("+t(params.row.robot_status)+")"}>
<Badge variant="dot" overlap="circular" badgeContent="" color={this.statusBadgeColor(params.row.robot_status)}>
<Badge overlap="circular" anchorOrigin={{horizontal: 'right', vertical: 'bottom'}} badgeContent={<div style={{position:"relative", left:"11px", top:"2px"}}> {params.row.type == t("Buyer") ? <SendReceiveIcon sx={{transform: "scaleX(-1)",height:"20px",width:"20px"}} color="secondary"/> : <SendReceiveIcon sx={{height:"20px",width:"20px"}} color="primary"/>}</div>}>
<div style={{ width: 45, height: 45 }}>
<Image className='bookAvatar'
disableError='true'
disableSpinner='true'
color='null'
<Image className='bookAvatar'
disableError={true}
disableSpinner={true}
color={null}
alt={params.row.robot}
src={params.row.avatar}
/>
@ -225,16 +229,19 @@ class BookPage extends Component {
);
} },
{ field: 'type', headerName: t("Is"), width: 60, hide:'true'},
{ field: 'amount', headerName: t("Amount"), type: 'number', width: 84,
{ field: 'amount', headerName: t("Amount"), type: 'number', width: 84,
renderCell: (params) => {return (
<Tooltip placement="right" enterTouchDelay="0" title={t(params.row.type)}>
<Tooltip placement="right" enterTouchDelay={0} title={t(params.row.type)}>
<div style={{ cursor: "pointer" }}>{this.amountToString(params.row.amount,params.row.has_range, params.row.min_amount, params.row.max_amount)}</div>
</Tooltip>
)} },
{ field: 'currency', headerName: t("Currency"), width: 85,
{ field: 'currency', headerName: t("Currency"), width: 85,
renderCell: (params) => {return (
// <Tooltip placement="left" enterTouchDelay="0" title={params.row.payment_method}>
<div style={{ cursor: "pointer", display:'flex',alignItems:'center', flexWrap:'wrap'}}>{params.row.currency+" "}{getFlags(params.row.currency)}</div>
// <Tooltip placement="left" enterTouchDelay={0} title={params.row.payment_method}>
<div style={{ cursor: "pointer", display:'flex',alignItems:'center', flexWrap:'wrap'}}>
{params.row.currency+" "}
<FlagWithProps code={params.row.currency} />
</div>
// </Tooltip>
)} },
{ field: 'payment_method', headerName: t("Payment Method"), width: 180, hide:'true'},
@ -248,17 +255,17 @@ class BookPage extends Component {
)} },
{ field: 'premium', headerName: t("Premium"), type: 'number', width: 85,
renderCell: (params) => {return (
<Tooltip placement="left" enterTouchDelay="0" title={pn(params.row.price) + " " +params.row.currency+ "/BTC" }>
<Tooltip placement="left" enterTouchDelay={0} title={pn(params.row.price) + " " +params.row.currency+ "/BTC" }>
<div style={{ cursor: "pointer" }}>{parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }</div>
</Tooltip>
)} },
]}
components={{
NoRowsOverlay: () => (
<Stack height="100%" alignItems="center" justifyContent="center">
<div style={{ height:"220px"}}/>
<this.NoOrdersFound/>
{this.NoOrdersFound()}
</Stack>
),
NoResultsOverlay: () => (
@ -268,7 +275,7 @@ class BookPage extends Component {
)
}}
pageSize={this.props.bookLoading ? 0 : this.state.pageSize}
rowsPerPageOptions={[6,20,50]}
rowsPerPageOptions={[0,6,20,50]}
onPageSizeChange={(newPageSize) => this.setState({pageSize:newPageSize})}
onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
@ -304,6 +311,7 @@ class BookPage extends Component {
NoOrdersFound=()=>{
const { t } = this.props;
return(
<Grid item xs={12} align="center">
<Grid item xs={12} align="center">
@ -319,7 +327,7 @@ class BookPage extends Component {
<Grid item>
<Button size="large" variant="contained" color='primary' to='/make/' component={Link}>{t("Make Order")}</Button>
</Grid>
<Typography color="primary" component="body1" variant="body1">
<Typography color="primary" variant="body1">
{t("Be the first one to create an order")}
<br/>
<br/>
@ -338,18 +346,18 @@ class BookPage extends Component {
<Grid item xs={6} align="right">
<FormControl align="center">
<FormHelperText align="center">
<div style={{position:"relative", left:"10px", textAlign:"center"}}>{t("I want to")} </div>
<FormHelperText align="center" sx={{position:"relative", left:"10px", textAlign:"center"}}>
{t("I want to")}
</FormHelperText>
<RadioGroup row>
<div style={{position:"relative", left:"20px"}}>
<FormControlLabel
control={<Checkbox defaultChecked={true} icon={<BuySatsIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<BuySatsCheckedIcon sx={{width:"30px",height:"30px"}} color="primary"/>}/>}
control={<Checkbox icon={<BuySatsIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<BuySatsCheckedIcon sx={{width:"30px",height:"30px"}} color="primary"/>}/>}
label={
<div style={{position:"relative",top:"-13px"}}>
{this.props.buyChecked ?
{this.props.buyChecked ?
<Typography variant="caption" color="primary"><b>{t("Buy")}</b></Typography>
:
:
<Typography variant="caption" color="text.secondary">{t("Buy")}</Typography>
}
</div>
@ -360,12 +368,12 @@ class BookPage extends Component {
/>
</div>
<FormControlLabel
control={<Checkbox defaultChecked={true} icon={<SellSatsIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<SellSatsCheckedIcon sx={{width:"30px",height:"30px"}} color="secondary"/>}/>}
control={<Checkbox icon={<SellSatsIcon sx={{width:"30px",height:"30px"}} color="inherit"/>} checkedIcon={<SellSatsCheckedIcon sx={{width:"30px",height:"30px"}} color="secondary"/>}/>}
label={
<div style={{position:"relative",top:"-13px"}}>
{this.props.sellChecked ?
{this.props.sellChecked ?
<Typography variant="caption" color="secondary"><b>{t("Sell")}</b></Typography>
:
:
<Typography variant="caption" color="text.secondary">{t("Sell")}</Typography>
}
</div>
@ -380,25 +388,23 @@ class BookPage extends Component {
<Grid item xs={6} align="left">
<FormControl align="center">
<FormHelperText align="center">
<div style={{textAlign:"center", position:"relative", left:"-5px"}}>
<FormHelperText align="center" sx={{textAlign:"center", position:"relative", left:"-5px"}}>
{this.props.bookType == 0 ? t("and receive") : (this.props.bookType == 1 ? t("and pay with") : t("and use") )}
</div>
</FormHelperText>
<Select
//autoWidth={true}
sx={{width:120}}
label={t("Select Payment Currency")}
required="true"
value={this.props.bookCurrency}
required={true}
value={this.props.bookCurrency}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handleCurrencyChange}
> <MenuItem value={0}><div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>{getFlags('ANY')}{" "+t("ANY_currency")}</div></MenuItem>
> <MenuItem value={0}><div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}><FlagWithProps code="ANY" />{" "+t("ANY_currency")}</div></MenuItem>
{
Object.entries(currencyDict)
.map( ([key, value]) => <MenuItem value={parseInt(key)}><div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>{getFlags(value)}{" "+value}</div></MenuItem> )
.map( ([key, value]) => <MenuItem key={key} value={parseInt(key)}><div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}><FlagWithProps code={value} />{" "+value}</div></MenuItem> )
}
</Select>
</FormControl>
@ -406,34 +412,34 @@ class BookPage extends Component {
{ this.props.bookNotFound ? "" :
<Grid item xs={12} align="center">
<Typography component="h5" variant="h5">
{this.props.bookType == 0 ?
t("You are SELLING BTC for {{currencyCode}}",{currencyCode:this.props.bookCurrencyCode})
:
(this.props.bookType == 1 ?
{this.props.bookType == 0 ?
t("You are SELLING BTC for {{currencyCode}}",{currencyCode:this.props.bookCurrencyCode})
:
(this.props.bookType == 1 ?
t("You are BUYING BTC for {{currencyCode}}",{currencyCode:this.props.bookCurrencyCode})
:
t("You are looking at all")
)
}
}
</Typography>
</Grid>
}
{ this.props.bookNotFound ?
<this.NoOrdersFound/>
:
:
<Grid item xs={12} align="center">
{/* Desktop Book */}
<MediaQuery minWidth={930}>
<Paper elevation={0} style={{width: 925, maxHeight: 500, overflow: 'auto'}}>
<this.bookListTableDesktop/>
{this.bookListTableDesktop()}
</Paper>
</MediaQuery>
{/* Smartphone Book */}
<MediaQuery maxWidth={929}>
<Paper elevation={0} style={{width: 395, maxHeight: 450, overflow: 'auto'}}>
<this.bookListTablePhone/>
{this.bookListTablePhone()}
</Paper>
</MediaQuery>
</Grid>
@ -445,7 +451,7 @@ class BookPage extends Component {
</Grid>
</Grid>
);
};
}
}
export default withTranslation()(BookPage);
export default withTranslation()(BookPage);

View File

@ -1,9 +1,8 @@
import React, { Component } from 'react'
import { withTranslation, Trans} from "react-i18next";
import { withTranslation } from "react-i18next";
import {FormControlLabel, Link, Switch, CircularProgress, Badge, Tooltip, TextField, ListItemAvatar, Button, Avatar,Paper, Grid, IconButton, Typography, Select, MenuItem, List, ListItemText, ListItem, ListItemIcon, ListItemButton, Divider, Dialog, DialogContent} from "@mui/material";
import MediaQuery from 'react-responsive'
import { Link as LinkRouter } from 'react-router-dom'
import Flags from 'country-flag-icons/react/3x2'
// Icons
import SettingsIcon from '@mui/icons-material/Settings';
@ -16,7 +15,7 @@ import PriceChangeIcon from '@mui/icons-material/PriceChange';
import BoltIcon from '@mui/icons-material/Bolt';
import GitHubIcon from '@mui/icons-material/GitHub';
import EqualizerIcon from '@mui/icons-material/Equalizer';
import SendIcon from '@mui/icons-material/Send';
import PublicIcon from '@mui/icons-material/Public';
import NumbersIcon from '@mui/icons-material/Numbers';
import PasswordIcon from '@mui/icons-material/Password';
@ -29,6 +28,8 @@ import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
import AmbossIcon from "./icons/AmbossIcon";
import FavoriteIcon from '@mui/icons-material/Favorite';
import { CommunityDialog } from './Dialogs';
import { getCookie } from "../utils/cookies";
import { pn } from "../utils/prettyNumbers";
@ -62,8 +63,11 @@ class BottomBar extends Component {
showRewardsSpinner: false,
withdrawn: false,
};
}
componentDidMount() {
this.getInfo();
}
}
getInfo() {
this.setState(null)
@ -179,62 +183,6 @@ class BottomBar extends Component {
this.setState({openCommuniy: false});
};
CommunityDialog =() =>{
const { t } = this.props;
return(
<Dialog
open={this.state.openCommuniy}
onClose={this.handleClickCloseCommunity}
aria-labelledby="community-dialog-title"
aria-describedby="community-description"
>
<DialogContent>
<Typography component="h5" variant="h5">{t("Community")}</Typography>
<Typography component="body2" variant="body2">
<p>{t("Support is only offered via public channels. Join our Telegram community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!")}</p>
</Typography>
<List>
<Divider/>
<ListItemButton component="a" target="_blank" href="https://t.me/robosats">
<ListItemIcon><SendIcon/></ListItemIcon>
<ListItemText primary={t("Join the RoboSats group")}
secondary={t("Telegram (English / Main)")}/>
</ListItemButton>
<Divider/>
<ListItem>
<ListItemIcon><SendIcon/></ListItemIcon>
<ListItemText secondary={t("RoboSats Telegram Communities")}>
<Tooltip title={t("Join RoboSats Spanish speaking community!")}>
<IconButton component="a" target="_blank" href="https://t.me/robosats_es"><Flags.ES width={30} height={30} style={{filter: 'drop-shadow(2px 2px 2px #444444)'}}/></IconButton>
</Tooltip>
<Tooltip title={t("Join RoboSats Russian speaking community!")}>
<IconButton component="a" target="_blank" href="https://t.me/robosats_ru"><Flags.RU width={30} height={30} style={{filter: 'drop-shadow(2px 2px 2px #444444)'}}/></IconButton>
</Tooltip>
<Tooltip title={t("Join RoboSats Chinese speaking community!")}>
<IconButton component="a" target="_blank" href="https://t.me/robosats_cn"><Flags.CN width={30} height={30} style={{filter: 'drop-shadow(2px 2px 2px #444444)'}}/></IconButton>
</Tooltip>
<Tooltip title={t("Join RoboSats English speaking community!")}>
<IconButton component="a" target="_blank" href="https://t.me/robosats"><Flags.US width={30} height={30} style={{filter: 'drop-shadow(2px 2px 2px #444444)'}}/></IconButton>
</Tooltip>
</ListItemText>
</ListItem>
<Divider/>
<ListItemButton component="a" target="_blank" href="https://github.com/Reckless-Satoshi/robosats/issues">
<ListItemIcon><GitHubIcon/></ListItemIcon>
<ListItemText primary={t("Tell us about a new feature or a bug")}
secondary={t("Github Issues - The Robotic Satoshis Open Source Project")}/>
</ListItemButton>
</List>
</DialogContent>
</Dialog>
)
}
handleClickOpenProfile = () => {
this.getInfo();
this.setState({openProfile: true, profileShown: true});
@ -349,7 +297,7 @@ class BottomBar extends Component {
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay="0" title={t("Copied!")}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick= {()=> (navigator.clipboard.writeText(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))}>
<ContentCopy color={this.props.copiedToken ? "inherit" : "primary"}/>
</IconButton>
@ -363,7 +311,7 @@ class BottomBar extends Component {
<Divider/>
<Grid spacing={1} align="center">
<Grid item align="center">
<FormControlLabel labelPlacement="start"control={
<Switch
checked={this.state.showRewards}
@ -384,7 +332,7 @@ class BottomBar extends Component {
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay="0" title={t("Copied!")}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick= {()=>navigator.clipboard.writeText('http://'+this.getHost()+'/ref/'+this.state.referral_code)}>
<ContentCopy />
</IconButton>
@ -400,7 +348,7 @@ class BottomBar extends Component {
</ListItemIcon>
{!this.state.openClaimRewards ?
<ListItemText secondary={t("Your earned rewards")}>
<Grid container xs={12}>
<Grid container>
<Grid item xs={9}>
<Typography>{this.state.earned_rewards+" Sats"}</Typography>
</Grid>
@ -454,33 +402,36 @@ class BottomBar extends Component {
bottomBarDesktop =()=>{
const { t } = this.props;
var hasRewards = this.state.earned_rewards > 0 ? true: false;
var hasOrder = this.state.active_order_id > 0 & !this.state.profileShown & this.props.avatarLoaded ? true : false;
return(
<Paper elevation={6} style={{height:40}}>
<this.StatsDialog/>
<this.CommunityDialog/>
<this.dialogProfile/>
<this.exchangeSummaryDialog/>
<Grid container xs={12}>
{this.StatsDialog()}
{this.dialogProfile()}
{this.exchangeSummaryDialog()}
<Grid container>
<Grid item xs={1.9}>
<div style={{display: this.props.avatarLoaded ? '':'none'}}>
<ListItemButton onClick={this.handleClickOpenProfile} >
<Tooltip open={this.state.earned_rewards > 0 ? true: false} title={t("You can claim satoshis!")}>
<Tooltip open={(this.state.active_order_id > 0 & !this.state.profileShown & this.props.avatarLoaded) ? true: false}
title={t("You have an active order")}>
<Tooltip
open={hasRewards || hasOrder}
title={(hasRewards ? t("You can claim satoshis!")+" ": "" )+
(hasOrder ? t("You have an active order"):"")}
>
<ListItemAvatar sx={{ width: 30, height: 30 }} >
<Badge badgeContent={(this.state.active_order_id > 0 & !this.state.profileShown) ? "": null} color="primary">
<Avatar className='flippedSmallAvatar' sx={{margin: 0, top: -13}}
alt={this.props.nickname}
imgProps={{
onLoad:() => this.props.setAppState({avatarLoaded: true}),
}}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
/>
alt={this.props.nickname}
imgProps={{
onLoad:() => this.props.setAppState({avatarLoaded: true}),
}}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
/>
</Badge>
</ListItemAvatar>
</Tooltip>
</Tooltip>
<ListItemText primary={this.props.nickname}/>
</ListItemButton>
</div>
@ -553,10 +504,10 @@ bottomBarDesktop =()=>{
<Grid container item xs={1}>
<Grid item xs={6}>
<this.LangSelect/>
{this.LangSelect()}
</Grid>
<Grid item xs={3}>
<Tooltip enterTouchDelay="250" title={t("Show community and support links")}>
<Tooltip enterTouchDelay={250} title={t("Show community and support links")}>
<IconButton
color="primary"
aria-label="Community"
@ -566,7 +517,7 @@ bottomBarDesktop =()=>{
</Tooltip>
</Grid>
<Grid item xs={3}>
<Tooltip enterTouchDelay="250" title={t("Show stats for nerds")}>
<Tooltip enterTouchDelay={250} title={t("Show stats for nerds")}>
<IconButton color="primary"
aria-label="Stats for Nerds"
onClick={this.handleClickOpenStatsForNerds} >
@ -690,7 +641,7 @@ bottomBarDesktop =()=>{
<ListItemIcon size="small">
<PercentIcon/>
</ListItemIcon>
<Grid container xs={12}>
<Grid container >
<Grid item xs={6}>
<ListItemText
primaryTypographyProps={{fontSize: '14px'}}
@ -718,19 +669,20 @@ bottomBarDesktop =()=>{
bottomBarPhone =()=>{
const { t } = this.props;
var hasRewards = this.state.earned_rewards > 0 ? true: false;
var hasOrder = this.state.active_order_id > 0 & !this.state.profileShown & this.props.avatarLoaded ? true : false;
return(
<Paper elevation={6} style={{height:40}}>
<this.StatsDialog/>
<this.CommunityDialog/>
<this.exchangeSummaryDialog/>
<this.dialogProfile/>
<Grid container xs={12}>
{this.StatsDialog()}
{this.exchangeSummaryDialog()}
{this.dialogProfile()}
<Grid container>
<Grid item xs={1.6}>
<div style={{display: this.props.avatarLoaded ? '':'none'}}>
<Tooltip open={this.state.earned_rewards > 0 ? true: false} title={t("You can claim satoshis!")}>
<Tooltip open={(this.state.active_order_id > 0 & !this.state.profileShown & this.props.avatarLoaded) ? true: false}
title={t("You have an active order")}>
<Tooltip open={hasRewards || hasOrder}
title={(hasRewards ? t("You can claim satoshis!")+" ": "" )+
(hasOrder ? t("You have an active order"):"")}>
<IconButton onClick={this.handleClickOpenProfile} sx={{margin: 0, bottom: 17, right: 8}} >
<Badge badgeContent={(this.state.active_order_id >0 & !this.state.profileShown) ? "": null} color="primary">
<Avatar className='phoneFlippedSmallAvatar'
@ -744,12 +696,11 @@ bottomBarPhone =()=>{
</Badge>
</IconButton>
</Tooltip>
</Tooltip>
</div>
</Grid>
<Grid item xs={1.6} align="center">
<Tooltip enterTouchDelay="300" title={t("Number of public BUY orders")}>
<Tooltip enterTouchDelay={300} title={t("Number of public BUY orders")}>
<IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.num_public_buy_orders} color="action">
<InventoryIcon />
@ -759,7 +710,7 @@ bottomBarPhone =()=>{
</Grid>
<Grid item xs={1.6} align="center">
<Tooltip enterTouchDelay="300" title={t("Number of public SELL orders")}>
<Tooltip enterTouchDelay={300} title={t("Number of public SELL orders")}>
<IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.num_public_sell_orders} color="action">
<SellIcon />
@ -769,7 +720,7 @@ bottomBarPhone =()=>{
</Grid>
<Grid item xs={1.6} align="center">
<Tooltip enterTouchDelay="300" title={t("Today active robots")}>
<Tooltip enterTouchDelay={300} title={t("Today active robots")}>
<IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.active_robots_today} color="action">
<SmartToyIcon />
@ -779,7 +730,7 @@ bottomBarPhone =()=>{
</Grid>
<Grid item xs={1.8} align="center">
<Tooltip enterTouchDelay="300" title={t("24h non-KYC bitcoin premium")}>
<Tooltip enterTouchDelay={300} title={t("24h non-KYC bitcoin premium")}>
<IconButton onClick={this.handleClickOpenExchangeSummary} >
<Badge badgeContent={this.state.last_day_nonkyc_btc_premium+"%"} color="action">
<PriceChangeIcon />
@ -790,10 +741,10 @@ bottomBarPhone =()=>{
<Grid container item xs={3.8}>
<Grid item xs={6}>
<this.LangSelect/>
{this.LangSelect()}
</Grid>
<Grid item xs={3}>
<Tooltip enterTouchDelay="250" title={t("Show community and support links")}>
<Tooltip enterTouchDelay={250} title={t("Show community and support links")}>
<IconButton
color="primary"
aria-label="Community"
@ -803,7 +754,7 @@ bottomBarPhone =()=>{
</Tooltip>
</Grid>
<Grid item xs={3}>
<Tooltip enterTouchDelay="250" title={t("Show stats for nerds")}>
<Tooltip enterTouchDelay={250} title={t("Show stats for nerds")}>
<IconButton color="primary"
aria-label="Stats for Nerds"
onClick={this.handleClickOpenStatsForNerds} >
@ -821,15 +772,20 @@ bottomBarPhone =()=>{
render() {
return (
<div>
<CommunityDialog
isOpen={this.state.openCommuniy}
handleClickCloseCommunity={this.handleClickCloseCommunity}
/>
<MediaQuery minWidth={1200}>
<this.bottomBarDesktop/>
{this.bottomBarDesktop()}
</MediaQuery>
<MediaQuery maxWidth={1199}>
<this.bottomBarPhone/>
{this.bottomBarPhone()}
</MediaQuery>
</div>
)
}
}
export default withTranslation()(BottomBar);

View File

@ -75,7 +75,7 @@ class Chat extends Component {
message: this.state.value,
nick: this.props.ur_nick,
}));
this.state.value = ''
this.setState({value: ""});
}
e.preventDefault();
}
@ -84,90 +84,91 @@ class Chat extends Component {
const { t } = this.props;
return (
<Container component="main" maxWidth="xs" >
<Grid container xs={12} spacing={0.5}>
<Grid item xs={0.3}/>
<Grid item xs={5.5}>
<Paper elevation={1} style={this.state.connected ? {backgroundColor: '#e8ffe6'}: {backgroundColor: '#FFF1C5'}}>
<Typography variant='caption' sx={{color: '#111111'}}>
{t("You")+": "}{this.state.connected ? t("connected"): t("disconnected")}
</Typography>
</Paper>
</Grid>
<Grid item xs={0.4}/>
<Grid item xs={5.5}>
<Paper elevation={1} style={this.state.peer_connected ? {backgroundColor: '#e8ffe6'}: {backgroundColor: '#FFF1C5'}}>
<Typography variant='caption' sx={{color: '#111111'}}>
{t("Peer")+": "}{this.state.peer_connected ? t("connected"): t("disconnected")}
</Typography>
</Paper>
</Grid>
<Grid item xs={0.3}/>
</Grid>
<Paper elevation={1} style={{ height: '300px', maxHeight: '300px' , width: '280px' ,overflow: 'auto', backgroundColor: '#F7F7F7' }}>
{this.state.messages.map(message => <>
<Card elevation={5} align="left" >
{/* If message sender is not our nick, gray color, if it is our nick, green color */}
{message.userNick == this.props.ur_nick ?
<CardHeader sx={{color: '#111111'}}
avatar={
<Badge variant="dot" overlap="circular" badgeContent="" color={this.state.connected ? "success" : "error"}>
<Avatar className="flippedSmallAvatar"
alt={message.userNick}
src={window.location.origin +'/static/assets/avatars/' + message.userNick + '.png'}
/>
</Badge>
}
style={{backgroundColor: '#eeeeee'}}
title={message.userNick}
subheader={message.msg}
subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444'}}}
/>
:
<CardHeader sx={{color: '#111111'}}
avatar={
<Badge variant="dot" overlap="circular" badgeContent="" color={this.state.peer_connected ? "success" : "error"}>
<Avatar className="flippedSmallAvatar"
alt={message.userNick}
src={window.location.origin +'/static/assets/avatars/' + message.userNick + '.png'}
/>
</Badge>
}
style={{backgroundColor: '#fafafa'}}
title={message.userNick}
subheader={message.msg}
subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444'}}}
/>}
</Card>
</>)}
<div style={{ float:"left", clear: "both" }} ref={(el) => { this.messagesEnd = el; }}></div>
<Grid container spacing={0.5}>
<Grid item xs={0.3}/>
<Grid item xs={5.5}>
<Paper elevation={1} style={this.state.connected ? {backgroundColor: '#e8ffe6'}: {backgroundColor: '#FFF1C5'}}>
<Typography variant='caption' sx={{color: '#111111'}}>
{t("You")+": "}{this.state.connected ? t("connected"): t("disconnected")}
</Typography>
</Paper>
<form noValidate onSubmit={this.onButtonClicked}>
<Grid containter alignItems="stretch" style={{ display: "flex" }}>
<Grid item alignItems="stretch" style={{ display: "flex"}}>
<TextField
label={t("Type a message")}
variant="standard"
size="small"
helperText={this.state.connected ? null : t("Connecting...")}
value={this.state.value}
onChange={e => {
this.setState({ value: e.target.value });
this.value = this.state.value;
}}
sx={{width: 214}}
/>
</Grid>
<Grid item alignItems="stretch" style={{ display: "flex" }}>
<Button sx={{'width':68}} disabled={!this.state.connected} type="submit" variant="contained" color="primary">{t("Send")} </Button>
</Grid>
</Grid>
</form>
<FormHelperText>
{t("The chat has no memory: if you leave, messages are lost.")} <Link target="_blank" href={t("PGP_guide_url")}> {t("Learn easy PGP encryption.")}</Link>
</FormHelperText>
</Grid>
<Grid item xs={0.4}/>
<Grid item xs={5.5}>
<Paper elevation={1} style={this.state.peer_connected ? {backgroundColor: '#e8ffe6'}: {backgroundColor: '#FFF1C5'}}>
<Typography variant='caption' sx={{color: '#111111'}}>
{t("Peer")+": "}{this.state.peer_connected ? t("connected"): t("disconnected")}
</Typography>
</Paper>
</Grid>
<Grid item xs={0.3}/>
</Grid>
<Paper elevation={1} style={{ height: '300px', maxHeight: '300px' , width: '280px' ,overflow: 'auto', backgroundColor: '#F7F7F7' }}>
{this.state.messages.map((message, index) =>
<li style={{listStyleType:"none"}} key={index}>
<Card elevation={5} align="left" >
{/* If message sender is not our nick, gray color, if it is our nick, green color */}
{message.userNick == this.props.ur_nick ?
<CardHeader sx={{color: '#111111'}}
avatar={
<Badge variant="dot" overlap="circular" badgeContent="" color={this.state.connected ? "success" : "error"}>
<Avatar className="flippedSmallAvatar"
alt={message.userNick}
src={window.location.origin +'/static/assets/avatars/' + message.userNick + '.png'}
/>
</Badge>
}
style={{backgroundColor: '#eeeeee'}}
title={message.userNick}
subheader={message.msg}
subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444'}}}
/>
:
<CardHeader sx={{color: '#111111'}}
avatar={
<Badge variant="dot" overlap="circular" badgeContent="" color={this.state.peer_connected ? "success" : "error"}>
<Avatar className="flippedSmallAvatar"
alt={message.userNick}
src={window.location.origin +'/static/assets/avatars/' + message.userNick + '.png'}
/>
</Badge>
}
style={{backgroundColor: '#fafafa'}}
title={message.userNick}
subheader={message.msg}
subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444'}}}
/>}
</Card>
</li>)}
<div style={{ float:"left", clear: "both" }} ref={(el) => { this.messagesEnd = el; }}></div>
</Paper>
<form noValidate onSubmit={this.onButtonClicked}>
<Grid alignItems="stretch" style={{ display: "flex" }}>
<Grid item alignItems="stretch" style={{ display: "flex"}}>
<TextField
label={t("Type a message")}
variant="standard"
size="small"
helperText={this.state.connected ? null : t("Connecting...")}
value={this.state.value}
onChange={e => {
this.setState({ value: e.target.value });
this.value = this.state.value;
}}
sx={{width: 214}}
/>
</Grid>
<Grid item alignItems="stretch" style={{ display: "flex" }}>
<Button sx={{'width':68}} disabled={!this.state.connected} type="submit" variant="contained" color="primary">{t("Send")} </Button>
</Grid>
</Grid>
</form>
<FormHelperText>
{t("The chat has no memory: if you leave, messages are lost.")} <Link target="_blank" href={t("PGP_guide_url")}> {t("Learn easy PGP encryption.")}</Link>
</FormHelperText>
</Container>
)
}
}
export default withTranslation()(Chat);
export default withTranslation()(Chat);

View File

@ -0,0 +1,151 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
DialogContent,
Divider,
IconButton,
List,
ListItemText,
ListItem,
ListItemIcon,
ListItemButton,
Tooltip,
Typography,
} from "@mui/material";
import SendIcon from '@mui/icons-material/Send';
import GitHubIcon from '@mui/icons-material/GitHub';
import Flags from 'country-flag-icons/react/3x2'
type Props = {
isOpen: boolean;
handleClickCloseCommunity: () => void;
}
const CommunityDialog = ({
isOpen,
handleClickCloseCommunity,
}: Props): JSX.Element => {
const { t } = useTranslation();
const flagProps = {
width: 30,
height: 30,
style: {
filter: "drop-shadow(2px 2px 2px #444444)",
},
};
return (
<Dialog
open={isOpen}
onClose={handleClickCloseCommunity}
aria-labelledby="community-dialog-title"
aria-describedby="community-description"
>
<DialogContent>
<Typography component="h5" variant="h5">
{t("Community")}
</Typography>
<Typography component="div" variant="body2">
<p>{t("Support is only offered via public channels. Join our Telegram community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!")}</p>
</Typography>
<List>
<Divider/>
<ListItemButton
component="a"
target="_blank"
href="https://t.me/robosats"
rel="noreferrer"
>
<ListItemIcon>
<SendIcon/>
</ListItemIcon>
<ListItemText
primary={t("Join the RoboSats group")}
secondary={t("Telegram (English / Main)")}
/>
</ListItemButton>
<Divider/>
<ListItem>
<ListItemIcon>
<SendIcon/>
</ListItemIcon>
<ListItemText secondary={t("RoboSats Telegram Communities")}>
<Tooltip title={t("Join RoboSats Spanish speaking community!") || ""}>
<IconButton
component="a"
target="_blank"
href="https://t.me/robosats_es"
rel="noreferrer"
>
<Flags.ES {...flagProps} />
</IconButton>
</Tooltip>
<Tooltip title={t("Join RoboSats Russian speaking community!") || ""}>
<IconButton
component="a"
target="_blank"
href="https://t.me/robosats_ru"
rel="noreferrer"
>
<Flags.RU {...flagProps} />
</IconButton>
</Tooltip>
<Tooltip title={t("Join RoboSats Chinese speaking community!") || ""}>
<IconButton
component="a"
target="_blank"
href="https://t.me/robosats_cn"
rel="noreferrer"
>
<Flags.CN {...flagProps} />
</IconButton>
</Tooltip>
<Tooltip title={t("Join RoboSats English speaking community!") || ""}>
<IconButton
component="a"
target="_blank"
href="https://t.me/robosats"
rel="noreferrer"
>
<Flags.US {...flagProps} />
</IconButton>
</Tooltip>
</ListItemText>
</ListItem>
<Divider/>
<ListItemButton
component="a"
target="_blank"
href="https://github.com/Reckless-Satoshi/robosats/issues"
rel="noreferrer"
>
<ListItemIcon>
<GitHubIcon/>
</ListItemIcon>
<ListItemText
primary={t("Tell us about a new feature or a bug")}
secondary={t("Github Issues - The Robotic Satoshis Open Source Project")}
/>
</ListItemButton>
</List>
</DialogContent>
</Dialog>
);
};
export default CommunityDialog;

View File

@ -0,0 +1,137 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
Typography,
Link,
DialogActions,
DialogContent,
Button,
Grid,
} from "@mui/material"
import Image from 'material-ui-image'
import MediaQuery from 'react-responsive'
type Props = {
open: boolean;
onClose: () => void;
}
const InfoDialog = ({
open,
onClose,
}: Props): JSX.Element => {
const { t } = useTranslation();
return (
<Dialog
open={open}
onClose={onClose}
aria-labelledby="info-dialog-title"
aria-describedby="info-dialog-description"
scroll="paper">
<DialogContent>
<MediaQuery minWidth={475}>
<Grid container>
<Grid item xs={8}>
<Typography component="h4" variant="h4">{t("What is RoboSats?")}</Typography>
<Typography component="div" variant="body2">
<p>{t("It is a BTC/FIAT peer-to-peer exchange over lightning.")} <br/>
{t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}</p>
<p>{t("RoboSats is an open source project ")} <Link
href='https://github.com/reckless-satoshi/robosats'>{t("(GitHub).")}</Link>
</p>
</Typography>
</Grid>
<Grid item xs={4} align="center">
<Image className='newAvatar'
disableError={true}
cover={true}
color='null'
src={window.location.origin +'/static/assets/images/v0.1.2-04.png'}
/>
</Grid>
</Grid>
</MediaQuery>
<MediaQuery maxWidth={474}>
<Typography component="h4" variant="h4">{t("What is RoboSats?")}</Typography>
<Typography component="div" variant="body2">
<p>{t("It is a BTC/FIAT peer-to-peer exchange over lightning.")+" "} {t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}</p>
<img
width='100%'
src={window.location.origin +'/static/assets/images/v0.1.2-03.png'}
/>
<p>{t("RoboSats is an open source project ")} <Link
href='https://github.com/reckless-satoshi/robosats'>{t("(GitHub).")}</Link>
</p>
</Typography>
</MediaQuery>
<Typography component="h5" variant="h5">{t("How does it work?")}</Typography>
<Typography component="div" variant="body2">
<p> {t("AnonymousAlice01 wants to sell bitcoin. She posts a sell order. BafflingBob02 wants to buy bitcoin and he takes Alice's order. Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning hold invoice. RoboSats locks the invoice until Alice confirms she received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, Bob!")}</p>
<p>{t("At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, RoboSats staff will help resolving the dispute.")}
{t("You can find a step-by-step description of the trade pipeline in ")}
<Link href='https://github.com/Reckless-Satoshi/robosats/blob/main/README.md#how-it-works'>{t("How it works")}</Link>.
{" "+t("You can also check the full guide in ")}
<Link href='https://github.com/Reckless-Satoshi/robosats/blob/main/docs/how-to-use.md'>{t("How to use")}</Link>.</p>
</Typography>
<Typography component="h5" variant="h5">{t("What payment methods are accepted?")}</Typography>
<Typography component="div" variant="body2">
<p>{t("All of them as long as they are fast. You can write down your preferred payment method(s). You will have to match with a peer who also accepts that method. The step to exchange fiat has a expiry time of 24 hours before a dispute is automatically open. We highly recommend using instant fiat payment rails.")} </p>
</Typography>
<Typography component="h5" variant="h5">{t("Are there trade limits?")}</Typography>
<Typography component="div" variant="body2">
<p>{t("Maximum single trade size is {{maxAmount}} Satoshis to minimize lightning routing failure. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously in different browsers (remember to back up your robot tokens!).", {maxAmount: '1,200,000'})} </p>
</Typography>
<Typography component="h5" variant="h5">{t("Is RoboSats private?")}</Typography>
<Typography component="div" variant="body2">
<p> {t("RoboSats will never ask you for your name, country or ID. RoboSats does not custody your funds and does not care who you are. RoboSats does not collect or custody any personal data. For best anonymity use Tor Browser and access the .onion hidden service.")} </p>
<p>{t("Your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.")} </p>
</Typography>
<Typography component="h5" variant="h5">{t("What are the risks?")}</Typography>
<Typography component="div" variant="body2">
<p> {t("This is an experimental application, things could go wrong. Trade small amounts!")}</p>
<p> {t("The seller faces the same charge-back risk as with any other peer-to-peer service. Paypal or credit cards are not recommended.")}</p>
</Typography>
<Typography component="h5" variant="h5">{t("What is the trust model?")}</Typography>
<Typography component="div" variant="body2">
<p> {t("The buyer and the seller never have to trust each other. Some trust on RoboSats is needed since linking the seller's hold invoice and buyer payment is not atomic (yet). In addition, disputes are solved by the RoboSats staff.")}</p>
<p> {t("To be totally clear. Trust requirements are minimized. However, there is still one way RoboSats could run away with your satoshis: by not releasing the satoshis to the buyer. It could be argued that such move is not in RoboSats' interest as it would damage the reputation for a small payout. However, you should hesitate and only trade small quantities at a time. For large amounts use an onchain escrow service such as Bisq")}</p>
<p> {t("You can build more trust on RoboSats by inspecting the source code.")} <Link href='https://github.com/reckless-satoshi/robosats'> {t("Project source code")}</Link>. </p>
</Typography>
<Typography component="h5" variant="h5">{t("What happens if RoboSats suddenly disappears?")}</Typography>
<Typography component="div" variant="body2">
<p> {t("Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if RoboSats goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if RoboSats disappears. This window is about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels.")}</p>
</Typography>
<Typography component="h5" variant="h5">{t("Is RoboSats legal in my country?")}</Typography>
<Typography component="div" variant="body2">
<p> {t("In many countries using RoboSats is no different than using Ebay or Craiglist. Your regulation may vary. It is your responsibility to comply.")}</p>
</Typography>
<Typography component="h5" variant="h5">{t("Disclaimer")}</Typography>
<Typography component="div" variant="body2">
<p> {t("This lightning application is provided as is. It is in active development: trade with the utmost caution. There is no private support. Support is only offered via public channels ")}<Link href='https://t.me/robosats'>{t("(Telegram)")}</Link>{t(". RoboSats will never contact you. RoboSats will definitely never ask for your robot token.")}</p>
</Typography>
<DialogActions>
<Button onClick={onClose}>{t("Close")}</Button>
</DialogActions>
</DialogContent>
</Dialog>
)
}
export default InfoDialog;

View File

@ -0,0 +1,48 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
DialogTitle,
DialogActions,
DialogContent,
DialogContentText,
Button,
} from "@mui/material"
import { Link } from 'react-router-dom'
type Props = {
open: boolean;
onClose: () => void;
}
const NoRobotDialog = ({
open,
onClose,
}: Props): JSX.Element => {
const { t } = useTranslation();
return (
<Dialog
open={open}
onClose={onClose}
>
<DialogTitle>
{t("You do not have a robot avatar")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t("You need to generate a robot avatar in order to become an order maker")}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose} autoFocus>{t("Go back")}</Button>
<Button onClick={onClose} to="/" component={Link}>{t("Generate Robot")}</Button>
</DialogActions>
</Dialog>
)
}
export default NoRobotDialog;

View File

@ -0,0 +1,80 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
DialogTitle,
Tooltip,
IconButton,
TextField,
DialogActions,
DialogContent,
DialogContentText,
Button,
Grid,
} from "@mui/material"
import { getCookie } from "../../utils/cookies";
import ContentCopy from "@mui/icons-material/ContentCopy";
type Props = {
open: boolean;
onClose: () => void;
copyIconColor: string;
onClickCopy: () => void;
onClickBack: () => void;
onClickDone: () => void;
}
const StoreTokenDialog = ({
open,
onClose,
copyIconColor,
onClickCopy,
onClickBack,
onClickDone,
}: Props): JSX.Element => {
const { t } = useTranslation();
return (
<Dialog
open={open}
onClose={onClose}
>
<DialogTitle >
{t("Store your robot token")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t("You might need to recover your robot avatar in the future: store it safely. You can simply copy it into another application.")}
</DialogContentText>
<br/>
<Grid align="center">
<TextField
sx={{width:"100%", maxWidth:"550px"}}
disabled
label={t("Back it up!")}
value={getCookie("robot_token")}
variant='filled'
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick={onClickCopy}>
<ContentCopy color={copyIconColor}/>
</IconButton>
</Tooltip>,
}}
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={onClickBack} autoFocus>{t("Go back")}</Button>
<Button onClick={onClickDone}>{t("Done")}</Button>
</DialogActions>
</Dialog>
)
}
export default StoreTokenDialog;

View File

@ -0,0 +1,4 @@
export { default as CommunityDialog } from "./CommunityDialog";
export { default as InfoDialog } from "./InfoDialog";
export { default as StoreTokenDialog } from "./StoreTokenDialog";
export { default as NoRobotDialog } from "./NoRobotDialog";

View File

@ -0,0 +1,91 @@
import React from "react";
import Flags from 'country-flag-icons/react/3x2'
import SwapCallsIcon from '@mui/icons-material/SwapCalls';
import GoldIcon from '../icons/GoldIcon';
import EarthIcon from '../icons/EarthIcon'
type Props = {
code: string;
}
const FlagWithProps = ({ code }: Props): JSX.Element => {
const defaultProps = {
width: 20,
height: 20,
};
let flag: JSX.Element | null = null;
if(code === 'AUD') flag = <Flags.AU {...defaultProps}/>;
if(code === 'ARS') flag = <Flags.AR {...defaultProps}/>;
if(code === 'BRL') flag = <Flags.BR {...defaultProps}/>;
if(code === 'CAD') flag = <Flags.CA {...defaultProps}/>;
if(code === 'CHF') flag = <Flags.CH {...defaultProps}/>;
if(code === 'CLP') flag = <Flags.CL {...defaultProps}/>;
if(code === 'CNY') flag = <Flags.CN {...defaultProps}/>;
if(code === 'EUR') flag = <Flags.EU {...defaultProps}/>;
if(code === 'HRK') flag = <Flags.HR {...defaultProps}/>;
if(code === 'CZK') flag = <Flags.CZ {...defaultProps}/>;
if(code === 'DKK') flag = <Flags.DK {...defaultProps}/>;
if(code === 'GBP') flag = <Flags.GB {...defaultProps}/>;
if(code === 'HKD') flag = <Flags.HK {...defaultProps}/>;
if(code === 'HUF') flag = <Flags.HU {...defaultProps}/>;
if(code === 'INR') flag = <Flags.IN {...defaultProps}/>;
if(code === 'ISK') flag = <Flags.IS {...defaultProps}/>;
if(code === 'JPY') flag = <Flags.JP {...defaultProps}/>;
if(code === 'KRW') flag = <Flags.KR {...defaultProps}/>;
if(code === 'MXN') flag = <Flags.MX {...defaultProps}/>;
if(code === 'NOK') flag = <Flags.NO {...defaultProps}/>;
if(code === 'NZD') flag = <Flags.NZ {...defaultProps}/>;
if(code === 'PLN') flag = <Flags.PL {...defaultProps}/>;
if(code === 'RON') flag = <Flags.RO {...defaultProps}/>;
if(code === 'RUB') flag = <Flags.RU {...defaultProps}/>;
if(code === 'SEK') flag = <Flags.SE {...defaultProps}/>;
if(code === 'SGD') flag = <Flags.SG {...defaultProps}/>;
if(code === 'VES') flag = <Flags.VE {...defaultProps}/>;
if(code === 'TRY') flag = <Flags.TR {...defaultProps}/>;
if(code === 'USD') flag = <Flags.US {...defaultProps}/>;
if(code === 'ZAR') flag = <Flags.ZA {...defaultProps}/>;
if(code === 'COP') flag = <Flags.CO {...defaultProps}/>;
if(code === 'PEN') flag = <Flags.PE {...defaultProps}/>;
if(code === 'UYU') flag = <Flags.UY {...defaultProps}/>;
if(code === 'PYG') flag = <Flags.PY {...defaultProps}/>;
if(code === 'BOB') flag = <Flags.BO {...defaultProps}/>;
if(code === 'IDR') flag = <Flags.ID {...defaultProps}/>;
if(code === 'ANG') flag = <Flags.CW {...defaultProps}/>;
if(code === 'CRC') flag = <Flags.CR {...defaultProps}/>;
if(code === 'CUP') flag = <Flags.CU {...defaultProps}/>;
if(code === 'DOP') flag = <Flags.DO {...defaultProps}/>;
if(code === 'GHS') flag = <Flags.GH {...defaultProps}/>;
if(code === 'GTQ') flag = <Flags.GT {...defaultProps}/>;
if(code === 'ILS') flag = <Flags.IL {...defaultProps}/>;
if(code === 'JMD') flag = <Flags.JM {...defaultProps}/>;
if(code === 'KES') flag = <Flags.KE {...defaultProps}/>;
if(code === 'KZT') flag = <Flags.KZ {...defaultProps}/>;
if(code === 'MYR') flag = <Flags.MY {...defaultProps}/>;
if(code === 'NAD') flag = <Flags.NA {...defaultProps}/>;
if(code === 'NGN') flag = <Flags.NG {...defaultProps}/>;
if(code === 'AZN') flag = <Flags.AZ {...defaultProps}/>;
if(code === 'PAB') flag = <Flags.PA {...defaultProps}/>;
if(code === 'PHP') flag = <Flags.PH {...defaultProps}/>;
if(code === 'PKR') flag = <Flags.PK {...defaultProps}/>;
if(code === 'QAR') flag = <Flags.QA {...defaultProps}/>;
if(code === 'SAR') flag = <Flags.SA {...defaultProps}/>;
if(code === 'THB') flag = <Flags.TH {...defaultProps}/>;
if(code === 'TTD') flag = <Flags.TT {...defaultProps}/>;
if(code === 'VND') flag = <Flags.VN {...defaultProps}/>;
if(code === 'XOF') flag = <Flags.BJ {...defaultProps}/>;
if(code === 'TWD') flag = <Flags.TW {...defaultProps}/>;
if(code === 'TZS') flag = <Flags.TZ {...defaultProps}/>;
if(code === 'XAF') flag = <Flags.CM {...defaultProps}/>;
if(code === 'UAH') flag = <Flags.UA {...defaultProps}/>;
if(code === 'ANY') flag = <EarthIcon {...defaultProps}/>;
if(code === 'XAU') flag = <GoldIcon {...defaultProps}/>;
if(code === 'BTC') flag = <SwapCallsIcon color="primary"/>;
return (
<div style={{width:28, height: 20}}>{flag}</div>
);
};
export default FlagWithProps;

View File

@ -0,0 +1 @@
export { default } from "./FlagWithProps";

View File

@ -24,7 +24,7 @@ export default class HomePage extends Component {
bookLoading: true,
}
}
setAppState=(newState)=>{
this.setState(newState)
}
@ -51,4 +51,4 @@ export default class HomePage extends Component {
</Router>
);
}
}
}

View File

@ -1,115 +0,0 @@
import React, { Component } from 'react'
import { withTranslation, Trans} from "react-i18next";
import {Typography, Link, DialogActions, DialogContent, Button, Grid} from "@mui/material"
import Image from 'material-ui-image'
import MediaQuery from 'react-responsive'
class InfoDialog extends Component {
render() {
const { t } = this.props;
return (
<div>
<DialogContent>
<MediaQuery minWidth={475}>
<Grid container xs={12}>
<Grid item xs={8}>
<Typography component="h4" variant="h4">{t("What is RoboSats?")}</Typography>
<Typography component="body2" variant="body2">
<p>{t("It is a BTC/FIAT peer-to-peer exchange over lightning.")} <br/>
{t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}</p>
<p>{t("RoboSats is an open source project ")} <Link
href='https://github.com/reckless-satoshi/robosats'>{t("(GitHub).")}</Link>
</p>
</Typography>
</Grid>
<Grid item xs={4} align="center">
<Image className='newAvatar'
disableError='true'
cover='true'
color='null'
src={window.location.origin +'/static/assets/images/v0.1.2-04.png'}
/>
</Grid>
</Grid>
</MediaQuery>
<MediaQuery maxWidth={474}>
<Typography component="h4" variant="h4">{t("What is RoboSats?")}</Typography>
<Typography component="body2" variant="body2">
<p>{t("It is a BTC/FIAT peer-to-peer exchange over lightning.")+" "} {t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}</p>
<img
width='100%'
src={window.location.origin +'/static/assets/images/v0.1.2-03.png'}
/>
<p>{t("RoboSats is an open source project ")} <Link
href='https://github.com/reckless-satoshi/robosats'>{t("(GitHub).")}</Link>
</p>
</Typography>
</MediaQuery>
<Typography component="h5" variant="h5">{t("How does it work?")}</Typography>
<Typography component="body2" variant="body2">
<p> {t("AnonymousAlice01 wants to sell bitcoin. She posts a sell order. BafflingBob02 wants to buy bitcoin and he takes Alice's order. Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning hold invoice. RoboSats locks the invoice until Alice confirms she received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, Bob!")}</p>
<p>{t("At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, RoboSats staff will help resolving the dispute.")}
{t("You can find a step-by-step description of the trade pipeline in ")}
<Link href='https://github.com/Reckless-Satoshi/robosats/blob/main/README.md#how-it-works'>{t("How it works")}</Link>.
{" "+t("You can also check the full guide in ")}
<Link href='https://github.com/Reckless-Satoshi/robosats/blob/main/docs/how-to-use.md'>{t("How to use")}</Link>.</p>
</Typography>
<Typography component="h5" variant="h5">{t("What payment methods are accepted?")}</Typography>
<Typography component="body2" variant="body2">
<p>{t("All of them as long as they are fast. You can write down your preferred payment method(s). You will have to match with a peer who also accepts that method. The step to exchange fiat has a expiry time of 24 hours before a dispute is automatically open. We highly recommend using instant fiat payment rails.")} </p>
</Typography>
<Typography component="h5" variant="h5">{t("Are there trade limits?")}</Typography>
<Typography component="body2" variant="body2">
<p>{t("Maximum single trade size is {{maxAmount}} Satoshis to minimize lightning routing failure. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously in different browsers (remember to back up your robot tokens!).", {maxAmount: '1,200,000'})} </p>
</Typography>
<Typography component="h5" variant="h5">{t("Is RoboSats private?")}</Typography>
<Typography component="body2" variant="body2">
<p> {t("RoboSats will never ask you for your name, country or ID. RoboSats does not custody your funds and does not care who you are. RoboSats does not collect or custody any personal data. For best anonymity use Tor Browser and access the .onion hidden service.")} </p>
<p>{t("Your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.")} </p>
</Typography>
<Typography component="h5" variant="h5">{t("What are the risks?")}</Typography>
<Typography component="body2" variant="body2">
<p> {t("This is an experimental application, things could go wrong. Trade small amounts!")}</p>
<p> {t("The seller faces the same charge-back risk as with any other peer-to-peer service. Paypal or credit cards are not recommended.")}</p>
</Typography>
<Typography component="h5" variant="h5">{t("What is the trust model?")}</Typography>
<Typography component="body2" variant="body2">
<p> {t("The buyer and the seller never have to trust each other. Some trust on RoboSats is needed since linking the seller's hold invoice and buyer payment is not atomic (yet). In addition, disputes are solved by the RoboSats staff.")}</p>
<p> {t("To be totally clear. Trust requirements are minimized. However, there is still one way RoboSats could run away with your satoshis: by not releasing the satoshis to the buyer. It could be argued that such move is not in RoboSats' interest as it would damage the reputation for a small payout. However, you should hesitate and only trade small quantities at a time. For large amounts use an onchain escrow service such as Bisq")}</p>
<p> {t("You can build more trust on RoboSats by inspecting the source code.")} <Link href='https://github.com/reckless-satoshi/robosats'> {t("Project source code")}</Link>. </p>
</Typography>
<Typography component="h5" variant="h5">{t("What happens if RoboSats suddenly disappears?")}</Typography>
<Typography component="body2" variant="body2">
<p> {t("Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if RoboSats goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if RoboSats disappears. This window is about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels.")}</p>
</Typography>
<Typography component="h5" variant="h5">{t("Is RoboSats legal in my country?")}</Typography>
<Typography component="body2" variant="body2">
<p> {t("In many countries using RoboSats is no different than using Ebay or Craiglist. Your regulation may vary. It is your responsibility to comply.")}</p>
</Typography>
<Typography component="h5" variant="h5">{t("Disclaimer")}</Typography>
<Typography component="body2" variant="body2">
<p> {t("This lightning application is provided as is. It is in active development: trade with the utmost caution. There is no private support. Support is only offered via public channels ")}<Link href='https://t.me/robosats'>{t("(Telegram)")}</Link>{t(". RoboSats will never contact you. RoboSats will definitely never ask for your robot token.")}</p>
</Typography>
<DialogActions>
<Button onClick={this.props.handleCloseInfo}>{t("Close")}</Button>
</DialogActions>
</DialogContent>
</div>
)
}
}
export default withTranslation()(InfoDialog);

View File

@ -0,0 +1,26 @@
import React, { useState, useEffect } from "react";
import { Box, LinearProgress } from "@mui/material"
import { calcTimeDelta } from 'react-countdown';
export default function LinearDeterminate(props) {
const [progress, setProgress] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setProgress((oldProgress) => {
var left = calcTimeDelta( new Date(props.expires_at)).total /1000;
return (left / props.total_secs_exp) * 100;
});
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return (
<Box sx={{ width: '100%' }}>
<LinearProgress variant="determinate" value={progress} />
</Box>
);
}

View File

@ -1,12 +1,13 @@
import React, { Component } from 'react';
import { withTranslation } from "react-i18next";
import { InputAdornment, LinearProgress, Dialog, IconButton, DialogActions, DialogContent, DialogContentText, DialogTitle, Accordion, AccordionDetails, AccordionSummary, Checkbox, Slider, Box, Tab, Tabs, SliderThumb, Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material"
import RangeSlider from "./RangeSlider";
import { LocalizationProvider, TimePicker} from '@mui/lab';
import DateFnsUtils from "@date-io/date-fns";
import { Link as LinkRouter } from 'react-router-dom'
import { styled } from '@mui/material/styles';
import { StoreTokenDialog, NoRobotDialog } from "./Dialogs";
import getFlags from './getFlags';
import FlagWithProps from './FlagWithProps';
import AutocompletePayments from './AutocompletePayments';
import currencyDict from '../../static/assets/currencies.json';
@ -18,7 +19,6 @@ import BuySatsIcon from "./icons/BuySatsIcon";
import BuySatsCheckedIcon from "./icons/BuySatsCheckedIcon";
import SellSatsIcon from "./icons/SellSatsIcon";
import SellSatsCheckedIcon from "./icons/SellSatsCheckedIcon";
import ContentCopy from "@mui/icons-material/ContentCopy";
import { getCookie } from "../utils/cookies";
import { pn } from "../utils/prettyNumbers";
@ -41,25 +41,31 @@ class MakerPage extends Component {
minTradeSats: this.defaultMinTradeSats,
maxTradeSats: this.defaultMaxTradeSats,
maxBondlessSats: this.defaultMaxBondlessSats,
tabValue: 0,
openStoreToken: false,
is_explicit: false,
type: null,
currency: this.defaultCurrency,
currencyCode: this.defaultCurrencyCode,
payment_method: this.defaultPaymentMethod,
premium: 0,
satoshis: null,
satoshis: "",
showAdvanced: false,
allowBondless: false,
publicExpiryTime: new Date(0, 0, 0, 23, 59),
escrowExpiryTime: new Date(0, 0, 0, 3, 0),
enableAmountRange: false,
minAmount: null,
bondSize: 1,
limits: null,
minAmount: null,
maxAmount: null,
minAmount: "",
maxAmount: "",
loadingLimits: true,
amount: "",
badPaymentMethod: "",
}
}
componentDidMount() {
this.getLimits()
}
@ -268,7 +274,7 @@ class MakerPage extends Component {
const { t } = this.props;
return(
<Paper elevation={12} style={{ padding: 8, width:'260px', align:'center'}}>
<Grid item xs={12} align="center" spacing={1}>
<Grid item xs={12} align="center">
<div style={{position:'relative', left:'5px'}}>
<FormControl component="fieldset">
<FormHelperText sx={{textAlign:"center"}}>
@ -277,13 +283,13 @@ class MakerPage extends Component {
<RadioGroup row value={this.state.type} onChange={this.handleTypeChange}>
<FormControlLabel
value="0"
value={0}
control={<Radio icon={<BuySatsIcon sx={{width:"30px",height:"30px"}} color="text.secondary"/>} checkedIcon={<BuySatsCheckedIcon sx={{width:"30px",height:"30px"}} color="primary"/>}/>}
label={this.state.type == 0 ? <Typography color="primary"><b>{t("Buy")}</b></Typography>: <Typography color="text.secondary">{t("Buy")}</Typography>}
labelPlacement="end"
/>
<FormControlLabel
value="1"
value={1}
control={<Radio color="secondary" icon={<SellSatsIcon sx={{width:"30px",height:"30px"}} color="text.secondary"/>} checkedIcon={<SellSatsCheckedIcon sx={{width:"30px",height:"30px"}} color="secondary"/>}/>}
label={this.state.type == 1 ? <Typography color="secondary"><b>{t("Sell")}</b></Typography>: <Typography color="text.secondary">{t("Sell")}</Typography>}
labelPlacement="end"
@ -293,18 +299,18 @@ class MakerPage extends Component {
</div>
</Grid>
<Grid containter xs={12} alignItems="stretch" style={{ display: "flex" }}>
<Grid alignItems="stretch" style={{ display: "flex" }}>
<div style={{maxWidth:150}}>
<Tooltip placement="top" enterTouchDelay="500" enterDelay="700" enterNextDelay="2000" title={t("Amount of fiat to exchange for bitcoin")}>
<Tooltip placement="top" enterTouchDelay={500} enterDelay={700} enterNextDelay={2000} title={t("Amount of fiat to exchange for bitcoin")}>
<TextField
disabled = {this.state.enableAmountRange}
variant = {this.state.enableAmountRange ? 'filled' : 'outlined'}
error={(this.state.amount <= this.getMinAmount() || this.state.amount >= this.getMaxAmount()) & this.state.amount != "" }
error={(this.state.amount <= this.getMinAmount() || this.state.amount >= this.getMaxAmount()) & this.state.amount != "" ? true : false}
helperText={this.state.amount <= this.getMinAmount() & this.state.amount != "" ? t("Too low")
: (this.state.amount >= this.getMaxAmount() & this.state.amount != "" ? t("Too high") : null)}
label={t("Amount")}
type="number"
required="true"
required={true}
value={this.state.amount}
inputProps={{
min:0 ,
@ -317,43 +323,40 @@ class MakerPage extends Component {
<div >
<Select
sx={{width:'120px'}}
required="true"
required={true}
defaultValue={this.defaultCurrency}
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handleCurrencyChange}>
{Object.entries(currencyDict)
.map( ([key, value]) => <MenuItem value={parseInt(key)}>
<div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>{getFlags(value)}{" "+value}</div>
.map( ([key, value]) => <MenuItem key={key} value={parseInt(key)}>
<div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}><FlagWithProps code={value}/>{" "+value}</div>
</MenuItem> )}
</Select>
</div>
</Grid>
<Grid item xs={12} align="center">
<Tooltip placement="top" enterTouchDelay="300" enterDelay="700" enterNextDelay="2000" title={t("Enter your preferred fiat payment methods. Fast methods are highly recommended.")}>
<AutocompletePayments
onAutocompleteChange={this.handlePaymentMethodChange}
optionsType={this.state.currency==1000 ? "swap":"fiat"}
error={this.state.badPaymentMethod}
helperText={this.state.badPaymentMethod ? t("Must be shorter than 65 characters"):""}
label={this.state.currency==1000 ? t("Swap Destination(s)") : t("Fiat Payment Method(s)")}
tooltipTitle={t("Enter your preferred fiat payment methods. Fast methods are highly recommended.")}
listHeaderText={t("You can add new methods")}
addNewButtonText={t("Add New")}
/>
</Tooltip>
</Grid>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<FormHelperText >
<div align='center'>
{t("Choose a Pricing Method")}
</div>
<FormHelperText sx={{textAlign:"center"}}>
{t("Choose a Pricing Method")}
</FormHelperText>
<RadioGroup row defaultValue="relative">
<Tooltip placement="top" enterTouchDelay="0" enterDelay="1000" enterNextDelay="2000" title={t("Let the price move with the market")}>
<Tooltip placement="top" enterTouchDelay={0} enterDelay={1000} enterNextDelay={2000} title={t("Let the price move with the market")}>
<FormControlLabel
value="relative"
control={<Radio color="primary"/>}
@ -362,7 +365,7 @@ class MakerPage extends Component {
onClick={this.handleClickRelative}
/>
</Tooltip>
<Tooltip placement="top" enterTouchDelay="0" enterDelay="1000" enterNextDelay="2000" title={t("Set a fix amount of satoshis")}>
<Tooltip placement="top" enterTouchDelay={0} enterDelay={1000} enterNextDelay={2000} title={t("Set a fix amount of satoshis")}>
<FormControlLabel
disabled={this.state.enableAmountRange}
value="explicit"
@ -384,7 +387,7 @@ class MakerPage extends Component {
error={this.state.badSatoshis}
helperText={this.state.badSatoshis}
type="number"
required="true"
required={true}
value={this.state.satoshis}
inputProps={{
min:this.state.minTradeSats ,
@ -410,7 +413,7 @@ class MakerPage extends Component {
/>
</div>
<Grid item>
<Tooltip placement="top" enterTouchDelay="0" enterDelay="1000" enterNextDelay="2000" title={this.state.is_explicit? t("Your order fixed exchange rate"): t("Your order's current exchange rate. Rate will move with the market.")}>
<Tooltip placement="top" enterTouchDelay={0} enterDelay={1000} enterNextDelay={2000} title={this.state.is_explicit? t("Your order fixed exchange rate"): t("Your order's current exchange rate. Rate will move with the market.")}>
<Typography variant="caption" color="text.secondary">
{(this.state.is_explicit ? t("Order rate:"): t("Order current rate:"))+" "+pn(this.priceNow())+" "+this.state.currencyCode+"/BTC"}
</Typography>
@ -467,36 +470,6 @@ class MakerPage extends Component {
return parseFloat(Number(min_amount*1.1).toPrecision(2))
}
RangeSlider = styled(Slider)(({ theme }) => ({
color: 'primary',
height: 3,
padding: '13px 0',
'& .MuiSlider-thumb': {
height: 27,
width: 27,
backgroundColor: '#fff',
border: '1px solid currentColor',
'&:hover': {
boxShadow: '0 0 0 8px rgba(58, 133, 137, 0.16)',
},
'& .range-bar': {
height: 9,
width: 1,
backgroundColor: 'currentColor',
marginLeft: 1,
marginRight: 1,
},
},
'& .MuiSlider-track': {
height: 3,
},
'& .MuiSlider-rail': {
color: theme.palette.mode === 'dark' ? '#bfbfbf' : '#d8d8d8',
opacity: theme.palette.mode === 'dark' ? undefined : 1,
height: 3,
},
}));
RangeThumbComponent(props) {
const { children, ...other } = props;
return (
@ -551,52 +524,50 @@ class MakerPage extends Component {
return(
<Paper elevation={12} style={{ padding: 8, width:'280px', align:'center'}}>
<Grid container xs={12} spacing={1}>
<Grid container spacing={1}>
<Grid item xs={12} align="center" spacing={1}>
<Grid item xs={12} align="center">
<FormControl align="center">
<FormHelperText>
<Tooltip enterTouchDelay="0" placement="top" align="center" title={t("Let the taker chose an amount within the range")}>
<div align="center" style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>
<Checkbox onChange={(e)=>this.setState({enableAmountRange:e.target.checked, is_explicit: false})}/>
{this.state.enableAmountRange & this.state.minAmount != null? <this.rangeText/> : t("Enable Amount Range")}
</div>
</Tooltip>
<Tooltip enterTouchDelay={0} placement="top" align="center" title={t("Let the taker chose an amount within the range")}>
<FormHelperText align="center" style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>
<Checkbox onChange={(e)=>this.setState({enableAmountRange:e.target.checked, is_explicit: false})}/>
{this.state.enableAmountRange & this.state.minAmount != null? this.rangeText() : t("Enable Amount Range")}
</FormHelperText>
<div style={{ display: this.state.loadingLimits == true ? '':'none'}}>
<LinearProgress />
</div>
<div style={{ display: this.state.loadingLimits == false ? '':'none'}}>
<this.RangeSlider
disableSwap={true}
sx={{width:200, align:"center"}}
disabled={!this.state.enableAmountRange || this.state.loadingLimits}
value={[this.state.minAmount, this.state.maxAmount]}
step={(this.getMaxAmount()-this.getMinAmount())/5000}
valueLabelDisplay="auto"
components={{ Thumb: this.RangeThumbComponent }}
valueLabelFormat={(x) => (parseFloat(Number(x).toPrecision(x < 100 ? 2 : 3))+" "+this.state.currencyCode)}
marks={this.state.limits == null?
null
:
[{value: this.getMinAmount(),label: this.getMinAmount()+" "+ this.state.currencyCode},
{value: this.getMaxAmount(),label: this.getMaxAmount()+" "+this.state.currencyCode}]}
min={this.getMinAmount()}
max={this.getMaxAmount()}
onChange={this.handleRangeAmountChange}
/>
</div>
</Tooltip>
<div style={{ display: this.state.loadingLimits == true ? '':'none'}}>
<LinearProgress />
</div>
<div style={{ display: this.state.loadingLimits == false ? '':'none'}}>
<RangeSlider
disableSwap={true}
sx={{width:200, align:"center"}}
disabled={!this.state.enableAmountRange || this.state.loadingLimits}
value={[Number(this.state.minAmount), Number(this.state.maxAmount)]}
step={(this.getMaxAmount()-this.getMinAmount())/5000}
valueLabelDisplay="auto"
components={{ Thumb: this.RangeThumbComponent }}
valueLabelFormat={(x) => (parseFloat(Number(x).toPrecision(x < 100 ? 2 : 3))+" "+this.state.currencyCode)}
marks={this.state.limits == null?
null
:
[{value: this.getMinAmount(),label: this.getMinAmount()+" "+ this.state.currencyCode},
{value: this.getMaxAmount(),label: this.getMaxAmount()+" "+this.state.currencyCode}]}
min={this.getMinAmount()}
max={this.getMaxAmount()}
onChange={this.handleRangeAmountChange}
/>
</div>
</FormControl>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<Accordion elevation={0} sx={{width:'280px', position:'relative', left:'-12px'}}>
<Grid item xs={12} align="center">
<Accordion elevation={0} sx={{width:'280px', position:'relative', left:'-8px'}}>
<AccordionSummary expandIcon={<ExpandMoreIcon color="primary"/>}>
<Typography sx={{flexGrow: 1, textAlign: "center"}} color="text.secondary">{t("Expiry Timers")}</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container xs={12} spacing={1}>
<Grid item xs={12} align="center" spacing={1}>
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<LocalizationProvider dateAdapter={DateFnsUtils}>
<TimePicker
sx={{width:210, align:"center"}}
@ -624,8 +595,8 @@ class MakerPage extends Component {
/>
</LocalizationProvider>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<Grid item xs={12} align="center">
<LocalizationProvider dateAdapter={DateFnsUtils}>
<TimePicker
sx={{width:210, align:"center"}}
@ -658,15 +629,12 @@ class MakerPage extends Component {
</Accordion>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<Grid item xs={12} align="center">
<FormControl align="center">
<Tooltip enterDelay="800" enterTouchDelay="0" placement="top" title={t("Set the skin-in-the-game, increase for higher safety assurance")}>
<FormHelperText>
<div align="center" style={{display:'flex',flexWrap:'wrap', transform: 'translate(20%, 0)'}}>
{t("Fidelity Bond Size")} <LockIcon sx={{height:20,width:20}}/>
</div>
</FormHelperText>
<Tooltip enterDelay={800} enterTouchDelay={0} placement="top" title={t("Set the skin-in-the-game, increase for higher safety assurance")}>
<FormHelperText align="center" sx={{display:'flex',flexWrap:'wrap', transform: 'translate(20%, 0)'}}>
{t("Fidelity Bond Size")} <LockIcon sx={{height:20,width:20}}/>
</FormHelperText>
</Tooltip>
<Slider
sx={{width:220, align:"center"}}
@ -684,14 +652,14 @@ class MakerPage extends Component {
</FormControl>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<Tooltip enterTouchDelay="0" title={t("COMING SOON - High risk! Limited to {{limitSats}}K Sats",{ limitSats: this.state.maxBondlessSats/1000})}>
<Grid item xs={12} align="center">
<Tooltip enterTouchDelay={0} title={t("COMING SOON - High risk! Limited to {{limitSats}}K Sats",{ limitSats: this.state.maxBondlessSats/1000})}>
<FormControlLabel
label={t("Allow bondless takers")}
control={
<Checkbox
disabled
//disabled={this.state.type==0}
//disabled={this.state.type==0 || this.state.type === null}
color="secondary"
checked={this.state.allowBondless}
onChange={()=> this.setState({allowBondless: !this.state.allowBondless})}
@ -705,114 +673,51 @@ class MakerPage extends Component {
)
}
StoreTokenDialog = () =>{
const { t } = this.props;
// If there is a robot cookie, prompt user to store it
// Else, prompt user to generate a robot
if (getCookie("robot_token")){
return(
<Dialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
>
<DialogTitle >
{t("Store your robot token")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t("You might need to recover your robot avatar in the future: store it safely. You can simply copy it into another application.")}
</DialogContentText>
<br/>
<Grid align="center">
<TextField
sx={{width:"100%", maxWidth:"550px"}}
disabled
label={t("Back it up!")}
value={getCookie("robot_token") }
variant='filled'
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay="0" title={t("Copied!")}>
<IconButton onClick= {()=> (navigator.clipboard.writeText(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))}>
<ContentCopy color={this.props.copiedToken ? "inherit" : "primary"}/>
</IconButton>
</Tooltip>,
}}
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({openStoreToken:false})} autoFocus>{t("Go back")}</Button>
<Button onClick={this.handleCreateOfferButtonPressed}>{t("Done")}</Button>
</DialogActions>
</Dialog>
)
}else{
return(
<Dialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
>
<DialogTitle>
{t("You do not have a robot avatar")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t("You need to generate a robot avatar in order to become an order maker")}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({openStoreToken:false})} autoFocus>{t("Go back")}</Button>
<Button onClick={() => this.setState({openStoreToken:false})} to="/" component={LinkRouter}>{t("Generate Robot")}</Button>
</DialogActions>
</Dialog>
)
}
}
makeOrderBox=()=>{
const [value, setValue] = React.useState(this.state.showAdvanced);
const { t } = this.props;
const handleChange = (event, newValue) => {
this.setState({showAdvanced:newValue})
setValue(newValue);
};
return(
<Box sx={{width: this.state.showAdvanced? '270px':'252px'}}>
<Box sx={{width: this.state.tabValue==1? '270px':'252px'}}>
<Box sx={{ borderBottom: 1, borderColor: 'divider', position:'relative',left:'5px'}}>
<Tabs value={value? value:0} onChange={handleChange} variant="fullWidth" >
<Tab label={t("Order")} {...this.a11yProps(0)} />
<Tab label={t("Customize")} {...this.a11yProps(1)} />
<Tabs value={this.state.tabValue} variant="fullWidth" >
<Tab label={t("Order")} {...this.a11yProps(0)} onClick={() => this.setState({tabValue:0})}/>
<Tab label={t("Customize")} {...this.a11yProps(1)} onClick={() => this.setState({tabValue:1})}/>
</Tabs>
</Box>
<Grid item xs={12} align="center" spacing={1}>
<div style={{ display: this.state.showAdvanced == false ? '':'none'}}>
<this.StandardMakerOptions/>
<Grid item xs={12} align="center">
<div style={{ display: this.state.tabValue == 0 ? '':'none'}}>
{this.StandardMakerOptions()}
</div>
<div style={{ display: this.state.showAdvanced == true ? '':'none'}}>
<this.AdvancedMakerOptions/>
<div style={{ display: this.state.tabValue == 1 ? '':'none'}}>
{this.AdvancedMakerOptions()}
</div>
</Grid>
</Box>
)
}
render() {
const { t } = this.props;
return (
<Grid container xs={12} align="center" spacing={1} sx={{minWidth:380}}>
{/* <Grid item xs={12} align="center" sx={{minWidth:380}}>
<Typography component="h4" variant="h4">
ORDER MAKER
</Typography>
</Grid> */}
<this.StoreTokenDialog/>
<Grid container align="center" spacing={1} sx={{minWidth:380}}>
{getCookie("robot_token") ?
<StoreTokenDialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
onClickCopy={()=> (navigator.clipboard.writeText(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))}
copyIconColor={this.props.copiedToken ? "inherit" : "primary"}
onClickBack={() => this.setState({openStoreToken:false})}
onClickDone={this.handleCreateOfferButtonPressed}
/>
:
<NoRobotDialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
/>
}
<Grid item xs={12} align="center">
<this.makeOrderBox/>
</Grid>
<Grid item xs={12} align="center">
{this.makeOrderBox()}
</Grid>
<Grid item xs={12} align="center">
{/* conditions to disable the make button */}
@ -823,12 +728,12 @@ class MakerPage extends Component {
(this.state.is_explicit & (this.state.badSatoshis != null || this.state.satoshis == null)) ||
(!this.state.is_explicit & this.state.badPremium != null))
?
<Tooltip enterTouchDelay="0" title={t("You must fill the order correctly")}>
<Tooltip enterTouchDelay={0} title={t("You must fill the order correctly")}>
<div><Button disabled color="primary" variant="contained">{t("Create Order")}</Button></div>
</Tooltip>
:
<Button color="primary"
variant="contained"
<Button color="primary"
variant="contained"
onClick={this.props.copiedToken ? this.handleCreateOfferButtonPressed : (() => this.setState({openStoreToken:true}))}
>
{t("Create Order")}
@ -838,11 +743,11 @@ class MakerPage extends Component {
</Grid>
<Grid item xs={12} align="center">
{this.state.badRequest ?
<Typography component="subtitle2" variant="subtitle2" color="secondary">
<Typography component="h2" variant="subtitle2" color="secondary">
{this.state.badRequest} <br/>
</Typography>
: ""}
<Typography component="subtitle2" variant="subtitle2">
<Typography component="h2" variant="subtitle2">
<div align='center'>
{this.state.type==null ?
t("Create an order for ")

View File

@ -1,14 +1,15 @@
import React, { Component } from "react";
import { withTranslation} from "react-i18next";
import {TextField,Chip, Tooltip, IconButton, Badge, Tab, Tabs, Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material"
import Countdown, { zeroPad, calcTimeDelta } from 'react-countdown';
import MediaQuery from 'react-responsive'
import currencyDict from '../../static/assets/currencies.json';
import { Link as LinkRouter } from 'react-router-dom'
import Countdown, { zeroPad } from 'react-countdown';
import { StoreTokenDialog, NoRobotDialog } from "./Dialogs";
import currencyDict from '../../static/assets/currencies.json';
import PaymentText from './PaymentText'
import TradeBox from "./TradeBox";
import getFlags from './getFlags'
import FlagWithProps from './FlagWithProps'
import LinearDeterminate from './LinearDeterminate';
import MediaQuery from 'react-responsive'
import { t } from "i18next";
// icons
@ -19,7 +20,6 @@ import PaymentsIcon from '@mui/icons-material/Payments';
import ArticleIcon from '@mui/icons-material/Article';
import SendReceiveIcon from "./icons/SendReceiveIcon";
import HourglassTopIcon from '@mui/icons-material/HourglassTop';
import ContentCopy from "@mui/icons-material/ContentCopy";
import { getCookie } from "../utils/cookies";
import { pn } from "../utils/prettyNumbers";
@ -35,10 +35,10 @@ class OrderPage extends Component {
openCancel: false,
openCollaborativeCancel: false,
openInactiveMaker: false,
showContractBox: 1,
openStoreToken: false,
tabValue: 1,
orderId: this.props.match.params.orderId,
};
this.getOrderDetails(this.props.match.params.orderId);
// Refresh delays according to Order status
this.statusToDelay = {
@ -88,7 +88,6 @@ class OrderPage extends Component {
}
getOrderDetails =(id)=> {
this.setState(null)
this.setState({orderId:id})
fetch('/api/order' + '?order_id=' + id)
.then((response) => response.json())
@ -97,8 +96,10 @@ class OrderPage extends Component {
// These are used to refresh the data
componentDidMount() {
this.getOrderDetails(this.props.match.params.orderId);
this.interval = setInterval(this.tick, this.state.delay);
}
componentDidUpdate() {
clearInterval(this.interval);
this.interval = setInterval(this.tick, this.state.delay);
@ -178,11 +179,11 @@ class OrderPage extends Component {
const { t } = this.props;
if(this.state.has_range){
return(
<Grid container xs={12} align="center" alignItems="stretch" justifyContent="center" style={{ display: "flex"}}>
<this.InactiveMakerDialog/>
<this.StoreTokenDialog/>
<Grid container align="center" alignItems="stretch" justifyContent="center" style={{ display: "flex"}}>
{this.InactiveMakerDialog()}
{this.tokenDialog()}
<div style={{maxWidth:120}}>
<Tooltip placement="top" enterTouchDelay="500" enterDelay="700" enterNextDelay="2000" title={t("Enter amount of fiat to exchange for bitcoin")}>
<Tooltip placement="top" enterTouchDelay={500} enterDelay={700} enterNextDelay={2000} title={t("Enter amount of fiat to exchange for bitcoin")}>
<Paper elevation={5} sx={{maxHeight:40}}>
<TextField
error={(this.state.takeAmount < this.state.min_amount || this.state.takeAmount > this.state.max_amount) & this.state.takeAmount != "" }
@ -190,7 +191,7 @@ class OrderPage extends Component {
label={t("Amount {{currencyCode}}", {currencyCode: this.state.currencyCode})}
size="small"
type="number"
required="true"
required={true}
value={this.state.takeAmount}
inputProps={{
min:this.state.min_amount ,
@ -203,7 +204,7 @@ class OrderPage extends Component {
</Tooltip>
</div>
<div style={{height:38, top:'1px', position:'relative', display: (this.state.takeAmount < this.state.min_amount || this.state.takeAmount > this.state.max_amount || this.state.takeAmount == "" || this.state.takeAmount == null) ? '':'none'}}>
<Tooltip placement="top" enterTouchDelay="0" enterDelay="500" enterNextDelay="1200" title={t("You must specify an amount first")}>
<Tooltip placement="top" enterTouchDelay={0} enterDelay={500} enterNextDelay={1200} title={t("You must specify an amount first")}>
<Paper elevation={4}>
<Button sx={{height:38}} variant='contained' color='primary'
disabled={true}>
@ -225,8 +226,8 @@ class OrderPage extends Component {
}else{
return(
<>
<this.InactiveMakerDialog/>
<this.StoreTokenDialog/>
{this.InactiveMakerDialog()}
{this.tokenDialog()}
<Button sx={{height:38}} variant='contained' color='primary'
onClick={this.props.copiedToken ? (this.state.maker_status=='Inactive' ? this.handleClickOpenInactiveMakerDialog : this.takeOrder) : (() => this.setState({openStoreToken:true}))}>
{t("Take Order")}
@ -237,41 +238,18 @@ class OrderPage extends Component {
}
countdownTakeOrderRenderer = ({ seconds, completed }) => {
if(isNaN(seconds)){return (<this.takeOrderButton/>)}
if(isNaN(seconds)){return (this.takeOrderButton())}
if (completed) {
// Render a completed state
return ( <this.takeOrderButton/>);
return this.takeOrderButton();
} else{
return(
<Tooltip enterTouchDelay="0" title={t("Wait until you can take an order")}><div>
<Tooltip enterTouchDelay={0} title={t("Wait until you can take an order")}><div>
<Button disabled={true} variant='contained' color='primary'>{t("Take Order")}</Button>
</div></Tooltip>)
}
};
LinearDeterminate =()=> {
const [progress, setProgress] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setProgress((oldProgress) => {
var left = calcTimeDelta( new Date(this.state.expires_at)).total /1000;
return (left / this.state.total_secs_exp) * 100;
});
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return (
<Box sx={{ width: '100%' }}>
<LinearProgress variant="determinate" value={progress} />
</Box>
);
}
takeOrder=()=>{
this.setState({loading:true})
const requestOptions = {
@ -376,72 +354,26 @@ class OrderPage extends Component {
)
}
StoreTokenDialog = () =>{
const { t } = this.props;
// If there is a robot cookie, prompt user to store it
// Else, prompt user to generate a robot
if (getCookie("robot_token")){
return(
<Dialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
>
<DialogTitle >
{t("Store your robot token")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t("You might need to recover your robot avatar in the future: store it safely. You can simply copy it into another application.")}
</DialogContentText>
<br/>
<Grid align="center">
<TextField
sx={{width:"100%", maxWidth:"550px"}}
disabled
label={t("Back it up!")}
value={getCookie("robot_token") }
variant='filled'
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay="0" title={t("Copied!")}>
<IconButton onClick= {()=> (navigator.clipboard.writeText(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))}>
<ContentCopy color={this.props.copiedToken ? "inherit" : "primary"}/>
</IconButton>
</Tooltip>,
}}
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({openStoreToken:false})} autoFocus>{t("Go back")}</Button>
<Button onClick={() => this.setState({openStoreToken:false}) & (this.state.maker_status=='Inactive' ? this.handleClickOpenInactiveMakerDialog() : this.takeOrder())}>{t("Done")}</Button>
</DialogActions>
</Dialog>
)
}else{
return(
<Dialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
>
<DialogTitle>
{t("You do not have a robot avatar")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t("You need to generate a robot avatar in order to become an order maker")}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({openStoreToken:false})} autoFocus>{t("Go back")}</Button>
<Button onClick={() => this.setState({openStoreToken:false})} to="/" component={LinkRouter}>{t("Generate Robot")}</Button>
</DialogActions>
</Dialog>
)
}
}
tokenDialog = () =>{
return(getCookie("robot_token") ?
<StoreTokenDialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
onClickCopy={()=> (navigator.clipboard.writeText(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))}
copyIconColor={this.props.copiedToken ? "inherit" : "primary"}
onClickBack={() => this.setState({openStoreToken:false})}
onClickDone={() => this.setState({openStoreToken:false}) &
(this.state.maker_status=='Inactive' ?
this.handleClickOpenInactiveMakerDialog()
: this.takeOrder())
}/>
:
<NoRobotDialog
open={this.state.openStoreToken}
onClose={() => this.setState({openStoreToken:false})}
/>
)
}
handleClickConfirmCollaborativeCancelButton=()=>{
const requestOptions = {
@ -517,7 +449,7 @@ class OrderPage extends Component {
return(
<div id="openDialogCancelButton">
<Grid item xs={12} align="center">
<this.CancelDialog/>
{this.CancelDialog()}
<Button variant='contained' color='secondary' onClick={this.handleClickOpenConfirmCancelDialog}>{t("Cancel")}</Button>
</Grid>
</div>
@ -528,7 +460,7 @@ class OrderPage extends Component {
if ([8,9].includes(this.state.status)){
return(
<Grid item xs={12} align="center">
<this.CollaborativeCancelDialog/>
{this.CollaborativeCancelDialog()}
<Button variant='contained' color='secondary' onClick={this.handleClickOpenCollaborativeCancelDialog}>{t("Collaborative Cancel")}</Button>
</Grid>
)}
@ -554,11 +486,11 @@ class OrderPage extends Component {
{t("Order Box")}
</Typography>
</MediaQuery>
<Paper elevation={12} style={{ padding: 8,}}>
<List dense="true">
<Paper elevation={12} >
<List dense={true}>
<ListItem >
<ListItemAvatar sx={{ width: 56, height: 56 }}>
<Tooltip placement="top" enterTouchDelay="0" title={t(this.state.maker_status)} >
<Tooltip placement="top" enterTouchDelay={0} title={t(this.state.maker_status)} >
<Badge variant="dot" overlap="circular" badgeContent="" color={this.statusBadgeColor(this.state.maker_status)}>
<Badge overlap="circular" anchorOrigin={{horizontal: 'right', vertical: 'bottom'}} badgeContent={<div style={{position:"relative", left:"12px", top:"4px"}}> {!this.state.type ? <SendReceiveIcon sx={{transform: "scaleX(-1)"}} color="secondary"/> : <SendReceiveIcon color="primary"/>}</div>}>
<Avatar className="flippedSmallAvatar"
@ -580,7 +512,7 @@ class OrderPage extends Component {
<ListItem align="left">
<ListItemText primary={this.state.taker_nick + (this.state.type ? " "+t("(Buyer)") : " "+t("(Seller)"))} secondary={t("Order taker")}/>
<ListItemAvatar >
<Tooltip enterTouchDelay="0" title={t(this.state.taker_status)} >
<Tooltip enterTouchDelay={0} title={t(this.state.taker_status)} >
<Badge variant="dot" overlap="circular" badgeContent="" color={this.statusBadgeColor(this.state.taker_status)}>
<Badge overlap="circular" anchorOrigin={{horizontal: 'left', vertical: 'bottom'}} badgeContent={<div style={{position:"relative", right:"12px", top:"4px"}}> {this.state.type ? <SendReceiveIcon color="secondary"/> : <SendReceiveIcon sx={{transform: "scaleX(-1)"}} color="primary"/> }</div>}>
<Avatar className="smallAvatar"
@ -609,8 +541,8 @@ class OrderPage extends Component {
<ListItem>
<ListItemIcon>
<div style={{zoom:1.25,opacity: 0.7, '-ms-zoom': 1.25, '-webkit-zoom': 1.25,'-moz-transform': 'scale(1.25,1.25)', '-moz-transform-origin': 'left center'}}>
{getFlags(this.state.currencyCode)}
<div style={{zoom:1.25,opacity: 0.7, msZoom: 1.25, WebkitZoom: 1.25, MozTransform: 'scale(1.25,1.25)', MozTransformOrigin: 'left center'}}>
<FlagWithProps code={this.state.currencyCode} />
</div>
</ListItemIcon>
{this.state.has_range & this.state.amount == null ?
@ -653,7 +585,7 @@ class OrderPage extends Component {
<ListItemIcon>
<NumbersIcon/>
</ListItemIcon>
<Grid container xs={12}>
<Grid container>
<Grid item xs={4.5}>
<ListItemText primary={this.state.orderId} secondary={t("Order ID")}/>
</Grid>
@ -682,7 +614,7 @@ class OrderPage extends Component {
<Countdown date={new Date(this.state.expires_at)} renderer={this.countdownRenderer} />
</ListItemText>
</ListItem>
<this.LinearDeterminate />
<LinearDeterminate key={this.state.expires_at} total_secs_exp={this.state.total_secs_exp} expires_at={this.state.expires_at}/>
</List>
{/* If the user has a penalty/limit */}
@ -728,8 +660,8 @@ class OrderPage extends Component {
{/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */}
{this.state.is_participant ?
<>
<this.CancelButton/>
<this.BackButton/>
{this.CancelButton()}
{this.BackButton()}
</>
:
<Grid container spacing={1}>
@ -748,7 +680,7 @@ class OrderPage extends Component {
doubleOrderPageDesktop=()=>{
return(
<Grid container xs={12} align="center" spacing={2} >
<Grid container align="center" spacing={2} >
<Grid item xs={6} align="left" style={{ width:330}} >
{this.orderBox()}
</Grid>
@ -768,27 +700,21 @@ class OrderPage extends Component {
doubleOrderPagePhone=()=>{
const { t } = this.props;
const [value, setValue] = React.useState(this.state.showContractBox);
const handleChange = (event, newValue) => {
this.setState({showContractBox:newValue})
setValue(newValue);
};
return(
<Box sx={{ width: '100%'}}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={value} onChange={handleChange} variant="fullWidth" >
<Tab label={t("Order")} {...this.a11yProps(0)} />
<Tab label={t("Contract")} {...this.a11yProps(1)} />
<Tabs value={this.state.tabValue} variant="fullWidth" >
<Tab label={t("Order")} {...this.a11yProps(0)} onClick={() => this.setState({tabValue:0})}/>
<Tab label={t("Contract")} {...this.a11yProps(1)} onClick={() => this.setState({tabValue:1})}/>
</Tabs>
</Box>
<Grid container spacing={2}>
<Grid item >
<div style={{ width:330, display: this.state.showContractBox == 0 ? '':'none'}}>
<div style={{ width:330, display: this.state.tabValue == 0 ? '':'none'}}>
{this.orderBox()}
</div>
<div style={{display: this.state.showContractBox == 1 ? '':'none'}}>
<div style={{display: this.state.tabValue == 1 ? '':'none'}}>
<TradeBox push={this.props.history.push} getOrderDetails={this.getOrderDetails} pauseLoading={this.state.pauseLoading} width={330} data={this.state} completeSetState={this.completeSetState} />
</div>
</Grid>
@ -802,7 +728,7 @@ class OrderPage extends Component {
return(
this.state.bad_request ?
<div align='center'>
<Typography component="subtitle2" variant="subtitle2" color="secondary" >
<Typography variant="subtitle2" color="secondary" >
{/* IMPLEMENT I18N for bad_request */}
{t(this.state.bad_request)}<br/>
</Typography>
@ -813,12 +739,12 @@ class OrderPage extends Component {
<>
{/* Desktop View */}
<MediaQuery minWidth={920}>
<this.doubleOrderPageDesktop/>
{this.doubleOrderPageDesktop()}
</MediaQuery>
{/* SmarPhone View */}
<MediaQuery maxWidth={919}>
<this.doubleOrderPagePhone/>
{this.doubleOrderPagePhone()}
</MediaQuery>
</>
:

View File

@ -21,7 +21,7 @@ class PaymentText extends Component {
if(this.props.text.includes(method.name)){
custom_methods = custom_methods.replace(method.name,'')
rows.push(
<Tooltip placement="top" enterTouchDelay="0" title={t(method.name)}>
<Tooltip key={`${method.name}-${i}`} placement="top" enterTouchDelay={0} title={t(method.name)}>
<div style={{display: 'inline-block', width: this.props.size+2, height: this.props.size}}>
<PaymentIcon width={this.props.size} height={this.props.size} icon={method.icon}/>
</div>
@ -32,9 +32,9 @@ class PaymentText extends Component {
// Adds a Custom icon if there are words that do not match
var chars_left = custom_methods.replace(' ','').replace(' ','').replace(' ','').replace(' ','').replace(' ','')
if(chars_left.length > 0){rows.push(
<Tooltip placement="top" enterTouchDelay="0" title={this.props.verbose ? this.props.othersText: this.props.othersText+": "+ custom_methods} >
<Tooltip key={"pushed"} placement="top" enterTouchDelay={0} title={this.props.verbose ? this.props.othersText: this.props.othersText+": "+ custom_methods} >
<div style={{position:'relative', display: 'inline-block',width: this.props.size+2, maxHeight: this.props.size, top:'-1px'}}>
<PaymentIcon width={this.props.size*1.1} height={this.props.size*1.1} icon={"custom"}/>
</div>
@ -49,12 +49,12 @@ class PaymentText extends Component {
}
render() {
return (
return (
<div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>
{this.parseText()}
</div>
)
}
};
}
export default withTranslation()(PaymentText);
export default withTranslation()(PaymentText);

View File

@ -0,0 +1,34 @@
import { Slider } from "@mui/material"
import { styled } from '@mui/material/styles';
const RangeSlider = styled(Slider)(({ theme }) => ({
color: 'primary',
height: 3,
padding: '13px 0',
'& .MuiSlider-thumb': {
height: 27,
width: 27,
backgroundColor: '#fff',
border: '1px solid currentColor',
'&:hover': {
boxShadow: '0 0 0 8px rgba(58, 133, 137, 0.16)',
},
'& .range-bar': {
height: 9,
width: 1,
backgroundColor: 'currentColor',
marginLeft: 1,
marginRight: 1,
},
},
'& .MuiSlider-track': {
height: 3,
},
'& .MuiSlider-rail': {
color: theme.palette.mode === 'dark' ? '#bfbfbf' : '#d8d8d8',
opacity: theme.palette.mode === 'dark' ? undefined : 1,
height: 3,
},
}));
export default RangeSlider;

View File

@ -40,7 +40,7 @@ class TradeBox extends Component {
Sound = ({soundFileName}) => (
Sound = (soundFileName) => (
// Four filenames: "locked-invoice", "taker-found", "open-chat", "successful"
<audio autoPlay src={`/static/assets/sounds/${soundFileName}.mp3`} />
)
@ -182,19 +182,19 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2">
<Typography variant="body2">
{t("Robots show commitment to their peers")}
</Typography>
</Grid>
<Grid item xs={12} align="center">
{this.props.data.is_maker ?
<Typography color="primary" component="subtitle1" variant="subtitle1">
<Typography color="primary" variant="subtitle1">
<b>
{t("Lock {{amountSats}} Sats to PUBLISH order", {amountSats: pn(this.props.data.bond_satoshis)})}
</b> {" " + this.stepXofY()}
</Typography>
:
<Typography color="primary" component="subtitle1" variant="subtitle1">
<Typography color="primary" variant="subtitle1">
<b>
{t("Lock {{amountSats}} Sats to TAKE order", {amountSats: pn(this.props.data.bond_satoshis)})}
</b> {" " + this.stepXofY()}
@ -205,7 +205,7 @@ class TradeBox extends Component {
<Box sx={{bgcolor:'#ffffff', width:'315px', position:'relative', left:'-5px'}} >
<QRCode value={this.props.data.bond_invoice} size={305} style={{position:'relative', top:'3px'}}/>
</Box>
<Tooltip disableHoverListener enterTouchDelay="0" title={t("Copied!")}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<Button size="small" color="inherit" onClick={() => {navigator.clipboard.writeText(this.props.data.bond_invoice)}} align="center"> <ContentCopy/>{t("Copy to clipboard")}</Button>
</Tooltip>
</Grid>
@ -215,7 +215,7 @@ class TradeBox extends Component {
variant="standard"
size="small"
defaultValue={this.props.data.bond_invoice}
disabled="true"
disabled={true}
helperText={t("This is a hold invoice, it will freeze in your wallet. It will be charged only if you cancel or lose a dispute.")}
color = "secondary"
/>
@ -228,7 +228,7 @@ class TradeBox extends Component {
const {t} = this.props
return (
<Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1" align="center">
<Typography color="primary" variant="subtitle1" align="center">
<div style={{display:'flex', alignItems:'center', justifyContent:'center', flexWrap:'wrap'}}>
<LockIcon/>
{this.props.data.is_maker ? t("Your maker bond is locked") : t("Your taker bond is locked")}
@ -242,7 +242,7 @@ class TradeBox extends Component {
const { t } = this.props;
return (
<Grid item xs={12} align="center">
<Typography color="error" component="subtitle1" variant="subtitle1" align="center">
<Typography color="error" variant="subtitle1" align="center">
<div style={{display:'flex',alignItems:'center', justifyContent:'center', flexWrap:'wrap', align:"center"}} align="center">
<BalanceIcon/>
{this.props.data.is_maker ? t("Your maker bond was settled") : t("Your taker bond was settled")}
@ -256,7 +256,7 @@ class TradeBox extends Component {
const { t } = this.props;
return (
<Grid item xs={12} align="center">
<Typography color="green" component="subtitle1" variant="subtitle1" align="center">
<Typography color="green" variant="subtitle1" align="center">
<div style={{display:'flex',alignItems:'center', justifyContent:'center', flexWrap:'wrap'}}>
<LockOpenIcon/>
{this.props.data.is_maker ? t("Your maker bond was unlocked") : t("Your taker bond was unlocked")}
@ -271,9 +271,9 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
{/* Make confirmation sound for HTLC received. */}
<this.Sound soundFileName="locked-invoice"/>
{this.Sound("locked-invoice")}
<Grid item xs={12} align="center">
<Typography color="green" component="subtitle1" variant="subtitle1">
<Typography color="green" variant="subtitle1">
<b>
{t("Lock {{amountSats}} Sats as collateral", {amountSats:pn(this.props.data.escrow_satoshis)})}
</b>{" " + this.stepXofY()}
@ -283,7 +283,7 @@ class TradeBox extends Component {
<Box sx={{bgcolor:'#ffffff', width:'315px', position:'relative', left:'-5px'}} >
<QRCode value={this.props.data.escrow_invoice} size={305} style={{position:'relative', top:'3px'}}/>
</Box>
<Tooltip disableHoverListener enterTouchDelay="0" title={t("Copied!")}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<Button size="small" color="inherit" onClick={() => {navigator.clipboard.writeText(this.props.data.escrow_invoice)}} align="center"> <ContentCopy/>{t("Copy to clipboard")}</Button>
</Tooltip>
</Grid>
@ -293,7 +293,7 @@ class TradeBox extends Component {
variant="filled"
size="small"
defaultValue={this.props.data.escrow_invoice}
disabled="true"
disabled={true}
helperText={t("This is a hold invoice, it will freeze in your wallet. It will be released to the buyer once you confirm to have received the {{currencyCode}}.",{currencyCode: this.props.data.currencyCode})}
color = "secondary"
/>
@ -308,15 +308,15 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
{/* Make bell sound when taker is found */}
<this.Sound soundFileName="taker-found"/>
{this.Sound("taker-found")}
<Grid item xs={12} align="center">
<Typography component="subtitle1" variant="subtitle1">
<Typography variant="subtitle1">
<b>{t("A taker has been found!")}</b> {" " + this.stepXofY()}
</Typography>
</Grid>
<Divider/>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2">
<Typography variant="body2">
{t("Please wait for the taker to lock a bond. If the taker does not lock a bond in time, the order will be made public again.")}
</Typography>
</Grid>
@ -387,21 +387,26 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
{/* Make confirmation sound for HTLC received. */}
<this.Sound soundFileName="locked-invoice"/>
<this.EnableTelegramDialog/>
{this.Sound("locked-invoice")}
{this.EnableTelegramDialog()}
<Grid item xs={12} align="center">
<Typography component="subtitle1" variant="subtitle1">
<Typography variant="subtitle1">
<b> {t("Your order is public")} </b> {" " + this.stepXofY()}
</Typography>
</Grid>
<Grid item xs={12} align="center">
<List dense="true">
<List dense={true}>
<Divider/>
<ListItem>
<Typography component="body2" variant="body2" align="left">
<p>{t("Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{deposit_timer_hours}}h {{deposit_timer_minutes}}m to reply. If you do not reply, you risk losing your bond.", this.depositHoursMinutes() )} </p>
<p>{t("If the order expires untaken, your bond will return to you (no action needed).")}</p>
<Typography variant="body2" align="left">
{t("Be patient while robots check the book. This box will ring 🔊 once a robot takes your order, then you will have {{deposit_timer_hours}}h {{deposit_timer_minutes}}m to reply. If you do not reply, you risk losing your bond.", this.depositHoursMinutes() )}
</Typography>
</ListItem>
<ListItem>
<Typography variant="body2" align="left">
{t("If the order expires untaken, your bond will return to you (no action needed).")}
</Typography>
</ListItem>
@ -431,7 +436,7 @@ class TradeBox extends Component {
{this.props.pauseLoading ?
<CircularProgress sx={{width:"30px",height:"30px"}}/>
:
<Tooltip placement="top" enterTouchDelay="500" enterDelay="700" enterNextDelay="2000" title={t("Pause the public order")}>
<Tooltip placement="top" enterTouchDelay={500} enterDelay={700} enterNextDelay={2000} title={t("Pause the public order")}>
<Button color="primary" onClick={this.handleClickPauseOrder}>
<PauseCircleIcon sx={{width:"36px",height:"36px"}}/>
</Button>
@ -464,16 +469,16 @@ class TradeBox extends Component {
<Grid container align="center" spacing={1}>
<Grid item xs={12} align="center">
<Typography component="subtitle1" variant="subtitle1">
<Typography variant="subtitle1">
<b> {t("Your order is paused")} </b> {" " + this.stepXofY()}
</Typography>
</Grid>
<Grid item xs={12} align="center">
<List dense="true">
<List dense={true}>
<Divider/>
<ListItem>
<Typography component="body2" variant="body2" align="left">
<Typography variant="body2" align="left">
{t("Your public order has been paused. At the moment it cannot be seen or taken by other robots. You can choose to unpause it at any time.")}
</Typography>
</ListItem>
@ -566,14 +571,14 @@ class TradeBox extends Component {
<Grid container spacing={1}>
<Grid item xs={12} align="center">
{/* Make confirmation sound for HTLC received. */}
<this.Sound soundFileName="locked-invoice"/>
<Typography color="primary" component="subtitle1" variant="subtitle1">
{this.Sound("locked-invoice")}
<Typography color="primary" variant="subtitle1">
<b> {t("Submit an invoice for {{amountSats}} Sats",{amountSats: pn(this.props.data.invoice_amount)})}
</b> {" " + this.stepXofY()}
</Typography>
</Grid>
<Grid item xs={12} align="left">
<Typography component="body2" variant="body2">
<Typography variant="body2">
{t("The taker is committed! Before letting you send {{amountFiat}} {{currencyCode}}, we want to make sure you are able to receive the BTC. Please provide a valid invoice for {{amountSats}} Satoshis.",
{amountFiat: parseFloat(parseFloat(this.props.data.amount).toFixed(4)),
currencyCode: this.props.data.currencyCode,
@ -627,15 +632,25 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1">
<Typography color="primary" variant="subtitle1">
<b> {t("We have received your statement")} </b>
</Typography>
</Grid>
<Grid item xs={12} align="left">
<Typography component="body2" variant="body2">
<p>{t("We are waiting for your trade counterpart statement. If you are hesitant about the state of the dispute or want to add more information, contact robosats@protonmail.com.")}</p>
<p>{t("Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself as the user involved in this trade via email (or other contact methods).")}</p>
</Typography>
<Grid item xs={12} align="center">
<List dense={true}>
<Divider/>
<ListItem>
<Typography variant="body2">
{t("We are waiting for your trade counterpart statement. If you are hesitant about the state of the dispute or want to add more information, contact robosats@protonmail.com.")}
</Typography>
</ListItem>
<ListItem>
<Typography variant="body2">
{t("Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself as the user involved in this trade via email (or other contact methods).")}
</Typography>
</ListItem>
<Divider/>
</List>
</Grid>
{this.showBondIsSettled()}
</Grid>
@ -647,15 +662,17 @@ class TradeBox extends Component {
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1">
<Typography color="primary" variant="subtitle1">
<b> {t("A dispute has been opened")} </b>
</Typography>
</Grid>
<Grid item xs={12} align="left">
<Typography component="body2" variant="body2">
{t("Please, submit your statement. Be clear and specific about what happened and provide the necessary evidence. You MUST provide a contact method: burner email, XMPP or telegram username to follow up with the staff. Disputes are solved at the discretion of real robots (aka humans), so be as helpful as possible to ensure a fair outcome. Max 5000 chars.")}
</Typography>
</Grid>
<List dense={true}>
<Divider/>
<ListItem>
<Typography variant="body2">
{t("Please, submit your statement. Be clear and specific about what happened and provide the necessary evidence. You MUST provide a contact method: burner email, XMPP or telegram username to follow up with the staff. Disputes are solved at the discretion of real robots (aka humans), so be as helpful as possible to ensure a fair outcome. Max 5000 chars.")}
</Typography>
</ListItem>
<Grid item xs={12} align="center">
<TextField
@ -674,6 +691,8 @@ class TradeBox extends Component {
<Grid item xs={12} align="center">
<Button onClick={this.handleClickSubmitStatementButton} variant='contained' color='primary'>Submit</Button>
</Grid>
</List>
{this.showBondIsSettled()}
</Grid>
)}
@ -684,15 +703,25 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1">
<Typography color="primary" variant="subtitle1">
<b> {t("We have the statements")} </b>
</Typography>
</Grid>
<Grid item xs={12} align="left">
<Typography component="body2" variant="body2">
<p>{t("Both statements have been received, wait for the staff to resolve the dispute. If you are hesitant about the state of the dispute or want to add more information, contact robosats@protonmail.com. If you did not provide a contact method, or are unsure whether you wrote it right, write us immediately.")} </p>
<p>{t("Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself as the user involved in this trade via email (or other contact methods).")}</p>
</Typography>
<Grid item xs={12} align="center">
<List dense={true}>
<Divider/>
<ListItem>
<Typography variant="body2">
{t("Both statements have been received, wait for the staff to resolve the dispute. If you are hesitant about the state of the dispute or want to add more information, contact robosats@protonmail.com. If you did not provide a contact method, or are unsure whether you wrote it right, write us immediately.")}
</Typography>
</ListItem>
<ListItem>
<Typography variant="body2">
{t("Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself as the user involved in this trade via email (or other contact methods).")}
</Typography>
</ListItem>
<Divider/>
</List>
</Grid>
{this.showBondIsSettled()}
</Grid>
@ -704,12 +733,12 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1">
<Typography color="primary" variant="subtitle1">
<b> {t("You have won the dispute")} </b>
</Typography>
</Grid>
<Grid item xs={12} align="left">
<Typography component="body2" variant="body2">
<Typography variant="body2">
{t("You can claim the dispute resolution amount (escrow and fidelity bond) from your profile rewards. If there is anything the staff can help with, do not hesitate to contact to robosats@protonmail.com (or via your provided burner contact method).")}
</Typography>
</Grid>
@ -723,12 +752,12 @@ class TradeBox extends Component {
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography color="error" component="subtitle1" variant="subtitle1">
<Typography color="error" variant="subtitle1">
<b> {t("You have lost the dispute")} </b>
</Typography>
</Grid>
<Grid item xs={12} align="left">
<Typography component="body2" variant="body2">
<Typography variant="body2">
{t("Unfortunately you have lost the dispute. If you think this is a mistake you can ask to re-open the case via email to robosats@protonmail.com. However, chances of it being investigated again are low.")}
</Typography>
</Grid>
@ -742,15 +771,25 @@ class TradeBox extends Component {
return(
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="subtitle1" variant="subtitle1">
<Typography variant="subtitle1">
<b>{t("Your invoice looks good!")}</b> {" " + this.stepXofY()}
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="left">
<p>{t("We are waiting for the seller lock the trade amount.")}</p>
<p>{t("Just hang on for a moment. If the seller does not deposit, you will get your bond back automatically. In addition, you will receive a compensation (check the rewards in your profile).")}</p>
</Typography>
<List dense={true}>
<Divider/>
<ListItem>
<Typography variant="body2" align="left">
{t("We are waiting for the seller lock the trade amount.")}
</Typography>
</ListItem>
<ListItem>
<Typography variant="body2" align="left">
{t("Just hang on for a moment. If the seller does not deposit, you will get your bond back automatically. In addition, you will receive a compensation (check the rewards in your profile).")}
</Typography>
</ListItem>
<Divider/>
</List>
</Grid>
{this.showBondIsLocked()}
</Grid>
@ -762,17 +801,28 @@ class TradeBox extends Component {
return(
<Grid container spacing={1}>
{/* Make confirmation sound for HTLC received. */}
<this.Sound soundFileName="locked-invoice"/>
{this.Sound("locked-invoice")}
<Grid item xs={12} align="center">
<Typography component="subtitle1" variant="subtitle1">
<Typography variant="subtitle1">
<b>{t("The trade collateral is locked!")}</b> {" " + this.stepXofY()}
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="left">
<p>{t("We are waiting for the buyer to post a lightning invoice. Once he does, you will be able to directly communicate the fiat payment details.")} </p>
<p>{t("Just hang on for a moment. If the buyer does not cooperate, you will get back the trade collateral and your bond automatically. In addition, you will receive a compensation (check the rewards in your profile).")}</p>
</Typography>
<List dense={true}>
<Divider/>
<ListItem>
<Typography variant="body2" align="left">
{t("We are waiting for the buyer to post a lightning invoice. Once he does, you will be able to directly communicate the fiat payment details.")}
</Typography>
</ListItem>
<ListItem>
<Typography variant="body2" align="left">
{t("Just hang on for a moment. If the buyer does not cooperate, you will get back the trade collateral and your bond automatically. In addition, you will receive a compensation (check the rewards in your profile).")}
</Typography>
</ListItem>
<Divider/>
</List>
</Grid>
{this.showBondIsLocked()}
</Grid>
@ -892,13 +942,13 @@ handleRatingRobosatsChange=(e)=>{
return(
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="subtitle1" variant="subtitle1">
<Typography variant="subtitle1">
<b>{t("The order has expired")}</b>
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2">
<Typography variant="body2">
{t(this.props.data.expiry_message)}
</Typography>
</Grid>
@ -945,15 +995,15 @@ handleRatingRobosatsChange=(e)=>{
return(
<Grid container spacing={1}>
{/* Make confirmation sound for Chat Open. */}
<this.Sound soundFileName="chat-open"/>
{this.Sound("chat-open")}
<Grid item xs={12} align="center">
<Typography component="subtitle1" variant="subtitle1">
<Typography variant="subtitle1">
<b> {this.props.data.is_seller ? t("Chat with the buyer"): t("Chat with the seller")}</b> {" " + this.stepXofY()}
</Typography>
</Grid>
<Grid item xs={12} align="center">
{this.props.data.is_seller ?
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
{this.props.data.status == 9?
t("Say hi! Be helpful and concise. Let them know how to send you {{currencyCode}}.",{currencyCode: this.props.data.currencyCode})
:
@ -961,7 +1011,7 @@ handleRatingRobosatsChange=(e)=>{
}
</Typography>
:
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
{this.props.data.status == 9?
t("Say hi! Ask for payment details and click 'Confirm Sent' as soon as the payment is sent.")
:
@ -989,14 +1039,14 @@ handleRatingRobosatsChange=(e)=>{
return(
<Grid container spacing={1}>
{/* Make confirmation sound for Chat Open. */}
<this.Sound soundFileName="successful"/>
{this.Sound("successful")}
<Grid item xs={12} align="center">
<Typography component="h6" variant="h6">
{t("🎉Trade finished!🥳")}
</Typography>
</Grid>
{/* <Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
What do you think of <b>{this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}</b>?
</Typography>
</Grid>
@ -1004,7 +1054,7 @@ handleRatingRobosatsChange=(e)=>{
<Rating name="size-large" defaultValue={0} size="large" onChange={this.handleRatingUserChange} />
</Grid> */}
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
<Trans i18nKey="rate_robosats">What do you think of 🤖<b>RoboSats</b>?</Trans>
</Typography>
</Grid>
@ -1013,7 +1063,7 @@ handleRatingRobosatsChange=(e)=>{
</Grid>
{this.state.rating_platform==5 ?
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
<p><b>{t("Thank you! RoboSats loves you too ❤️")}</b></p>
<p>{t("RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!")}</p>
</Typography>
@ -1021,7 +1071,7 @@ handleRatingRobosatsChange=(e)=>{
: null}
{this.state.rating_platform!=5 & this.state.rating_platform!=null ?
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
<p><b>{t("Thank you for using Robosats!")}</b></p>
<p><Trans i18nKey="let_us_know_hot_to_improve">Let us know how the platform could improve (<Link target='_blank' href="https://t.me/robosats">Telegram</Link> / <Link target='_blank' href="https://github.com/Reckless-Satoshi/robosats/issues">Github</Link>)</Trans></p>
</Typography>
@ -1056,7 +1106,7 @@ handleRatingRobosatsChange=(e)=>{
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
{t("RoboSats is trying to pay your lightning invoice. Remember that lightning nodes must be online in order to receive payments.")}
</Typography>
<br/>
@ -1093,13 +1143,13 @@ handleRatingRobosatsChange=(e)=>{
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
{t("Your invoice has expired or more than 3 payment attempts have been made. Muun wallet is not recommended. ")}
<Link href="https://github.com/Reckless-Satoshi/robosats/issues/44"> {t("Check the list of compatible wallets")}</Link>
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1">
<Typography color="primary" variant="subtitle1">
<b> {t("Submit an invoice for {{amountSats}} Sats",{amountSats: pn(this.props.data.invoice_amount)})}</b>
</Typography>
</Grid>
@ -1133,7 +1183,7 @@ handleRatingRobosatsChange=(e)=>{
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<Typography variant="body2" align="center">
{t("RoboSats will try to pay your invoice 3 times every 5 minutes. If it keeps failing, you will be able to submit a new invoice. Check whether you have enough inbound liquidity. Remember that lightning nodes must be online in order to receive payments.")}
</Typography>
<List>
@ -1152,8 +1202,8 @@ handleRatingRobosatsChange=(e)=>{
const { t } = this.props;
return (
<Grid container spacing={1} style={{ width:this.props.width}}>
<this.ConfirmDisputeDialog/>
<this.ConfirmFiatReceivedDialog/>
{this.ConfirmDisputeDialog()}
{this.ConfirmFiatReceivedDialog()}
<Grid item xs={12} align="center">
<MediaQuery minWidth={920}>
<Typography component="h5" variant="h5">

View File

@ -1,9 +1,9 @@
import React, { Component } from "react";
import { withTranslation } from "react-i18next";
import { Button , Tooltip, Dialog, Grid, Typography, TextField, ButtonGroup, CircularProgress, IconButton} from "@mui/material"
import { Button , Tooltip, Grid, Typography, TextField, ButtonGroup, CircularProgress, IconButton} from "@mui/material"
import { Link } from 'react-router-dom'
import Image from 'material-ui-image'
import InfoDialog from './InfoDialog'
import { InfoDialog } from './Dialogs'
import SmartToyIcon from '@mui/icons-material/SmartToy';
import CasinoIcon from '@mui/icons-material/Casino';
@ -19,25 +19,28 @@ class UserGenPage extends Component {
this.state = {
openInfo: false,
tokenHasChanged: false,
token: ""
};
this.refCode = this.props.match.params.refCode;
}
componentDidMount() {
// Checks in parent HomePage if there is already a nick and token
// Displays the existing one
if (this.props.nickname != null){
this.state = {
this.setState({
nickname: this.props.nickname,
token: this.props.token? this.props.token : null,
token: this.props.token? this.props.token : "",
avatar_url: '/static/assets/avatars/' + this.props.nickname + '.png',
loadingRobot: false
}
});
}
else{
var newToken = this.genBase62Token(36)
this.state = {
this.setState({
token: newToken
};
});
this.getGeneratedUser(newToken);
}
}
@ -78,12 +81,12 @@ class UserGenPage extends Component {
token: token,
avatarLoaded: false,
})) & writeCookie("robot_token",token))
&
&
// If the robot has been found (recovered) we assume the token is backed up
(data.found ? this.props.setAppState({copiedToken:true}) : null)
});
}
delGeneratedUser() {
const requestOptions = {
method: 'DELETE',
@ -124,20 +127,6 @@ class UserGenPage extends Component {
this.setState({openInfo: false});
};
InfoDialog =() =>{
return(
<Dialog
open={this.state.openInfo}
onClose={this.handleCloseInfo}
aria-labelledby="info-dialog-title"
aria-describedby="info-dialog-description"
scroll="paper"
>
<InfoDialog handleCloseInfo = {this.handleCloseInfo}/>
</Dialog>
)
}
render() {
const { t, i18n} = this.props;
return (
@ -158,13 +147,13 @@ class UserGenPage extends Component {
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Tooltip enterTouchDelay="0" title={t("This is your trading avatar")}>
<Tooltip enterTouchDelay={0} title={t("This is your trading avatar")}>
<div style={{ maxWidth: 200, maxHeight: 200 }}>
<Image className='newAvatar'
disableError='true'
cover='true'
disableError={true}
cover={true}
color='null'
src={this.state.avatar_url}
src={this.state.avatar_url || ""}
/>
</div>
</Tooltip><br/>
@ -187,7 +176,7 @@ class UserGenPage extends Component {
<TextField sx={{maxWidth: 280}}
error={this.state.bad_request}
label={t("Store your token safely")}
required='true'
required={true}
value={this.state.token}
variant='standard'
helperText={this.state.bad_request}
@ -200,13 +189,13 @@ class UserGenPage extends Component {
}}
InputProps={{
startAdornment:
<Tooltip disableHoverListener enterTouchDelay="0" title={t("Copied!")}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick= {()=> (navigator.clipboard.writeText(this.state.token) & this.props.setAppState({copiedToken:true}))}>
<ContentCopy color={this.props.avatarLoaded & !this.props.copiedToken & !this.state.bad_request ? 'primary' : 'inherit' } sx={{width:18, height:18}}/>
</IconButton>
</Tooltip>,
endAdornment:
<Tooltip enterTouchDelay="250" title={t("Generate a new token")}>
<Tooltip enterTouchDelay={250} title={t("Generate a new token")}>
<IconButton onClick={this.handleClickNewRandomToken}><CasinoIcon/></IconButton>
</Tooltip>,
}}
@ -220,7 +209,7 @@ class UserGenPage extends Component {
<span> {t("Generate Robot")}</span>
</Button>
:
<Tooltip enterTouchDelay="0" enterDelay="500" enterNextDelay="2000" title={t("You must enter a new token first")}>
<Tooltip enterTouchDelay={0} enterDelay={500} enterNextDelay={2000} title={t("You must enter a new token first")}>
<div>
<Button disabled={true} type="submit" size='small' >
<SmartToyIcon sx={{width:18, height:18}} />
@ -234,17 +223,17 @@ class UserGenPage extends Component {
<ButtonGroup variant="contained" aria-label="outlined primary button group">
<Button disabled={this.state.loadingRobot} color='primary' to='/make/' component={Link}>{t("Make Order")}</Button>
<Button color='inherit' style={{color: '#111111'}} onClick={this.handleClickOpenInfo}>{t("Info")}</Button>
<this.InfoDialog/>
<InfoDialog open={Boolean(this.state.openInfo)} onClose = {this.handleCloseInfo}/>
<Button disabled={this.state.loadingRobot} color='secondary' to='/book/' component={Link}>{t("View Book")}</Button>
</ButtonGroup>
</Grid>
<Grid item xs={12} align="center" spacing={2} sx={{width:370}}>
<Grid item xs={12} align="center" sx={{width:370}}>
<Grid item>
<div style={{height:40}}/>
</Grid>
<div style={{width:370, left:30}}>
<Grid container xs={12} align="center">
<Grid container align="center">
<Grid item xs={0.8}/>
<Grid item xs={7.5} align="right">
<Typography component="h5" variant="h5">

View File

@ -1,78 +0,0 @@
import React, { Component } from 'react';
import Flags from 'country-flag-icons/react/3x2'
import SwapCallsIcon from '@mui/icons-material/SwapCalls';
import GoldIcon from './icons/GoldIcon';
import EarthIcon from './icons/EarthIcon'
export default function getFlags(code){
const props = {width:20,height:20}
var flag = "";
if(code == 'AUD') flag = <Flags.AU {...props}/>;
if(code == 'ARS') flag = <Flags.AR {...props}/>;
if(code == 'BRL') flag = <Flags.BR {...props}/>;
if(code == 'CAD') flag = <Flags.CA {...props}/>;
if(code == 'CHF') flag = <Flags.CH {...props}/>;
if(code == 'CLP') flag = <Flags.CL {...props}/>;
if(code == 'CNY') flag = <Flags.CN {...props}/>;
if(code == 'EUR') flag = <Flags.EU {...props}/>;
if(code == 'HRK') flag = <Flags.HR {...props}/>;
if(code == 'CZK') flag = <Flags.CZ {...props}/>;
if(code == 'DKK') flag = <Flags.DK {...props}/>;
if(code == 'GBP') flag = <Flags.GB {...props}/>;
if(code == 'HKD') flag = <Flags.HK {...props}/>;
if(code == 'HUF') flag = <Flags.HU {...props}/>;
if(code == 'INR') flag = <Flags.IN {...props}/>;
if(code == 'ISK') flag = <Flags.IS {...props}/>;
if(code == 'JPY') flag = <Flags.JP {...props}/>;
if(code == 'KRW') flag = <Flags.KR {...props}/>;
if(code == 'MXN') flag = <Flags.MX {...props}/>;
if(code == 'NOK') flag = <Flags.NO {...props}/>;
if(code == 'NZD') flag = <Flags.NZ {...props}/>;
if(code == 'PLN') flag = <Flags.PL {...props}/>;
if(code == 'RON') flag = <Flags.RO {...props}/>;
if(code == 'RUB') flag = <Flags.RU {...props}/>;
if(code == 'SEK') flag = <Flags.SE {...props}/>;
if(code == 'SGD') flag = <Flags.SG {...props}/>;
if(code == 'VES') flag = <Flags.VE {...props}/>;
if(code == 'TRY') flag = <Flags.TR {...props}/>;
if(code == 'USD') flag = <Flags.US {...props}/>;
if(code == 'ZAR') flag = <Flags.ZA {...props}/>;
if(code == 'COP') flag = <Flags.CO {...props}/>;
if(code == 'PEN') flag = <Flags.PE {...props}/>;
if(code == 'UYU') flag = <Flags.UY {...props}/>;
if(code == 'PYG') flag = <Flags.PY {...props}/>;
if(code == 'BOB') flag = <Flags.BO {...props}/>;
if(code == 'IDR') flag = <Flags.ID {...props}/>;
if(code == 'ANG') flag = <Flags.CW {...props}/>;
if(code == 'CRC') flag = <Flags.CR {...props}/>;
if(code == 'CUP') flag = <Flags.CU {...props}/>;
if(code == 'DOP') flag = <Flags.DO {...props}/>;
if(code == 'GHS') flag = <Flags.GH {...props}/>;
if(code == 'GTQ') flag = <Flags.GT {...props}/>;
if(code == 'ILS') flag = <Flags.IL {...props}/>;
if(code == 'JMD') flag = <Flags.JM {...props}/>;
if(code == 'KES') flag = <Flags.KE {...props}/>;
if(code == 'KZT') flag = <Flags.KZ {...props}/>;
if(code == 'MYR') flag = <Flags.MY {...props}/>;
if(code == 'NAD') flag = <Flags.NA {...props}/>;
if(code == 'NGN') flag = <Flags.NG {...props}/>;
if(code == 'AZN') flag = <Flags.AZ {...props}/>;
if(code == 'PAB') flag = <Flags.PA {...props}/>;
if(code == 'PHP') flag = <Flags.PH {...props}/>;
if(code == 'PKR') flag = <Flags.PK {...props}/>;
if(code == 'QAR') flag = <Flags.QA {...props}/>;
if(code == 'SAR') flag = <Flags.SA {...props}/>;
if(code == 'THB') flag = <Flags.TH {...props}/>;
if(code == 'TTD') flag = <Flags.TT {...props}/>;
if(code == 'VND') flag = <Flags.VN {...props}/>;
if(code == 'XOF') flag = <Flags.BJ {...props}/>;
if(code == 'TWD') flag = <Flags.TW {...props}/>;
if(code == 'TZS') flag = <Flags.TZ {...props}/>;
if(code == 'XAF') flag = <Flags.CM {...props}/>;
if(code == 'UAH') flag = <Flags.UA {...props}/>;
if(code == 'ANY') flag = <EarthIcon {...props}/>;
if(code == 'XAU') flag = <GoldIcon {...props}/>;
if(code == 'BTC') flag = <SwapCallsIcon color="primary"/>;
return <div style={{width:28, height: 20}}>{flag}</div>;
};

View File

@ -3,7 +3,7 @@ import { SvgIcon } from "@mui/material"
export default function GoldIcon(props) {
return (
<SvgIcon x="0px" y="0px" viewBox="0 0 511.882 511.882">
<SvgIcon {...props} x="0px" y="0px" viewBox="0 0 511.882 511.882">
<polygon style={{fill:"#F6BB42"}} points="350.216,176.572 278.374,158.615 37.038,264.123 0,338.207 125.753,374.324 386.13,258.531
"/>
<polygon style={{fill:"#FFCE54"}} points="350.216,176.572 107.756,284.345 125.753,374.324 386.13,258.531 "/>

View File

@ -1,11 +0,0 @@
export const pn = (value) => {
if (value == null || value == undefined) {
return;
}
let parts = value.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
};

View File

@ -0,0 +1,11 @@
export const pn = (value?: number | null): string | undefined => {
if (value === null || value === undefined) {
return;
}
const parts = value.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
};

17
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
},
"include": ["src"]
}

View File

@ -1,37 +0,0 @@
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "./static/frontend"),
filename: "[name].js",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
optimization: {
minimize: true,
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
// This has effect on the react lib size
NODE_ENV: JSON.stringify("production"),
},
}),
//new webpack.optimize.AggressiveMergingPlugin() //Merge chunks
],
resolve: {
extensions: ['.ts', '.js'],
},
};

View File

@ -0,0 +1,33 @@
import path from "path";
import { Configuration } from "webpack";
const config: Configuration = {
entry: "./src/index.js",
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
},
},
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
output: {
path: path.resolve(__dirname, "static/frontend"),
filename: "main.js",
},
};
export default config;