robosats/frontend/src/components/MakerPage.js

697 lines
29 KiB
JavaScript
Raw Normal View History

2022-01-02 00:37:04 +00:00
import React, { Component } from 'react';
import { LinearProgress, Link, Checkbox, Slider, Box, Tab, Tabs, SliderThumb, Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material"
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 getFlags from './getFlags'
import LockIcon from '@mui/icons-material/Lock';
2022-01-02 00:37:04 +00:00
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
2022-01-02 00:37:04 +00:00
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// pretty numbers
function pn(x) {
2022-02-01 00:45:58 +00:00
if(x==null){
return(null)
}
var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
2022-01-02 00:37:04 +00:00
export default class MakerPage extends Component {
defaultCurrency = 1;
defaultCurrencyCode = 'USD';
2022-01-10 01:12:58 +00:00
defaultPaymentMethod = "not specified";
2022-01-02 00:37:04 +00:00
defaultPremium = 0;
2022-03-07 17:04:29 +00:00
minTradeSats = 20000;
maxTradeSats = 800000;
maxBondlessSats = 50000;
maxRangeAmountMultiple = 4.8;
minRangeAmountMultiple = 1.6;
2022-01-02 00:37:04 +00:00
constructor(props) {
super(props);
this.state={
is_explicit: false,
2022-01-02 00:37:04 +00:00
type: 0,
currency: this.defaultCurrency,
currencyCode: this.defaultCurrencyCode,
2022-01-02 09:40:19 +00:00
payment_method: this.defaultPaymentMethod,
2022-01-02 00:37:04 +00:00
premium: 0,
satoshis: null,
currencies_dict: {"1":"USD"},
showAdvanced: false,
allowBondless: false,
2022-03-18 21:21:13 +00:00
publicExpiryTime: new Date(0, 0, 0, 23, 59),
2022-03-16 13:23:48 +00:00
enableAmountRange: false,
minAmount: null,
bondSize: 1,
limits: null,
minAmount: null,
maxAmount: null,
loadingLimits: false,
2022-01-02 00:37:04 +00:00
}
2022-01-08 11:51:55 +00:00
this.getCurrencyDict()
2022-01-02 00:37:04 +00:00
}
getLimits() {
this.setState({loadingLimits:true})
fetch('/api/limits/')
.then((response) => response.json())
.then((data) => this.setState({
limits:data,
loadingLimits:false,
minAmount: parseFloat(Number(data[this.state.currency]['max_amount']*0.25).toPrecision(2)),
maxAmount: this.state.amount ? this.state.amount : parseFloat(Number(data[this.state.currency]['max_amount']*0.75).toPrecision(2)),
}));
}
a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
2022-01-02 00:37:04 +00:00
handleTypeChange=(e)=>{
this.setState({
type: e.target.value,
});
}
handleCurrencyChange=(e)=>{
this.setState({
currency: e.target.value,
2022-01-08 11:51:55 +00:00
currencyCode: this.getCurrencyCode(e.target.value),
2022-01-02 00:37:04 +00:00
});
if(this.state.enableAmountRange){
this.setState({
minAmount: parseFloat(Number(this.state.limits[e.target.value]['max_amount']*0.25).toPrecision(2)),
maxAmount: parseFloat(Number(this.state.limits[e.target.value]['max_amount']*0.75).toPrecision(2)),
})
}
2022-01-02 00:37:04 +00:00
}
handleAmountChange=(e)=>{
this.setState({
amount: e.target.value,
});
}
handleMinAmountChange=(e)=>{
this.setState({
minAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
});
}
handleMaxAmountChange=(e)=>{
this.setState({
maxAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
});
}
2022-03-16 13:23:48 +00:00
handleRangeAmountChange = (e, newValue, activeThumb) => {
var maxAmount = this.getMaxAmount();
var minAmount = this.getMinAmount();
var lowerValue = e.target.value[0];
var upperValue = e.target.value[1];
var minRange = this.minRangeAmountMultiple;
var maxRange = this.maxRangeAmountMultiple;
if (lowerValue > maxAmount/minRange){
lowerValue = maxAmount/minRange
}
if (upperValue < minRange*minAmount){
upperValue = minRange*minAmount
}
if (lowerValue > upperValue/minRange) {
if (activeThumb === 0) {
upperValue = minRange*lowerValue
} else {
lowerValue = upperValue/minRange
}
}else if(lowerValue < upperValue/maxRange){
if (activeThumb === 0) {
upperValue = maxRange*lowerValue
} else {
lowerValue = upperValue/maxRange
}
}
2022-03-16 13:23:48 +00:00
this.setState({
minAmount: parseFloat(Number(lowerValue).toPrecision(lowerValue < 100 ? 2 : 3)),
maxAmount: parseFloat(Number(upperValue).toPrecision(upperValue < 100 ? 2 : 3)),
2022-03-16 13:23:48 +00:00
});
}
2022-01-02 09:40:19 +00:00
handlePaymentMethodChange=(e)=>{
this.setState({
2022-02-01 00:45:58 +00:00
payment_method: e.target.value,
badPaymentMethod: e.target.value.length > 35,
2022-01-02 09:40:19 +00:00
});
}
2022-01-02 00:37:04 +00:00
handlePremiumChange=(e)=>{
2022-02-01 00:45:58 +00:00
if(e.target.value > 999){
var bad_premium = "Must be less than 999%"
}
if(e.target.value < -100){
var bad_premium = "Must be more than -100%"
}
2022-01-02 00:37:04 +00:00
this.setState({
2022-02-01 00:45:58 +00:00
premium: e.target.value,
badPremium: bad_premium,
2022-01-02 00:37:04 +00:00
});
}
2022-02-01 00:45:58 +00:00
2022-01-02 00:37:04 +00:00
handleSatoshisChange=(e)=>{
2022-02-01 00:45:58 +00:00
if(e.target.value > this.maxTradeSats){
var bad_sats = "Must be less than " + pn(this.maxTradeSats)
}
if(e.target.value < this.minTradeSats){
var bad_sats = "Must be more than "+pn(this.minTradeSats)
}
2022-01-10 01:12:58 +00:00
2022-01-02 00:37:04 +00:00
this.setState({
2022-01-10 01:12:58 +00:00
satoshis: e.target.value,
badSatoshis: bad_sats,
2022-02-01 00:45:58 +00:00
});
}
2022-01-02 00:37:04 +00:00
handleClickRelative=(e)=>{
this.setState({
is_explicit: false,
2022-01-02 00:37:04 +00:00
});
this.handlePremiumChange();
2022-01-02 00:37:04 +00:00
}
handleClickExplicit=(e)=>{
2022-01-02 00:37:04 +00:00
this.setState({
is_explicit: true,
2022-01-02 00:37:04 +00:00
});
this.handleSatoshisChange();
2022-01-02 00:37:04 +00:00
}
handleCreateOfferButtonPressed=()=>{
this.state.amount == null ? this.setState({amount: 0}) : null;
2022-01-02 00:37:04 +00:00
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')},
2022-01-02 00:37:04 +00:00
body: JSON.stringify({
type: this.state.type,
currency: this.state.currency,
amount: this.state.amount,
has_range: this.state.enableAmountRange,
min_amount: this.state.minAmount,
max_amount: this.state.maxAmount,
2022-01-02 09:40:19 +00:00
payment_method: this.state.payment_method,
is_explicit: this.state.is_explicit,
2022-02-01 00:45:58 +00:00
premium: this.state.is_explicit ? null: this.state.premium,
satoshis: this.state.is_explicit ? this.state.satoshis: null,
2022-03-18 21:21:13 +00:00
public_duration: this.state.publicDuration,
2022-03-18 22:09:38 +00:00
bond_size: this.state.bondSize,
bondless_taker: this.state.allowBondless,
2022-01-02 00:37:04 +00:00
}),
};
fetch("/api/make/",requestOptions)
.then((response) => response.json())
.then((data) => (this.setState({badRequest:data.bad_request})
& (data.id ? this.props.history.push('/order/' + data.id) :"")));
2022-01-02 00:37:04 +00:00
}
2022-01-08 11:51:55 +00:00
getCurrencyDict() {
fetch('/static/assets/currencies.json')
2022-01-08 11:51:55 +00:00
.then((response) => response.json())
.then((data) =>
this.setState({
currencies_dict: data
}));
}
getCurrencyCode(val){
return this.state.currencies_dict[val.toString()]
2022-01-02 00:37:04 +00:00
}
handleInputBondSizeChange = (event) => {
this.setState({bondSize: event.target.value === '' ? 1 : Number(event.target.value)});
};
StandardMakerOptions = () => {
return(
<Paper elevation={12} style={{ padding: 8, width:'260px', align:'center'}}>
<Grid item xs={12} align="center" spacing={1}>
<FormControl component="fieldset">
<FormHelperText>
Buy or Sell Bitcoin?
</FormHelperText>
<RadioGroup row defaultValue="0" onChange={this.handleTypeChange}>
<FormControlLabel
value="0"
control={<Radio color="primary"/>}
label="Buy"
labelPlacement="Top"
/>
<FormControlLabel
value="1"
control={<Radio color="secondary"/>}
label="Sell"
labelPlacement="Top"
/>
</RadioGroup>
</FormControl>
</Grid>
<Grid containter xs={12} alignItems="stretch" style={{ display: "flex" }}>
<div style={{maxWidth:140}}>
<Tooltip placement="top" enterTouchDelay="500" enterDelay="700" enterNextDelay="2000" title="Amount of fiat to exchange for bitcoin">
<TextField
disabled = {this.state.enableAmountRange}
variant = {this.state.enableAmountRange ? 'filled' : 'outlined'}
error={this.state.amount <= 0 & this.state.amount != "" }
helperText={this.state.amount <= 0 & this.state.amount != "" ? 'Invalid' : null}
label="Amount"
type="number"
required="true"
value={this.state.amount}
inputProps={{
min:0 ,
style: {textAlign:"center"}
}}
onChange={this.handleAmountChange}
/>
</Tooltip>
</div>
<div >
<Select
required="true"
defaultValue={this.defaultCurrency}
2022-01-10 01:12:58 +00:00
inputProps={{
style: {textAlign:"center"}
}}
onChange={this.handleCurrencyChange}>
{Object.entries(this.state.currencies_dict)
.map( ([key, value]) => <MenuItem value={parseInt(key)}>
<div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>{getFlags(value)}{" "+value}</div>
</MenuItem> )}
</Select>
</div>
</Grid>
<br/>
<Grid item xs={12} align="center">
<Tooltip placement="top" enterTouchDelay="300" enterDelay="700" enterNextDelay="2000" title="Enter your preferred fiat payment methods. Instant recommended (e.g., Revolut, CashApp ...)">
<TextField
sx={{width:240}}
label={this.state.currency==1000 ? "Swap Destination (e.g. rBTC)":"Fiat Payment Method(s)"}
error={this.state.badPaymentMethod}
helperText={this.state.badPaymentMethod ? "Must be shorter than 35 characters":""}
type="text"
require={true}
inputProps={{
style: {textAlign:"center"},
maxLength: 35
}}
onChange={this.handlePaymentMethodChange}
/>
</Tooltip>
</Grid>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<FormHelperText >
<div align='center'>
Choose a Pricing Method
</div>
</FormHelperText>
<RadioGroup row defaultValue="relative">
<Tooltip placement="top" enterTouchDelay="0" enterDelay="1000" enterNextDelay="2000" title="Let the price move with the market">
<FormControlLabel
value="relative"
control={<Radio color="primary"/>}
label="Relative"
labelPlacement="Top"
onClick={this.handleClickRelative}
/>
</Tooltip>
<Tooltip placement="top" enterTouchDelay="0" enterDelay="1000" enterNextDelay="2000" title="Set a fix amount of satoshis">
<FormControlLabel
value="explicit"
control={<Radio color="secondary"/>}
label="Explicit"
labelPlacement="Top"
onClick={this.handleClickExplicit}
/>
</Tooltip>
</RadioGroup>
</FormControl>
</Grid>
{/* conditional shows either Premium % field or Satoshis field based on pricing method */}
<Grid item xs={12} align="center">
<div style={{display: this.state.is_explicit ? '':'none'}}>
<TextField
2022-02-01 00:45:58 +00:00
sx={{width:240}}
label="Satoshis"
error={this.state.badSatoshis}
helperText={this.state.badSatoshis}
type="number"
required="true"
value={this.state.satoshis}
inputProps={{
// TODO read these from .env file
min:this.minTradeSats ,
max:this.maxTradeSats ,
style: {textAlign:"center"}
}}
onChange={this.handleSatoshisChange}
// defaultValue={this.defaultSatoshis}
/>
</div>
<div style={{display: this.state.is_explicit ? 'none':''}}>
<TextField
2022-02-01 00:45:58 +00:00
sx={{width:240}}
error={this.state.badPremium}
helperText={this.state.badPremium}
label="Premium over Market (%)"
type="number"
// defaultValue={this.defaultPremium}
inputProps={{
min: -100,
max: 999,
style: {textAlign:"center"}
}}
onChange={this.handlePremiumChange}
/>
</div>
</Grid>
</Paper>
)
}
2022-03-18 21:21:13 +00:00
handleChangePublicDuration = (date) => {
let d = new Date(date),
hours = d.getHours(),
minutes = d.getMinutes();
var total_secs = hours*60*60 + minutes * 60;
this.setState({
changedPublicExpiryTime: true,
publicExpiryTime: date,
publicDuration: total_secs,
badDuration: false,
});
}
getMaxAmount = () => {
if (this.state.limits == null){
var max_amount = null
}else{
var max_amount = this.state.limits[this.state.currency]['max_amount']
}
// times 0.98 to allow a bit of margin with respect to the backend minimum
return parseFloat(Number(max_amount*0.98).toPrecision(2))
}
getMinAmount = () => {
if (this.state.limits == null){
var min_amount = null
}else{
var min_amount = this.state.limits[this.state.currency]['min_amount']
}
// times 1.1 to allow a bit of margin with respect to the backend minimum
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 (
<SliderThumb {...other}>
{children}
<span className="range-bar" />
<span className="range-bar" />
<span className="range-bar" />
</SliderThumb>
);
}
rangeText =()=> {
return (
<div style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>
<span style={{width: 40}}>From</span>
<TextField
variant="standard"
size="small"
value={this.state.minAmount}
onChange={this.handleMinAmountChange}
error={this.state.minAmount < this.getMinAmount()}
sx={{width: this.state.minAmount.toString().length * 10, maxWidth: 40}}
/>
<span style={{width: 20}}>to</span>
<TextField
variant="standard"
size="small"
value={this.state.maxAmount}
error={this.state.maxAmount > this.getMaxAmount()}
onChange={this.handleMaxAmountChange}
sx={{width: this.state.maxAmount.toString().length * 10, maxWidth: 50}}
/>
<span>{this.state.currencyCode}</span>
</div>
)
}
AdvancedMakerOptions = () => {
return(
<Paper elevation={12} style={{ padding: 8, width:'280px', align:'center'}}>
2022-03-18 22:09:38 +00:00
<Grid container xs={12} spacing={1}>
2022-03-18 22:09:38 +00:00
<Grid item xs={12} align="center" spacing={1}>
2022-03-16 13:23:48 +00:00
<FormControl align="center">
<FormHelperText>
<Tooltip enterTouchDelay="0" placement="top" align="center"title={"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}) & (e.target.checked ? this.getLimits() : null)}/>
{this.state.enableAmountRange & this.state.minAmount != null? <this.rangeText/> : "Enable Amount Range"}
2022-03-16 13:23:48 +00:00
</div>
</Tooltip>
</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?
2022-03-16 13:23:48 +00:00
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}
2022-03-16 13:23:48 +00:00
/>
</div>
2022-03-16 13:23:48 +00:00
</FormControl>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<LocalizationProvider dateAdapter={DateFnsUtils}>
<TimePicker
sx={{width:210, align:"center"}}
ampm={false}
openTo="hours"
views={['hours', 'minutes']}
inputFormat="HH:mm"
mask="__:__"
renderInput={(props) => <TextField {...props} />}
label="Public Duration (HH:mm)"
value={this.state.publicExpiryTime}
onChange={this.handleChangePublicDuration}
minTime={new Date(0, 0, 0, 0, 10)}
maxTime={new Date(0, 0, 0, 23, 59)}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<FormControl align="center">
<Tooltip enterDelay="800" enterTouchDelay="0" placement="top" title={"Set the skin-in-the-game, increase for higher safety assurance"}>
<FormHelperText>
<div align="center" style={{display:'flex',flexWrap:'wrap', transform: 'translate(20%, 0)'}}>
Fidelity Bond Size <LockIcon sx={{height:20,width:20}}/>
</div>
</FormHelperText>
</Tooltip>
<Slider
sx={{width:220, align:"center"}}
aria-label="Bond Size (%)"
defaultValue={1}
valueLabelDisplay="auto"
valueLabelFormat={(x) => (x+'%')}
step={0.25}
marks={[{value: 1,label: '1%'},{value: 5,label: '5%'},{value: 10,label: '10%'},{value: 15,label: '15%'}]}
min={1}
max={15}
onChange={(e) => this.setState({bondSize: e.target.value})}
/>
</FormControl>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
2022-03-18 21:21:13 +00:00
<Tooltip enterTouchDelay="0" title={"COMING SOON - High risk! Limited to "+ this.maxBondlessSats/1000 +"K Sats"}>
<FormControlLabel
label={<a>Allow bondless taker (<Link href="https://git.robosats.com" target="_blank">info</Link>)</a>}
control={
<Checkbox
disabled
//disabled={this.state.type==0}
color="secondary"
checked={this.state.allowBondless}
onChange={()=> this.setState({allowBondless: !this.state.allowBondless})}
/>
}
/>
</Tooltip>
</Grid>
</Grid>
</Paper>
)
}
makeOrderBox=()=>{
const [value, setValue] = React.useState(this.state.showAdvanced);
const handleChange = (event, newValue) => {
this.setState({showAdvanced:newValue})
setValue(newValue);
};
return(
<Box sx={{width: this.state.showAdvanced? '270px':'252px'}}>
<Box sx={{ borderBottom: 1, borderColor: 'divider', position:'relative',left:'5px'}}>
<Tabs value={value? value:0} onChange={handleChange} variant="fullWidth" >
<Tab label="Basic" {...this.a11yProps(0)} />
<Tab label="Advanced" {...this.a11yProps(1)} />
</Tabs>
</Box>
<Grid item xs={12} align="center" spacing={1}>
<div style={{ display: this.state.showAdvanced == false ? '':'none'}}>
<this.StandardMakerOptions/>
</div>
<div style={{ display: this.state.showAdvanced == true ? '':'none'}}>
<this.AdvancedMakerOptions/>
</div>
</Grid>
</Box>
)
}
render() {
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> */}
<Grid item xs={12} align="center">
<this.makeOrderBox/>
</Grid>
2022-01-02 00:37:04 +00:00
<Grid item xs={12} align="center">
{/* conditions to disable the make button */}
{(this.state.amount == null & (this.state.enableAmountRange == false & this.state.minAmount == null) ||
this.state.amount <= 0 & !this.state.enableAmountRange ||
(this.state.is_explicit & (this.state.badSatoshis != null || this.state.satoshis == null)) ||
(!this.state.is_explicit & this.state.badPremium != null))
?
<Tooltip enterTouchDelay="0" title="You must fill the form correctly">
<div><Button disabled color="primary" variant="contained" onClick={this.handleCreateOfferButtonPressed} >Create Order</Button></div>
</Tooltip>
:
<Button color="primary" variant="contained" onClick={this.handleCreateOfferButtonPressed} >Create Order</Button>
}
</Grid>
<Grid item xs={12} align="center">
{this.state.badRequest ?
<Typography component="subtitle2" variant="subtitle2" color="secondary">
{this.state.badRequest} <br/>
</Typography>
: ""}
2022-01-02 00:37:04 +00:00
<Typography component="subtitle2" variant="subtitle2">
<div align='center'>
Create a BTC {this.state.type==0 ? "buy":"sell"} order for {this.state.enableAmountRange & this.state.minAmount != null?
" "+this.state.minAmount+"-"+this.state.maxAmount : pn(this.state.amount)} {this.state.currencyCode}
2022-02-01 00:45:58 +00:00
{this.state.is_explicit ? " of " + pn(this.state.satoshis) + " Satoshis" :
2022-01-02 00:37:04 +00:00
(this.state.premium == 0 ? " at market price" :
(this.state.premium > 0 ? " at a " + this.state.premium + "% premium":" at a " + -this.state.premium + "% discount")
)
}
</div>
</Typography>
2022-01-03 23:05:19 +00:00
<Grid item xs={12} align="center">
<Button color="secondary" variant="contained" to="/" component={LinkRouter}>
2022-01-03 23:05:19 +00:00
Back
</Button>
</Grid>
2022-01-02 00:37:04 +00:00
</Grid>
</Grid>
);
}
}