mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Add MakerForm functional component (#265)
* Create maker form model * Add MakerForm functional component * Add advance options switch * Add range slider * Add timers, bond size, helpers and submit button * Style maker form * Add filter toolbar to maker page * Add new Maker Page * Fix MakerPage * Add match maker * Add order filter util, run lint:fix * Fix book filter by payment method * Add confirmation dialogs * Add Book page as functional component * Load orders and limits on app start (homepage) * Turn BookPage and MakerPage into modules * Add translation keys * Fixes * More fixes
This commit is contained in:
parent
bd073eac08
commit
ddd9f3fc32
21
frontend/package-lock.json
generated
21
frontend/package-lock.json
generated
@ -7052,20 +7052,6 @@
|
|||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
|
||||||
"version": "2.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
@ -20038,13 +20024,6 @@
|
|||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"fsevents": {
|
|
||||||
"version": "2.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"function-bind": {
|
"function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
|
@ -107,24 +107,18 @@ export default class App extends Component {
|
|||||||
<TorConnection />
|
<TorConnection />
|
||||||
<IconButton
|
<IconButton
|
||||||
color='inherit'
|
color='inherit'
|
||||||
sx={{ position: 'fixed', right: '34px' }}
|
sx={{ position: 'fixed', right: '34px', color: 'text.secondary' }}
|
||||||
onClick={() => this.setState({ openLearn: true })}
|
onClick={() => this.setState({ openLearn: true })}
|
||||||
>
|
>
|
||||||
<SchoolIcon />
|
<SchoolIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
color='inherit'
|
color='inherit'
|
||||||
sx={{ position: 'fixed', right: '0px' }}
|
sx={{ position: 'fixed', right: '0px', color: 'text.secondary' }}
|
||||||
onClick={() => this.handleThemeChange()}
|
onClick={() => this.handleThemeChange()}
|
||||||
>
|
>
|
||||||
{this.state.theme.palette.mode === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
|
{this.state.theme.palette.mode === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
|
||||||
sx={{ position: 'fixed', right: '34px' }}
|
|
||||||
onClick={() => this.setState({ openLearn: true })}
|
|
||||||
>
|
|
||||||
<SchoolIcon />
|
|
||||||
</IconButton>
|
|
||||||
<UnsafeAlert className='unsafeAlert' />
|
<UnsafeAlert className='unsafeAlert' />
|
||||||
<HomePage {...this.state} />
|
<HomePage {...this.state} />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@ -1,350 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
ToggleButtonGroup,
|
|
||||||
ToggleButton,
|
|
||||||
Typography,
|
|
||||||
Grid,
|
|
||||||
Select,
|
|
||||||
MenuItem,
|
|
||||||
FormControl,
|
|
||||||
FormHelperText,
|
|
||||||
IconButton,
|
|
||||||
ButtonGroup,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import currencyDict from '../../static/assets/currencies.json';
|
|
||||||
import FlagWithProps from './FlagWithProps';
|
|
||||||
import DepthChart from './Charts/DepthChart';
|
|
||||||
import { apiClient } from '../services/api/index';
|
|
||||||
|
|
||||||
// Icons
|
|
||||||
import { BarChart, FormatListBulleted, Refresh } from '@mui/icons-material';
|
|
||||||
import BookTable from './BookTable';
|
|
||||||
|
|
||||||
class BookPage extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
pageSize: 6,
|
|
||||||
view: 'list',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount = () => {
|
|
||||||
if (this.props.bookOrders.length < 1) {
|
|
||||||
this.getOrderDetails(true, false);
|
|
||||||
} else {
|
|
||||||
this.getOrderDetails(false, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getOrderDetails(loading, refreshing) {
|
|
||||||
this.props.setAppState({ bookLoading: loading, bookRefreshing: refreshing });
|
|
||||||
apiClient.get('/api/book/').then((data) =>
|
|
||||||
this.props.setAppState({
|
|
||||||
bookNotFound: data.not_found,
|
|
||||||
bookLoading: false,
|
|
||||||
bookRefreshing: false,
|
|
||||||
bookOrders: data,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRowClick = (e) => {
|
|
||||||
this.props.history.push('/order/' + e);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCurrencyChange = (e) => {
|
|
||||||
const currency = e.target.value;
|
|
||||||
this.props.setAppState({
|
|
||||||
currency,
|
|
||||||
bookCurrencyCode: this.getCurrencyCode(currency),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getCurrencyCode(val) {
|
|
||||||
const { t } = this.props;
|
|
||||||
if (val) {
|
|
||||||
return val == 0 ? t('ANY_currency') : currencyDict[val.toString()];
|
|
||||||
} else {
|
|
||||||
return t('ANY_currency');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTypeChange = (mouseEvent, val) => {
|
|
||||||
this.props.setAppState({ type: val });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClickView = () => {
|
|
||||||
this.setState({ view: this.state.view == 'depth' ? 'list' : 'depth' });
|
|
||||||
};
|
|
||||||
|
|
||||||
NoOrdersFound = () => {
|
|
||||||
const { t } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid item xs={12} align='center'>
|
|
||||||
<Grid item xs={12} align='center'>
|
|
||||||
<Typography component='h5' variant='h5'>
|
|
||||||
{this.props.type == 0
|
|
||||||
? t('No orders found to sell BTC for {{currencyCode}}', {
|
|
||||||
currencyCode: this.props.bookCurrencyCode,
|
|
||||||
})
|
|
||||||
: t('No orders found to buy BTC for {{currencyCode}}', {
|
|
||||||
currencyCode: this.props.bookCurrencyCode,
|
|
||||||
})}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<br />
|
|
||||||
<Grid item>
|
|
||||||
<Button size='large' variant='contained' color='primary' to='/make/' component={Link}>
|
|
||||||
{t('Make Order')}
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
<Typography color='primary' variant='body1'>
|
|
||||||
<b>{t('Be the first one to create an order')}</b>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
mainView = (doubleView, widthEm, heightEm) => {
|
|
||||||
if (this.props.bookNotFound) {
|
|
||||||
return this.NoOrdersFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doubleView) {
|
|
||||||
const width = widthEm * 0.9;
|
|
||||||
const bookTableWidth = 85;
|
|
||||||
const chartWidthEm = width - bookTableWidth;
|
|
||||||
const tableWidthXS = (bookTableWidth / width) * 12;
|
|
||||||
const chartWidthXS = (chartWidthEm / width) * 12;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
alignItems='center'
|
|
||||||
justifyContent='flex-start'
|
|
||||||
spacing={1}
|
|
||||||
direction='row'
|
|
||||||
style={{ width: `${widthEm}em`, position: 'relative', left: `${widthEm / 140}em` }}
|
|
||||||
>
|
|
||||||
<Grid item xs={tableWidthXS} style={{ width: `${bookTableWidth}em` }}>
|
|
||||||
<BookTable
|
|
||||||
loading={this.props.bookLoading}
|
|
||||||
refreshing={this.props.bookRefreshing}
|
|
||||||
clickRefresh={() => this.getOrderDetails(false, true)}
|
|
||||||
orders={this.props.bookOrders}
|
|
||||||
type={this.props.type}
|
|
||||||
currency={this.props.currency}
|
|
||||||
maxWidth={bookTableWidth} // EM units
|
|
||||||
maxHeight={heightEm * 0.8 - 11} // EM units
|
|
||||||
fullWidth={widthEm} // EM units
|
|
||||||
fullHeight={heightEm} // EM units
|
|
||||||
defaultFullscreen={false}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid
|
|
||||||
item
|
|
||||||
xs={chartWidthXS}
|
|
||||||
style={{ width: `${chartWidthEm}em`, position: 'relative', left: '-10em' }}
|
|
||||||
>
|
|
||||||
<DepthChart
|
|
||||||
bookLoading={this.props.bookLoading}
|
|
||||||
orders={this.props.bookOrders}
|
|
||||||
lastDayPremium={this.props.lastDayPremium}
|
|
||||||
currency={this.props.currency}
|
|
||||||
compact={true}
|
|
||||||
setAppState={this.props.setAppState}
|
|
||||||
limits={this.props.limits}
|
|
||||||
maxWidth={chartWidthEm} // EM units
|
|
||||||
maxHeight={heightEm * 0.8 - 11} // EM units
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (this.state.view === 'depth') {
|
|
||||||
return (
|
|
||||||
<DepthChart
|
|
||||||
bookLoading={this.props.bookLoading}
|
|
||||||
orders={this.props.bookOrders}
|
|
||||||
lastDayPremium={this.props.lastDayPremium}
|
|
||||||
currency={this.props.currency}
|
|
||||||
compact={true}
|
|
||||||
setAppState={this.props.setAppState}
|
|
||||||
limits={this.props.limits}
|
|
||||||
maxWidth={widthEm * 0.8} // EM units
|
|
||||||
maxHeight={heightEm * 0.8 - 11} // EM units
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<BookTable
|
|
||||||
loading={this.props.bookLoading}
|
|
||||||
refreshing={this.props.bookRefreshing}
|
|
||||||
clickRefresh={() => this.getOrderDetails(false, true)}
|
|
||||||
orders={this.props.bookOrders}
|
|
||||||
type={this.props.type}
|
|
||||||
currency={this.props.currency}
|
|
||||||
maxWidth={widthEm * 0.97} // EM units
|
|
||||||
maxHeight={heightEm * 0.8 - 11} // EM units
|
|
||||||
fullWidth={widthEm} // EM units
|
|
||||||
fullHeight={heightEm} // EM units
|
|
||||||
defaultFullscreen={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getTitle = (doubleView) => {
|
|
||||||
const { t } = this.props;
|
|
||||||
|
|
||||||
if (this.state.view == 'list' || doubleView) {
|
|
||||||
if (this.props.type == 0) {
|
|
||||||
return t('You are SELLING BTC for {{currencyCode}}', {
|
|
||||||
currencyCode: this.props.bookCurrencyCode,
|
|
||||||
});
|
|
||||||
} else if (this.props.type == 1) {
|
|
||||||
return t('You are BUYING BTC for {{currencyCode}}', {
|
|
||||||
currencyCode: this.props.bookCurrencyCode,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return t('You are looking at all');
|
|
||||||
}
|
|
||||||
} else if (this.state.view == 'depth') {
|
|
||||||
return t('Depth chart');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mainFilters = () => {
|
|
||||||
const { t } = this.props;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Grid item xs={6} align='right'>
|
|
||||||
<FormControl align='center'>
|
|
||||||
<FormHelperText align='center' sx={{ textAlign: 'center' }}>
|
|
||||||
{t('I want to')}
|
|
||||||
</FormHelperText>
|
|
||||||
<div style={{ textAlign: 'center' }}>
|
|
||||||
<ToggleButtonGroup
|
|
||||||
sx={{ height: '3.52em' }}
|
|
||||||
size='large'
|
|
||||||
exclusive={true}
|
|
||||||
value={this.props.type}
|
|
||||||
onChange={this.handleTypeChange}
|
|
||||||
>
|
|
||||||
<ToggleButton value={1} color={'primary'}>
|
|
||||||
{t('Buy')}
|
|
||||||
</ToggleButton>
|
|
||||||
<ToggleButton value={0} color={'secondary'}>
|
|
||||||
{t('Sell')}
|
|
||||||
</ToggleButton>
|
|
||||||
</ToggleButtonGroup>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={6} align='left'>
|
|
||||||
<FormControl align='center'>
|
|
||||||
<FormHelperText
|
|
||||||
align='center'
|
|
||||||
sx={{ textAlign: 'center', position: 'relative', left: '-5px' }}
|
|
||||||
>
|
|
||||||
{this.props.type == 0
|
|
||||||
? t('and receive')
|
|
||||||
: this.props.type == 1
|
|
||||||
? t('and pay with')
|
|
||||||
: t('and use')}
|
|
||||||
</FormHelperText>
|
|
||||||
<Select
|
|
||||||
// autoWidth={true}
|
|
||||||
sx={{ width: 120 }}
|
|
||||||
label={t('Select Payment Currency')}
|
|
||||||
required={true}
|
|
||||||
value={this.props.currency}
|
|
||||||
inputProps={{
|
|
||||||
style: { textAlign: 'center' },
|
|
||||||
}}
|
|
||||||
onChange={this.handleCurrencyChange}
|
|
||||||
>
|
|
||||||
<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 key={key} value={parseInt(key)}>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
|
||||||
<FlagWithProps code={value} />
|
|
||||||
{' ' + value}
|
|
||||||
</div>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { t } = this.props;
|
|
||||||
const widthEm = this.props.windowWidth / this.props.theme.typography.fontSize;
|
|
||||||
const heightEm = this.props.windowHeight / this.props.theme.typography.fontSize;
|
|
||||||
const doubleView = widthEm > 115;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid className='orderBook' container spacing={1} sx={{ minWidth: 400 }}>
|
|
||||||
{this.mainFilters()}
|
|
||||||
<Grid item xs={12} align='center'>
|
|
||||||
<Typography component='h5' variant='h5'>
|
|
||||||
{this.getTitle(doubleView)}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} align='center'>
|
|
||||||
{this.mainView(doubleView, widthEm, heightEm)}
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} align='center'>
|
|
||||||
<ButtonGroup variant='contained' aria-label='outlined primary button group'>
|
|
||||||
{!this.props.bookNotFound ? (
|
|
||||||
<>
|
|
||||||
<Button variant='contained' color='primary' to='/make/' component={Link}>
|
|
||||||
{t('Make Order')}
|
|
||||||
</Button>
|
|
||||||
{doubleView ? null : (
|
|
||||||
<Button
|
|
||||||
color='inherit'
|
|
||||||
style={{ color: '#111111' }}
|
|
||||||
onClick={this.handleClickView}
|
|
||||||
>
|
|
||||||
{this.state.view == 'depth' ? (
|
|
||||||
<>
|
|
||||||
<FormatListBulleted /> {t('List')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<BarChart /> {t('Chart')}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<Button color='secondary' variant='contained' to='/' component={Link}>
|
|
||||||
{t('Back')}
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTranslation()(BookPage);
|
|
247
frontend/src/components/BookPage/BookControl.tsx
Normal file
247
frontend/src/components/BookPage/BookControl.tsx
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
ToggleButton,
|
||||||
|
ToggleButtonGroup,
|
||||||
|
Select,
|
||||||
|
Divider,
|
||||||
|
MenuItem,
|
||||||
|
Box,
|
||||||
|
} from '@mui/material';
|
||||||
|
import currencyDict from '../../../static/assets/currencies.json';
|
||||||
|
import { useTheme } from '@mui/system';
|
||||||
|
import { AutocompletePayments } from '../MakerPage';
|
||||||
|
import { paymentMethods, swapDestinations } from '../payment-methods/Methods';
|
||||||
|
import FlagWithProps from '../FlagWithProps';
|
||||||
|
import PaymentIcon from '../payment-methods/Icons';
|
||||||
|
|
||||||
|
interface BookControlProps {
|
||||||
|
width: number;
|
||||||
|
type: number;
|
||||||
|
currency: number;
|
||||||
|
paymentMethod: string[];
|
||||||
|
onCurrencyChange: () => void;
|
||||||
|
onTypeChange: () => void;
|
||||||
|
setPaymentMethods: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BookControl = ({
|
||||||
|
width,
|
||||||
|
type,
|
||||||
|
currency,
|
||||||
|
onCurrencyChange,
|
||||||
|
onTypeChange,
|
||||||
|
paymentMethod,
|
||||||
|
setPaymentMethods,
|
||||||
|
}: BookControlProps): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const smallestToolbarWidth = (t('Buy').length + t('Sell').length) * 0.7 + 12;
|
||||||
|
const mediumToolbarWidth = smallestToolbarWidth + 12;
|
||||||
|
const verboseToolbarWidth =
|
||||||
|
mediumToolbarWidth + (t('and use').length + t('pay with').length) * 0.6;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
alignItems='flex-start'
|
||||||
|
direction='row'
|
||||||
|
justifyContent='center'
|
||||||
|
spacing={0.5}
|
||||||
|
sx={{ height: '3.4em', padding: '0.2em' }}
|
||||||
|
>
|
||||||
|
{width > verboseToolbarWidth ? (
|
||||||
|
<Grid item sx={{ position: 'relative', top: '0.5em' }}>
|
||||||
|
<Typography variant='caption' color='text.secondary'>
|
||||||
|
{t('I want to')}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Grid item>
|
||||||
|
<ToggleButtonGroup
|
||||||
|
sx={{
|
||||||
|
height: '2.6em',
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
border: '0.5px solid',
|
||||||
|
borderColor: 'text.disabled',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'text.primary',
|
||||||
|
border: '1px solid',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size='small'
|
||||||
|
exclusive={true}
|
||||||
|
value={type}
|
||||||
|
onChange={onTypeChange}
|
||||||
|
>
|
||||||
|
<ToggleButton value={1} color={'primary'}>
|
||||||
|
{t('Buy')}
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton value={0} color={'secondary'}>
|
||||||
|
{t('Sell')}
|
||||||
|
</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{width > verboseToolbarWidth ? (
|
||||||
|
<Grid item sx={{ position: 'relative', top: '0.5em' }}>
|
||||||
|
<Typography variant='caption' color='text.secondary'>
|
||||||
|
{t('and use')}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Grid item>
|
||||||
|
<Select
|
||||||
|
autoWidth
|
||||||
|
sx={{
|
||||||
|
height: '2.3em',
|
||||||
|
border: '0.5px solid',
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderColor: 'text.disabled',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'text.primary',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size='small'
|
||||||
|
label={t('Select Payment Currency')}
|
||||||
|
required={true}
|
||||||
|
value={currency}
|
||||||
|
inputProps={{
|
||||||
|
style: { textAlign: 'center' },
|
||||||
|
}}
|
||||||
|
onChange={onCurrencyChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<FlagWithProps code='ANY' />
|
||||||
|
<Typography sx={{ width: '2em' }} align='right' color='text.secondary'>
|
||||||
|
{' ' + t('ANY')}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
{Object.entries(currencyDict).map(([key, value]) => (
|
||||||
|
<MenuItem key={key} value={parseInt(key)} color='text.secondary'>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<FlagWithProps code={value} />
|
||||||
|
<Typography sx={{ width: '2em' }} align='right' color='text.secondary'>
|
||||||
|
{' ' + value}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{width > verboseToolbarWidth ? (
|
||||||
|
<Grid item sx={{ position: 'relative', top: '0.5em' }}>
|
||||||
|
<Typography variant='caption' color='text.secondary'>
|
||||||
|
{currency == 1000 ? t('swap to') : t('pay with')}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{width > mediumToolbarWidth ? (
|
||||||
|
<Grid item>
|
||||||
|
<AutocompletePayments
|
||||||
|
sx={{
|
||||||
|
minHeight: '2.6em',
|
||||||
|
width: '12em',
|
||||||
|
maxHeight: '2.6em',
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: theme.palette.text.disabled,
|
||||||
|
hoverBorderColor: 'text.primary',
|
||||||
|
}}
|
||||||
|
labelProps={{ sx: { top: '0.645em' } }}
|
||||||
|
tagProps={{ sx: { height: '1.8em' } }}
|
||||||
|
listBoxProps={{ sx: { width: '13em' } }}
|
||||||
|
onAutocompleteChange={setPaymentMethods}
|
||||||
|
value={paymentMethod}
|
||||||
|
optionsType={currency == 1000 ? 'swap' : 'fiat'}
|
||||||
|
error={false}
|
||||||
|
helperText={''}
|
||||||
|
label={currency == 1000 ? t('DESTINATION') : t('METHOD')}
|
||||||
|
tooltipTitle=''
|
||||||
|
listHeaderText=''
|
||||||
|
addNewButtonText=''
|
||||||
|
isFilter={true}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{width > smallestToolbarWidth && width < mediumToolbarWidth ? (
|
||||||
|
<Grid item>
|
||||||
|
<Select
|
||||||
|
sx={{
|
||||||
|
height: '2.3em',
|
||||||
|
border: '0.5px solid',
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderColor: 'text.disabled',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'text.primary',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size='small'
|
||||||
|
label={t('Select Payment Method')}
|
||||||
|
required={true}
|
||||||
|
renderValue={(value) => (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<PaymentIcon width={22} height={22} icon={value.icon} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
inputProps={{
|
||||||
|
style: { textAlign: 'center' },
|
||||||
|
}}
|
||||||
|
value={paymentMethod[0] ? paymentMethod[0] : ''}
|
||||||
|
onChange={(e) => setPaymentMethods([e.target.value])}
|
||||||
|
>
|
||||||
|
{currency === 1000
|
||||||
|
? swapDestinations.map((method, index) => (
|
||||||
|
<MenuItem
|
||||||
|
style={{ width: '10em' }}
|
||||||
|
key={index}
|
||||||
|
value={method}
|
||||||
|
color='text.secondary'
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<PaymentIcon width={22} height={22} icon={method.icon} />
|
||||||
|
<div style={{ width: '0.3em' }} />
|
||||||
|
<Typography sx={{ width: '2em' }} align='right' color='text.secondary'>
|
||||||
|
{' ' + method.name}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
: paymentMethods.map((method, index) => (
|
||||||
|
<MenuItem
|
||||||
|
style={{ width: '14em' }}
|
||||||
|
key={index}
|
||||||
|
value={method}
|
||||||
|
color='text.secondary'
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<PaymentIcon width={22} height={22} icon={method.icon} />
|
||||||
|
<div style={{ width: '0.3em' }} />
|
||||||
|
<Typography sx={{ width: '2em' }} align='right' color='text.secondary'>
|
||||||
|
{' ' + method.name}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
</Grid>
|
||||||
|
<Divider />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BookControl;
|
255
frontend/src/components/BookPage/BookPage.tsx
Normal file
255
frontend/src/components/BookPage/BookPage.tsx
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Button, Typography, Grid, ButtonGroup, Dialog, Box } from '@mui/material';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import currencyDict from '../../../static/assets/currencies.json';
|
||||||
|
import DepthChart from '../Charts/DepthChart';
|
||||||
|
|
||||||
|
import { Order, LimitList, Maker } from '../../models';
|
||||||
|
|
||||||
|
// Icons
|
||||||
|
import { BarChart, FormatListBulleted } from '@mui/icons-material';
|
||||||
|
import BookTable from './BookTable';
|
||||||
|
import { MakerForm } from '../MakerPage';
|
||||||
|
|
||||||
|
interface BookPageProps {
|
||||||
|
bookLoading?: boolean;
|
||||||
|
bookRefreshing?: boolean;
|
||||||
|
loadingLimits: boolean;
|
||||||
|
lastDayPremium: number;
|
||||||
|
orders: Order[];
|
||||||
|
limits: LimitList;
|
||||||
|
fetchLimits: () => void;
|
||||||
|
type: number;
|
||||||
|
currency: number;
|
||||||
|
windowWidth: number;
|
||||||
|
windowHeight: number;
|
||||||
|
fetchBook: (loading: boolean, refreshing: boolean) => void;
|
||||||
|
setAppState: (state: object) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BookPage = ({
|
||||||
|
bookLoading = false,
|
||||||
|
bookRefreshing = false,
|
||||||
|
lastDayPremium = 0,
|
||||||
|
loadingLimits,
|
||||||
|
orders = [],
|
||||||
|
limits,
|
||||||
|
fetchLimits,
|
||||||
|
type,
|
||||||
|
currency,
|
||||||
|
windowWidth,
|
||||||
|
windowHeight,
|
||||||
|
setAppState,
|
||||||
|
fetchBook,
|
||||||
|
}: BookPageProps): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const history = useHistory();
|
||||||
|
const [view, setView] = useState<'list' | 'depth'>('list');
|
||||||
|
const [openMaker, setOpenMaker] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const doubleView = windowWidth > 115;
|
||||||
|
const width = windowWidth * 0.9;
|
||||||
|
const maxBookTableWidth = 85;
|
||||||
|
const chartWidthEm = width - maxBookTableWidth;
|
||||||
|
|
||||||
|
const defaultMaker: Maker = {
|
||||||
|
isExplicit: false,
|
||||||
|
amount: '',
|
||||||
|
paymentMethods: [],
|
||||||
|
paymentMethodsText: 'not specified',
|
||||||
|
badPaymentMethod: false,
|
||||||
|
premium: '',
|
||||||
|
satoshis: '',
|
||||||
|
publicExpiryTime: new Date(0, 0, 0, 23, 59),
|
||||||
|
publicDuration: 86340,
|
||||||
|
escrowExpiryTime: new Date(0, 0, 0, 3, 0),
|
||||||
|
escrowDuration: 10800,
|
||||||
|
bondSize: 3,
|
||||||
|
minAmount: '',
|
||||||
|
maxAmount: '',
|
||||||
|
badPremiumText: '',
|
||||||
|
badSatoshisText: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const [maker, setMaker] = useState<Maker>(defaultMaker);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (orders.length < 1) {
|
||||||
|
fetchBook(true, false);
|
||||||
|
} else {
|
||||||
|
fetchBook(false, true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCurrencyChange = function (e) {
|
||||||
|
const currency = e.target.value;
|
||||||
|
setAppState({ currency });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTypeChange = function (mouseEvent, val) {
|
||||||
|
setAppState({ type: val });
|
||||||
|
};
|
||||||
|
|
||||||
|
const NoOrdersFound = function () {
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction='column'
|
||||||
|
justifyContent='center'
|
||||||
|
alignItems='center'
|
||||||
|
sx={{ width: '100%', height: '100%' }}
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<Typography align='center' component='h5' variant='h5'>
|
||||||
|
{type == 0
|
||||||
|
? t('No orders found to sell BTC for {{currencyCode}}', {
|
||||||
|
currencyCode: currency == 0 ? t('ANY') : currencyDict[currency.toString()],
|
||||||
|
})
|
||||||
|
: t('No orders found to buy BTC for {{currencyCode}}', {
|
||||||
|
currencyCode: currency == 0 ? t('ANY') : currencyDict[currency.toString()],
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Typography align='center' color='primary' variant='h6'>
|
||||||
|
{t('Be the first one to create an order')}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NavButtons = function () {
|
||||||
|
return (
|
||||||
|
<ButtonGroup variant='contained' color='inherit'>
|
||||||
|
<Button color='primary' onClick={() => setOpenMaker(true)}>
|
||||||
|
{t('Create Order')}
|
||||||
|
</Button>
|
||||||
|
{doubleView ? (
|
||||||
|
<></>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
color='inherit'
|
||||||
|
style={{ color: '#111111' }}
|
||||||
|
onClick={() => setView(view === 'depth' ? 'list' : 'depth')}
|
||||||
|
>
|
||||||
|
{view == 'depth' ? (
|
||||||
|
<>
|
||||||
|
<FormatListBulleted /> {t('List')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<BarChart /> {t('Chart')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button color='secondary' onClick={() => history.push('/')}>
|
||||||
|
{t('Back')}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Grid container direction='column' alignItems='center' spacing={1} sx={{ minWidth: 400 }}>
|
||||||
|
{openMaker ? (
|
||||||
|
<Dialog open={openMaker} onClose={() => setOpenMaker(false)}>
|
||||||
|
<Box sx={{ maxWidth: '18em', padding: '0.5em' }}>
|
||||||
|
<MakerForm
|
||||||
|
limits={limits}
|
||||||
|
fetchLimits={fetchLimits}
|
||||||
|
loadingLimits={loadingLimits}
|
||||||
|
pricingMethods={false}
|
||||||
|
setAppState={setAppState}
|
||||||
|
maker={maker}
|
||||||
|
defaultMaker={defaultMaker}
|
||||||
|
setMaker={setMaker}
|
||||||
|
type={type}
|
||||||
|
currency={currency}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Dialog>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
{doubleView ? (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
alignItems='center'
|
||||||
|
justifyContent='center'
|
||||||
|
spacing={1}
|
||||||
|
direction='row'
|
||||||
|
style={{ width: `${windowWidth}em` }}
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<BookTable
|
||||||
|
loading={bookLoading}
|
||||||
|
refreshing={bookRefreshing}
|
||||||
|
clickRefresh={() => fetchBook(false, true)}
|
||||||
|
orders={orders}
|
||||||
|
type={type}
|
||||||
|
currency={currency}
|
||||||
|
maxWidth={maxBookTableWidth} // EM units
|
||||||
|
maxHeight={windowHeight * 0.825 - 5} // EM units
|
||||||
|
fullWidth={windowWidth} // EM units
|
||||||
|
fullHeight={windowHeight} // EM units
|
||||||
|
defaultFullscreen={false}
|
||||||
|
onCurrencyChange={handleCurrencyChange}
|
||||||
|
onTypeChange={handleTypeChange}
|
||||||
|
noResultsOverlay={NoOrdersFound}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<DepthChart
|
||||||
|
orders={orders}
|
||||||
|
lastDayPremium={lastDayPremium}
|
||||||
|
currency={currency}
|
||||||
|
compact={true}
|
||||||
|
setAppState={setAppState}
|
||||||
|
limits={limits}
|
||||||
|
maxWidth={chartWidthEm} // EM units
|
||||||
|
maxHeight={windowHeight * 0.825 - 5} // EM units
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
) : view === 'depth' ? (
|
||||||
|
<DepthChart
|
||||||
|
bookLoading={bookLoading}
|
||||||
|
orders={orders}
|
||||||
|
lastDayPremium={lastDayPremium}
|
||||||
|
currency={currency}
|
||||||
|
compact={true}
|
||||||
|
setAppState={setAppState}
|
||||||
|
limits={limits}
|
||||||
|
maxWidth={windowWidth * 0.8} // EM units
|
||||||
|
maxHeight={windowHeight * 0.825 - 5} // EM units
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BookTable
|
||||||
|
loading={bookLoading}
|
||||||
|
refreshing={bookRefreshing}
|
||||||
|
clickRefresh={() => fetchBook(false, true)}
|
||||||
|
orders={orders}
|
||||||
|
type={type}
|
||||||
|
currency={currency}
|
||||||
|
maxWidth={windowWidth * 0.97} // EM units
|
||||||
|
maxHeight={windowHeight * 0.825 - 5} // EM units
|
||||||
|
fullWidth={windowWidth} // EM units
|
||||||
|
fullHeight={windowHeight} // EM units
|
||||||
|
defaultFullscreen={false}
|
||||||
|
onCurrencyChange={handleCurrencyChange}
|
||||||
|
onTypeChange={handleTypeChange}
|
||||||
|
noResultsOverlay={NoOrdersFound}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<NavButtons />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BookPage;
|
@ -7,7 +7,6 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
Typography,
|
Typography,
|
||||||
Paper,
|
Paper,
|
||||||
Stack,
|
|
||||||
ListItemButton,
|
ListItemButton,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
@ -15,38 +14,46 @@ import {
|
|||||||
CircularProgress,
|
CircularProgress,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { DataGrid, GridPagination } from '@mui/x-data-grid';
|
import { DataGrid, GridPagination } from '@mui/x-data-grid';
|
||||||
import currencyDict from '../../static/assets/currencies.json';
|
import currencyDict from '../../../static/assets/currencies.json';
|
||||||
import { Order } from '../models/Order.model';
|
import { Order } from '../../models';
|
||||||
|
import filterOrders from '../../utils/filterOrders';
|
||||||
|
import BookControl from './BookControl';
|
||||||
|
|
||||||
import FlagWithProps from './FlagWithProps';
|
import FlagWithProps from '../FlagWithProps';
|
||||||
import { pn, amountToString } from '../utils/prettyNumbers';
|
import { pn, amountToString } from '../../utils/prettyNumbers';
|
||||||
import PaymentText from './PaymentText';
|
import PaymentText from '../PaymentText';
|
||||||
import RobotAvatar from './Robots/RobotAvatar';
|
import RobotAvatar from '../Robots/RobotAvatar';
|
||||||
import hexToRgb from '../utils/hexToRgb';
|
import hexToRgb from '../../utils/hexToRgb';
|
||||||
import statusBadgeColor from '../utils/statusBadgeColor';
|
import statusBadgeColor from '../../utils/statusBadgeColor';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { Fullscreen, FullscreenExit, Refresh } from '@mui/icons-material';
|
import { Fullscreen, FullscreenExit, Refresh, WidthFull } from '@mui/icons-material';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
loading: boolean;
|
loading?: boolean;
|
||||||
refreshing: boolean;
|
refreshing?: boolean;
|
||||||
clickRefresh: () => void;
|
clickRefresh?: () => void;
|
||||||
orders: Order[];
|
orders: Order[];
|
||||||
type: number;
|
type: number;
|
||||||
currency: number;
|
currency: number;
|
||||||
maxWidth: number;
|
maxWidth: number;
|
||||||
maxHeight: number;
|
maxHeight: number;
|
||||||
fullWidth: number;
|
fullWidth?: number;
|
||||||
fullHeight: number;
|
fullHeight?: number;
|
||||||
defaultFullscreen: boolean;
|
defaultFullscreen: boolean;
|
||||||
|
showControls?: boolean;
|
||||||
|
showFooter?: boolean;
|
||||||
|
onCurrencyChange?: () => void;
|
||||||
|
onTypeChange?: () => void;
|
||||||
|
noResultsOverlay?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BookTable = ({
|
const BookTable = ({
|
||||||
loading,
|
loading = false,
|
||||||
refreshing,
|
refreshing = false,
|
||||||
clickRefresh,
|
clickRefresh,
|
||||||
orders,
|
orders,
|
||||||
type,
|
type,
|
||||||
@ -55,21 +62,27 @@ const BookTable = ({
|
|||||||
maxHeight,
|
maxHeight,
|
||||||
fullWidth,
|
fullWidth,
|
||||||
fullHeight,
|
fullHeight,
|
||||||
defaultFullscreen,
|
defaultFullscreen = false,
|
||||||
|
showControls = true,
|
||||||
|
showFooter = true,
|
||||||
|
onCurrencyChange,
|
||||||
|
onTypeChange,
|
||||||
|
noResultsOverlay,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [pageSize, setPageSize] = useState(0);
|
const [pageSize, setPageSize] = useState(0);
|
||||||
const [fullscreen, setFullscreen] = useState(defaultFullscreen);
|
const [fullscreen, setFullscreen] = useState(defaultFullscreen);
|
||||||
|
const [paymentMethods, setPaymentMethods] = useState<string[]>([]);
|
||||||
|
|
||||||
// all sizes in 'em'
|
// all sizes in 'em'
|
||||||
const fontSize = theme.typography.fontSize;
|
const fontSize = theme.typography.fontSize;
|
||||||
const verticalHeightFrame = 6.9075;
|
const verticalHeightFrame = 3.625 + (showControls ? 3.7 : 0) + (showFooter ? 2.35 : 0);
|
||||||
const verticalHeightRow = 3.25;
|
const verticalHeightRow = 3.25;
|
||||||
const defaultPageSize = Math.max(
|
const defaultPageSize = Math.max(
|
||||||
Math.floor(
|
Math.floor(
|
||||||
((fullscreen ? fullHeight * 0.875 : maxHeight) - verticalHeightFrame) / verticalHeightRow,
|
((fullscreen ? fullHeight * 0.9 : maxHeight) - verticalHeightFrame) / verticalHeightRow,
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
@ -98,7 +111,6 @@ const BookTable = ({
|
|||||||
|
|
||||||
const localeText = {
|
const localeText = {
|
||||||
MuiTablePagination: { labelRowsPerPage: t('Orders per page:') },
|
MuiTablePagination: { labelRowsPerPage: t('Orders per page:') },
|
||||||
noRowsLabel: t('No rows'),
|
|
||||||
noResultsOverlayLabel: t('No results found.'),
|
noResultsOverlayLabel: t('No results found.'),
|
||||||
errorOverlayDefaultLabel: t('An error occurred.'),
|
errorOverlayDefaultLabel: t('An error occurred.'),
|
||||||
toolbarColumns: t('Columns'),
|
toolbarColumns: t('Columns'),
|
||||||
@ -152,7 +164,7 @@ const BookTable = ({
|
|||||||
field: 'maker_nick',
|
field: 'maker_nick',
|
||||||
headerName: t('Robot'),
|
headerName: t('Robot'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<ListItemButton style={{ cursor: 'pointer', position: 'relative', left: '-1.3em' }}>
|
<ListItemButton style={{ cursor: 'pointer', position: 'relative', left: '-1.3em' }}>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
@ -179,7 +191,7 @@ const BookTable = ({
|
|||||||
field: 'maker_nick',
|
field: 'maker_nick',
|
||||||
headerName: t('Robot'),
|
headerName: t('Robot'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative', left: '-1.64em' }}>
|
<div style={{ position: 'relative', left: '-1.64em' }}>
|
||||||
<ListItemButton style={{ cursor: 'pointer' }}>
|
<ListItemButton style={{ cursor: 'pointer' }}>
|
||||||
@ -205,7 +217,7 @@ const BookTable = ({
|
|||||||
field: 'type',
|
field: 'type',
|
||||||
headerName: t('Is'),
|
headerName: t('Is'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => (params.row.type ? t('Seller') : t('Buyer')),
|
renderCell: (params: any) => (params.row.type ? t('Seller') : t('Buyer')),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -216,7 +228,7 @@ const BookTable = ({
|
|||||||
headerName: t('Amount'),
|
headerName: t('Amount'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div style={{ cursor: 'pointer' }}>
|
||||||
{amountToString(
|
{amountToString(
|
||||||
@ -237,7 +249,7 @@ const BookTable = ({
|
|||||||
field: 'currency',
|
field: 'currency',
|
||||||
headerName: t('Currency'),
|
headerName: t('Currency'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
const currencyCode = currencyDict[params.row.currency.toString()];
|
const currencyCode = currencyDict[params.row.currency.toString()];
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -262,7 +274,7 @@ const BookTable = ({
|
|||||||
field: 'payment_method',
|
field: 'payment_method',
|
||||||
headerName: t('Payment Method'),
|
headerName: t('Payment Method'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div style={{ cursor: 'pointer' }}>
|
||||||
<PaymentText
|
<PaymentText
|
||||||
@ -283,14 +295,13 @@ const BookTable = ({
|
|||||||
field: 'payment_icons',
|
field: 'payment_icons',
|
||||||
headerName: t('Pay'),
|
headerName: t('Pay'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
left: '-4px',
|
left: '-4px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
align: 'center',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PaymentText
|
<PaymentText
|
||||||
@ -311,7 +322,7 @@ const BookTable = ({
|
|||||||
headerName: t('Price'),
|
headerName: t('Price'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
const currencyCode = currencyDict[params.row.currency.toString()];
|
const currencyCode = currencyDict[params.row.currency.toString()];
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>{`${pn(params.row.price)} ${currencyCode}/BTC`}</div>
|
<div style={{ cursor: 'pointer' }}>{`${pn(params.row.price)} ${currencyCode}/BTC`}</div>
|
||||||
@ -332,7 +343,8 @@ const BookTable = ({
|
|||||||
headerName: t('Premium'),
|
headerName: t('Premium'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
|
const currencyCode = currencyDict[params.row.currency.toString()];
|
||||||
let fontColor = `rgb(0,0,0)`;
|
let fontColor = `rgb(0,0,0)`;
|
||||||
if (params.row.type === 0) {
|
if (params.row.type === 0) {
|
||||||
var premiumPoint = params.row.premium / buyOutstandingPremium;
|
var premiumPoint = params.row.premium / buyOutstandingPremium;
|
||||||
@ -353,11 +365,17 @@ const BookTable = ({
|
|||||||
}
|
}
|
||||||
const fontWeight = 400 + Math.round(premiumPoint * 5) * 100;
|
const fontWeight = 400 + Math.round(premiumPoint * 5) * 100;
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<Tooltip
|
||||||
<Typography variant='inherit' color={fontColor} sx={{ fontWeight }}>
|
placement='left'
|
||||||
{parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'}
|
enterTouchDelay={0}
|
||||||
</Typography>
|
title={pn(params.row.price) + ' ' + currencyCode + '/BTC'}
|
||||||
</div>
|
>
|
||||||
|
<div style={{ cursor: 'pointer' }}>
|
||||||
|
<Typography variant='inherit' color={fontColor} sx={{ fontWeight }}>
|
||||||
|
{parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -370,7 +388,7 @@ const BookTable = ({
|
|||||||
headerName: t('Timer'),
|
headerName: t('Timer'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
const hours = Math.round(params.row.escrow_duration / 3600);
|
const hours = Math.round(params.row.escrow_duration / 3600);
|
||||||
const minutes = Math.round((params.row.escrow_duration - hours * 3600) / 60);
|
const minutes = Math.round((params.row.escrow_duration - hours * 3600) / 60);
|
||||||
return <div style={{ cursor: 'pointer' }}>{hours > 0 ? `${hours}h` : `${minutes}m`}</div>;
|
return <div style={{ cursor: 'pointer' }}>{hours > 0 ? `${hours}h` : `${minutes}m`}</div>;
|
||||||
@ -385,9 +403,9 @@ const BookTable = ({
|
|||||||
headerName: t('Expiry'),
|
headerName: t('Expiry'),
|
||||||
type: 'string',
|
type: 'string',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
const expiresAt = new Date(params.row.expires_at);
|
const expiresAt: Date = new Date(params.row.expires_at);
|
||||||
const timeToExpiry = Math.abs(expiresAt - new Date());
|
const timeToExpiry: number = Math.abs(expiresAt - new Date());
|
||||||
const percent = Math.round((timeToExpiry / (24 * 60 * 60 * 1000)) * 100);
|
const percent = Math.round((timeToExpiry / (24 * 60 * 60 * 1000)) * 100);
|
||||||
const hours = Math.round(timeToExpiry / (3600 * 1000));
|
const hours = Math.round(timeToExpiry / (3600 * 1000));
|
||||||
const minutes = Math.round((timeToExpiry - hours * (3600 * 1000)) / 60000);
|
const minutes = Math.round((timeToExpiry - hours * (3600 * 1000)) / 60000);
|
||||||
@ -429,7 +447,7 @@ const BookTable = ({
|
|||||||
headerName: t('Sats now'),
|
headerName: t('Sats now'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div style={{ cursor: 'pointer' }}>
|
||||||
{params.row.satoshis_now > 1000000
|
{params.row.satoshis_now > 1000000
|
||||||
@ -447,7 +465,7 @@ const BookTable = ({
|
|||||||
field: 'id',
|
field: 'id',
|
||||||
headerName: 'Order ID',
|
headerName: 'Order ID',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div style={{ cursor: 'pointer' }}>
|
||||||
<Typography variant='caption' color='text.secondary'>
|
<Typography variant='caption' color='text.secondary'>
|
||||||
@ -610,14 +628,8 @@ const BookTable = ({
|
|||||||
|
|
||||||
const [columns, width] = filteredColumns(fullscreen ? fullWidth : maxWidth);
|
const [columns, width] = filteredColumns(fullscreen ? fullWidth : maxWidth);
|
||||||
|
|
||||||
const gridComponents = {
|
const Footer = function () {
|
||||||
LoadingOverlay: LinearProgress,
|
return (
|
||||||
NoResultsOverlay: () => (
|
|
||||||
<Stack height='100%' alignItems='center' justifyContent='center'>
|
|
||||||
{t('Filter has no results')}
|
|
||||||
</Stack>
|
|
||||||
),
|
|
||||||
Footer: () => (
|
|
||||||
<Grid container alignItems='center' direction='row' justifyContent='space-between'>
|
<Grid container alignItems='center' direction='row' justifyContent='space-between'>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Grid container alignItems='center' direction='row'>
|
<Grid container alignItems='center' direction='row'>
|
||||||
@ -638,7 +650,47 @@ const BookTable = ({
|
|||||||
<GridPagination />
|
<GridPagination />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
),
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GridComponentProps {
|
||||||
|
LoadingOverlay: JSX.Element;
|
||||||
|
NoResultsOverlay?: JSX.Element;
|
||||||
|
NoRowsOverlay?: JSX.Element;
|
||||||
|
Footer?: JSX.Element;
|
||||||
|
Toolbar?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Controls = function () {
|
||||||
|
return (
|
||||||
|
<BookControl
|
||||||
|
width={width}
|
||||||
|
type={type}
|
||||||
|
currency={currency}
|
||||||
|
onCurrencyChange={onCurrencyChange}
|
||||||
|
onTypeChange={onTypeChange}
|
||||||
|
paymentMethod={paymentMethods}
|
||||||
|
setPaymentMethods={setPaymentMethods}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridComponents = function () {
|
||||||
|
const components: GridComponentProps = {
|
||||||
|
LoadingOverlay: LinearProgress,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (noResultsOverlay != null) {
|
||||||
|
components.NoResultsOverlay = noResultsOverlay;
|
||||||
|
components.NoRowsOverlay = noResultsOverlay;
|
||||||
|
}
|
||||||
|
if (showFooter) {
|
||||||
|
components.Footer = Footer;
|
||||||
|
}
|
||||||
|
if (showControls) {
|
||||||
|
components.Toolbar = Controls;
|
||||||
|
}
|
||||||
|
return components;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!fullscreen) {
|
if (!fullscreen) {
|
||||||
@ -646,20 +698,26 @@ const BookTable = ({
|
|||||||
<Paper style={{ width: `${width}em`, height: `${height}em`, overflow: 'auto' }}>
|
<Paper style={{ width: `${width}em`, height: `${height}em`, overflow: 'auto' }}>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
localeText={localeText}
|
localeText={localeText}
|
||||||
rows={orders.filter(
|
rows={
|
||||||
(order) =>
|
showControls
|
||||||
(order.type == type || type == null) && (order.currency == currency || currency == 0),
|
? filterOrders({
|
||||||
)}
|
orders,
|
||||||
|
baseFilter: { currency, type },
|
||||||
|
paymentMethods,
|
||||||
|
})
|
||||||
|
: orders
|
||||||
|
}
|
||||||
loading={loading || refreshing}
|
loading={loading || refreshing}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
components={gridComponents}
|
hideFooter={!showFooter}
|
||||||
|
components={gridComponents()}
|
||||||
pageSize={loading ? 0 : pageSize}
|
pageSize={loading ? 0 : pageSize}
|
||||||
rowsPerPageOptions={[0, pageSize, defaultPageSize * 2, 50, 100]}
|
rowsPerPageOptions={width < 22 ? [] : [0, pageSize, defaultPageSize * 2, 50, 100]}
|
||||||
onPageSizeChange={(newPageSize) => {
|
onPageSizeChange={(newPageSize) => {
|
||||||
setPageSize(newPageSize);
|
setPageSize(newPageSize);
|
||||||
setUseDefaultPageSize(false);
|
setUseDefaultPageSize(false);
|
||||||
}}
|
}}
|
||||||
onRowClick={(params) => history.push('/order/' + params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
onRowClick={(params: any) => history.push('/order/' + params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
@ -676,14 +734,15 @@ const BookTable = ({
|
|||||||
)}
|
)}
|
||||||
loading={loading || refreshing}
|
loading={loading || refreshing}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
components={gridComponents}
|
hideFooter={!showFooter}
|
||||||
|
components={gridComponents()}
|
||||||
pageSize={loading ? 0 : pageSize}
|
pageSize={loading ? 0 : pageSize}
|
||||||
rowsPerPageOptions={[0, pageSize, defaultPageSize * 2, 50, 100]}
|
rowsPerPageOptions={[0, pageSize, defaultPageSize * 2, 50, 100]}
|
||||||
onPageSizeChange={(newPageSize) => {
|
onPageSizeChange={(newPageSize) => {
|
||||||
setPageSize(newPageSize);
|
setPageSize(newPageSize);
|
||||||
setUseDefaultPageSize(false);
|
setUseDefaultPageSize(false);
|
||||||
}}
|
}}
|
||||||
onRowClick={(params) => history.push('/order/' + params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
onRowClick={(params: any) => history.push('/order/' + params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Dialog>
|
</Dialog>
|
4
frontend/src/components/BookPage/index.ts
Normal file
4
frontend/src/components/BookPage/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import BookPage from './BookPage';
|
||||||
|
export default BookPage;
|
||||||
|
|
||||||
|
export { default as BookTable } from './BookTable';
|
@ -192,7 +192,7 @@ class BottomBar extends Component {
|
|||||||
secondaryTypographyProps: { fontSize: (fontSize * 12) / 14 },
|
secondaryTypographyProps: { fontSize: (fontSize * 12) / 14 },
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Paper elevation={6} style={{ height: 40 * fontSizeFactor, width: window.innerWidth }}>
|
<Paper elevation={6} style={{ height: '2.85em', width: '100%' }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={1.9}>
|
<Grid item xs={1.9}>
|
||||||
<div style={{ display: this.showProfileButton() ? '' : 'none' }}>
|
<div style={{ display: this.showProfileButton() ? '' : 'none' }}>
|
||||||
@ -485,7 +485,7 @@ class BottomBar extends Component {
|
|||||||
this.props.avatarLoaded
|
this.props.avatarLoaded
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Paper elevation={6} style={{ height: 40 }}>
|
<Paper elevation={6} style={{ height: '2.85em', width: '100%' }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={1.6}>
|
<Grid item xs={1.6}>
|
||||||
<div style={{ display: this.showProfileButton() ? '' : 'none' }}>
|
<div style={{ display: this.showProfileButton() ? '' : 'none' }}>
|
||||||
|
@ -21,8 +21,7 @@ import {
|
|||||||
import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material';
|
import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { Order } from '../../../models/Order.model';
|
import { Order, LimitList } from '../../../models';
|
||||||
import { LimitList } from '../../../models/Limit.model';
|
|
||||||
import RobotAvatar from '../../Robots/RobotAvatar';
|
import RobotAvatar from '../../Robots/RobotAvatar';
|
||||||
import { amountToString } from '../../../utils/prettyNumbers';
|
import { amountToString } from '../../../utils/prettyNumbers';
|
||||||
import currencyDict from '../../../../static/assets/currencies.json';
|
import currencyDict from '../../../../static/assets/currencies.json';
|
||||||
|
@ -91,7 +91,7 @@ const FlagWithProps = ({ code }: Props): JSX.Element => {
|
|||||||
if (code === 'XAU') flag = <GoldIcon {...defaultProps} />;
|
if (code === 'XAU') flag = <GoldIcon {...defaultProps} />;
|
||||||
if (code === 'BTC') flag = <SwapCallsIcon color='primary' />;
|
if (code === 'BTC') flag = <SwapCallsIcon color='primary' />;
|
||||||
|
|
||||||
return <div style={{ width: 28, height: 20 }}>{flag}</div>;
|
return <div style={{ width: 28, height: 20, maxHeight: 20 }}>{flag}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FlagWithProps;
|
export default FlagWithProps;
|
||||||
|
@ -7,6 +7,8 @@ import BookPage from './BookPage';
|
|||||||
import OrderPage from './OrderPage';
|
import OrderPage from './OrderPage';
|
||||||
import BottomBar from './BottomBar';
|
import BottomBar from './BottomBar';
|
||||||
|
|
||||||
|
import { apiClient } from '../services/api';
|
||||||
|
|
||||||
export default class HomePage extends Component {
|
export default class HomePage extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -20,7 +22,7 @@ export default class HomePage extends Component {
|
|||||||
type: null,
|
type: null,
|
||||||
currency: 0,
|
currency: 0,
|
||||||
bookCurrencyCode: 'ANY',
|
bookCurrencyCode: 'ANY',
|
||||||
bookOrders: new Array(),
|
orders: new Array(),
|
||||||
bookLoading: true,
|
bookLoading: true,
|
||||||
bookRefreshing: false,
|
bookRefreshing: false,
|
||||||
activeOrderId: null,
|
activeOrderId: null,
|
||||||
@ -29,14 +31,21 @@ export default class HomePage extends Component {
|
|||||||
referralCode: '',
|
referralCode: '',
|
||||||
lastDayPremium: 0,
|
lastDayPremium: 0,
|
||||||
limits: {},
|
limits: {},
|
||||||
|
loadingLimits: true,
|
||||||
|
maker: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
if (typeof window !== undefined) {
|
if (typeof window !== undefined) {
|
||||||
this.setState({ windowWidth: window.innerWidth, windowHeight: window.innerHeight });
|
this.setState({
|
||||||
|
windowWidth: window.innerWidth / this.props.theme.typography.fontSize,
|
||||||
|
windowHeight: window.innerHeight / this.props.theme.typography.fontSize,
|
||||||
|
});
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
}
|
}
|
||||||
|
this.fetchBook(true, false);
|
||||||
|
this.fetchLimits(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
@ -46,7 +55,10 @@ export default class HomePage extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onResize = () => {
|
onResize = () => {
|
||||||
this.setState({ windowWidth: window.innerWidth, windowHeight: window.innerHeight });
|
this.setState({
|
||||||
|
windowWidth: window.innerWidth / this.props.theme.typography.fontSize,
|
||||||
|
windowHeight: window.innerHeight / this.props.theme.typography.fontSize,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
setAppState = (newState) => {
|
setAppState = (newState) => {
|
||||||
@ -62,10 +74,29 @@ export default class HomePage extends Component {
|
|||||||
// Only for Android
|
// Only for Android
|
||||||
return window.location.pathname;
|
return window.location.pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchBook = (loading, refreshing) => {
|
||||||
|
this.setState({ bookLoading: loading, bookRefreshing: refreshing });
|
||||||
|
apiClient.get('/api/book/').then((data) =>
|
||||||
|
this.setState({
|
||||||
|
bookLoading: false,
|
||||||
|
bookRefreshing: false,
|
||||||
|
orders: data.not_found ? [] : data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchLimits = (loading) => {
|
||||||
|
this.setState({ loadingLimits: loading });
|
||||||
|
const limits = apiClient.get('/api/limits/').then((data) => {
|
||||||
|
this.setState({ limits: data, loadingLimits: false });
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
return limits;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const fontSize = this.props.theme.typography.fontSize;
|
const fontSize = this.props.theme.typography.fontSize;
|
||||||
const fontSizeFactor = fontSize / 14; // default fontSize is 14
|
const fontSizeFactor = fontSize / 14; // default fontSize is 14
|
||||||
@ -105,6 +136,7 @@ export default class HomePage extends Component {
|
|||||||
{...props}
|
{...props}
|
||||||
{...this.state}
|
{...this.state}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
|
fetchLimits={this.fetchLimits}
|
||||||
setAppState={this.setAppState}
|
setAppState={this.setAppState}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -116,6 +148,8 @@ export default class HomePage extends Component {
|
|||||||
{...props}
|
{...props}
|
||||||
{...this.state}
|
{...this.state}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
|
fetchBook={this.fetchBook}
|
||||||
|
fetchLimits={this.fetchLimits}
|
||||||
setAppState={this.setAppState}
|
setAppState={this.setAppState}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -135,7 +169,10 @@ export default class HomePage extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='bottomBar'
|
className='bottomBar'
|
||||||
style={{ height: `${40 * fontSizeFactor}px`, width: this.state.windowWidth }}
|
style={{
|
||||||
|
height: `${40 * fontSizeFactor}px`,
|
||||||
|
width: `${(this.state.windowWidth / 16) * 14}em`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<BottomBar
|
<BottomBar
|
||||||
redirectTo={this.redirectTo}
|
redirectTo={this.redirectTo}
|
||||||
|
File diff suppressed because it is too large
Load Diff
191
frontend/src/components/MakerPage/AmountRange.tsx
Normal file
191
frontend/src/components/MakerPage/AmountRange.tsx
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
SliderThumb,
|
||||||
|
Grid,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
Box,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import FlagWithProps from '../FlagWithProps';
|
||||||
|
import RangeSlider from './RangeSlider';
|
||||||
|
import currencyDict from '../../../static/assets/currencies.json';
|
||||||
|
import { pn } from '../../utils/prettyNumbers';
|
||||||
|
|
||||||
|
const RangeThumbComponent = function (props: object) {
|
||||||
|
const { children, ...other } = props;
|
||||||
|
return (
|
||||||
|
<SliderThumb {...other}>
|
||||||
|
{children}
|
||||||
|
<span className='range-bar' />
|
||||||
|
<span className='range-bar' />
|
||||||
|
<span className='range-bar' />
|
||||||
|
</SliderThumb>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AmountRangeProps {
|
||||||
|
minAmount: string;
|
||||||
|
maxAmount: string;
|
||||||
|
type: number;
|
||||||
|
currency: number;
|
||||||
|
handleRangeAmountChange: (e: any, activeThumb: any) => void;
|
||||||
|
handleMaxAmountChange: () => void;
|
||||||
|
handleMinAmountChange: () => void;
|
||||||
|
handleCurrencyChange: () => void;
|
||||||
|
maxAmountError: boolean;
|
||||||
|
minAmountError: boolean;
|
||||||
|
currencyCode: string;
|
||||||
|
amountLimits: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function AmountRange({
|
||||||
|
minAmount,
|
||||||
|
handleRangeAmountChange,
|
||||||
|
currency,
|
||||||
|
currencyCode,
|
||||||
|
handleCurrencyChange,
|
||||||
|
amountLimits,
|
||||||
|
maxAmount,
|
||||||
|
minAmountError,
|
||||||
|
maxAmountError,
|
||||||
|
handleMinAmountChange,
|
||||||
|
handleMaxAmountChange,
|
||||||
|
}: AmountRangeProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: '0.5em',
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
border: '1px solid',
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#ffffff' : '#2f2f2f',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid container direction='column' alignItems='center' spacing={0.5}>
|
||||||
|
<Grid item sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
width: `${t('From').length * 0.56 + 0.6}em`,
|
||||||
|
textAlign: 'left',
|
||||||
|
color: 'text.secondary',
|
||||||
|
}}
|
||||||
|
variant='caption'
|
||||||
|
>
|
||||||
|
{t('From')}
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
variant='standard'
|
||||||
|
type='number'
|
||||||
|
size='small'
|
||||||
|
value={minAmount}
|
||||||
|
onChange={handleMinAmountChange}
|
||||||
|
error={minAmountError}
|
||||||
|
sx={{
|
||||||
|
width: `${minAmount.toString().length * 0.56}em`,
|
||||||
|
minWidth: '0.56em',
|
||||||
|
maxWidth: '2.8em',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
width: `${t('to').length * 0.56 + 0.6}em`,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'text.secondary',
|
||||||
|
}}
|
||||||
|
variant='caption'
|
||||||
|
>
|
||||||
|
{t('to')}
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
variant='standard'
|
||||||
|
size='small'
|
||||||
|
type='number'
|
||||||
|
value={maxAmount}
|
||||||
|
onChange={handleMaxAmountChange}
|
||||||
|
error={maxAmountError}
|
||||||
|
sx={{
|
||||||
|
width: `${maxAmount.toString().length * 0.56}em`,
|
||||||
|
minWidth: '0.56em',
|
||||||
|
maxWidth: '3.36em',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ width: '0.5em' }} />
|
||||||
|
<Select
|
||||||
|
sx={{ width: '3.8em' }}
|
||||||
|
variant='standard'
|
||||||
|
size='small'
|
||||||
|
required={true}
|
||||||
|
inputProps={{
|
||||||
|
style: { textAlign: 'center' },
|
||||||
|
}}
|
||||||
|
value={currency == 0 ? 1 : currency}
|
||||||
|
renderValue={() => currencyCode}
|
||||||
|
onChange={(e) => handleCurrencyChange(e.target.value)}
|
||||||
|
>
|
||||||
|
{Object.entries(currencyDict).map(([key, value]) => (
|
||||||
|
<MenuItem key={key} value={parseInt(key)}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<FlagWithProps code={value} />
|
||||||
|
{' ' + value}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
sx={{
|
||||||
|
width: `calc(100% - ${Math.log10(amountLimits[1] * 0.65) + 2}em)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RangeSlider
|
||||||
|
disableSwap={true}
|
||||||
|
value={[Number(minAmount), Number(maxAmount)]}
|
||||||
|
step={(amountLimits[1] - amountLimits[0]) / 5000}
|
||||||
|
valueLabelDisplay='auto'
|
||||||
|
components={{ Thumb: RangeThumbComponent }}
|
||||||
|
componentsProps={{
|
||||||
|
thumb: { style: { backgroundColor: theme.palette.background.paper } },
|
||||||
|
}}
|
||||||
|
valueLabelFormat={(x) =>
|
||||||
|
pn(parseFloat(Number(x).toPrecision(x < 100 ? 2 : 3))) + ' ' + currencyCode
|
||||||
|
}
|
||||||
|
marks={[
|
||||||
|
{
|
||||||
|
value: amountLimits[0],
|
||||||
|
label: `${pn(
|
||||||
|
parseFloat(Number(amountLimits[0]).toPrecision(3)),
|
||||||
|
)} ${currencyCode}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: amountLimits[1],
|
||||||
|
label: `${pn(
|
||||||
|
parseFloat(Number(amountLimits[1]).toPrecision(3)),
|
||||||
|
)} ${currencyCode}`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
min={amountLimits[0]}
|
||||||
|
max={amountLimits[1]}
|
||||||
|
onChange={handleRangeAmountChange}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AmountRange;
|
@ -3,57 +3,60 @@ import PropTypes from 'prop-types';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
|
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { Button, Tooltip } from '@mui/material';
|
import { Button, Fade, Tooltip, Typography, Grow } from '@mui/material';
|
||||||
import { paymentMethods, swapDestinations } from './payment-methods/Methods';
|
import { paymentMethods, swapDestinations } from '../payment-methods/Methods';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import DashboardCustomizeIcon from '@mui/icons-material/DashboardCustomize';
|
import DashboardCustomizeIcon from '@mui/icons-material/DashboardCustomize';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import PaymentIcon from '../payment-methods/Icons';
|
||||||
import PaymentIcon from './payment-methods/Icons';
|
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
const Root = styled('div')(
|
const Root = styled('div')(
|
||||||
({ theme }) => `
|
({ theme }) => `
|
||||||
color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,.85)'};
|
color: ${theme.palette.text.primary};
|
||||||
font-size: 14px;
|
font-size: ${theme.typography.fontSize};
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const Label = styled('label')(
|
const Label = styled('label')(
|
||||||
({ theme, error }) => `
|
({ theme, error, sx }) => `
|
||||||
color: ${
|
color: ${
|
||||||
theme.palette.mode === 'dark' ? (error ? '#f44336' : '#cfcfcf') : error ? '#dd0000' : '#717171'
|
theme.palette.mode === 'dark' ? (error ? '#f44336' : '#cfcfcf') : error ? '#dd0000' : '#717171'
|
||||||
};
|
};
|
||||||
align: center;
|
pointer-events: none;
|
||||||
padding: 0 0 4px;
|
position: relative;
|
||||||
line-height: 1.5; f44336
|
left: 1em;
|
||||||
display: block;
|
top: ${sx.top};
|
||||||
font-size: 13px;
|
maxHeight: 0em;
|
||||||
|
height: 0em;
|
||||||
|
white-space: no-wrap;
|
||||||
|
font-size: 1em;
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const InputWrapper = styled('div')(
|
const InputWrapper = styled('div')(
|
||||||
({ theme, error }) => `
|
({ theme, error, sx }) => `
|
||||||
width: 244px;
|
min-height: ${sx.minHeight};
|
||||||
min-height: 44px;
|
max-height: ${sx.maxHeight};
|
||||||
max-height: 124px;
|
|
||||||
border: 1px solid ${
|
border: 1px solid ${
|
||||||
theme.palette.mode === 'dark' ? (error ? '#f44336' : '#434343') : error ? '#dd0000' : '#c4c4c4'
|
theme.palette.mode === 'dark' ? (error ? '#f44336' : '#434343') : error ? '#dd0000' : '#c4c4c4'
|
||||||
};
|
};
|
||||||
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
border-color: ${sx.borderColor ? `border-color ${sx.borderColor}` : ''}
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
overflow-y:auto;
|
overflow-y:auto;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: ${
|
border-color: ${
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? error
|
? error
|
||||||
? '#f44336'
|
? '#f44336'
|
||||||
: '#ffffff'
|
: sx.hoverBorderColor
|
||||||
: error
|
: error
|
||||||
? '#dd0000'
|
? '#dd0000'
|
||||||
: '#2f2f2f'
|
: '#2f2f2f'
|
||||||
@ -75,17 +78,17 @@ const InputWrapper = styled('div')(
|
|||||||
& input {
|
& input {
|
||||||
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
||||||
color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,.85)'};
|
color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,.85)'};
|
||||||
height: 30px;
|
height: 2.15em;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
width: 0;
|
width: 0;
|
||||||
min-width: 30px;
|
min-width: 2.15em;
|
||||||
font-size: 15px;
|
font-size: ${theme.typography.fontSize * 1.0714};
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border: 0;
|
border: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
max-height: 124px;
|
max-height: 8.6em;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
@ -110,12 +113,12 @@ Tag.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const StyledTag = styled(Tag)(
|
const StyledTag = styled(Tag)(
|
||||||
({ theme }) => `
|
({ theme, sx }) => `
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 34px;
|
height: ${sx.height};
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
line-height: 22px;
|
line-height: 1.5em;
|
||||||
background-color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : '#fafafa'};
|
background-color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : '#fafafa'};
|
||||||
border: 1px solid ${theme.palette.mode === 'dark' ? '#303030' : '#e8e8e8'};
|
border: 1px solid ${theme.palette.mode === 'dark' ? '#303030' : '#e8e8e8'};
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@ -133,11 +136,11 @@ const StyledTag = styled(Tag)(
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-size: 15px;
|
font-size: 0.928em;
|
||||||
}
|
}
|
||||||
|
|
||||||
& svg {
|
& svg {
|
||||||
font-size: 15px;
|
font-size: 0.857em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
@ -152,27 +155,27 @@ const ListHeader = styled('span')(
|
|||||||
max-height: 10px;
|
max-height: 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#ffffff'};
|
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#ffffff'};
|
||||||
font-size: 12px;
|
font-size: 0.875em;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const Listbox = styled('ul')(
|
const Listbox = styled('ul')(
|
||||||
({ theme }) => `
|
({ theme, sx }) => `
|
||||||
width: 244px;
|
width: ${sx ? sx.width : '15.6em'};
|
||||||
margin: 2px 0 0;
|
margin: 2px 0 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 250px;
|
max-height: 17em;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|
||||||
& li {
|
& li {
|
||||||
padding: 5px 12px;
|
padding: 0em 0em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
& span {
|
& span {
|
||||||
@ -219,26 +222,19 @@ export default function AutocompletePayments(props) {
|
|||||||
focused = 'true',
|
focused = 'true',
|
||||||
setAnchorEl,
|
setAnchorEl,
|
||||||
} = useAutocomplete({
|
} = useAutocomplete({
|
||||||
sx: { width: '200px', align: 'left' },
|
fullWidth: true,
|
||||||
id: 'payment-methods',
|
id: 'payment-methods',
|
||||||
multiple: true,
|
multiple: true,
|
||||||
|
value: props.value,
|
||||||
options: props.optionsType == 'fiat' ? paymentMethods : swapDestinations,
|
options: props.optionsType == 'fiat' ? paymentMethods : swapDestinations,
|
||||||
getOptionLabel: (option) => option.name,
|
getOptionLabel: (option) => option.name,
|
||||||
onInputChange: (e) => setVal(e ? (e.target.value ? e.target.value : '') : ''),
|
onInputChange: (e) => setVal(e ? (e.target.value ? e.target.value : '') : ''),
|
||||||
onChange: (event, value) => props.onAutocompleteChange(optionsToString(value)),
|
onChange: (event, value) => props.onAutocompleteChange(value),
|
||||||
onClose: () => setVal(() => ''),
|
onClose: () => setVal(() => ''),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [val, setVal] = useState();
|
const [val, setVal] = useState('');
|
||||||
|
const fewerOptions = groupedOptions.length > 8 ? groupedOptions.slice(0, 8) : groupedOptions;
|
||||||
function optionsToString(newValue) {
|
|
||||||
let str = '';
|
|
||||||
const arrayLength = newValue.length;
|
|
||||||
for (let i = 0; i < arrayLength; i++) {
|
|
||||||
str += newValue[i].name + ' ';
|
|
||||||
}
|
|
||||||
return str.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleAddNew(inputProps) {
|
function handleAddNew(inputProps) {
|
||||||
paymentMethods.push({ name: inputProps.value, icon: 'custom' });
|
paymentMethods.push({ name: inputProps.value, icon: 'custom' });
|
||||||
@ -246,53 +242,75 @@ export default function AutocompletePayments(props) {
|
|||||||
setVal(() => '');
|
setVal(() => '');
|
||||||
|
|
||||||
if (a || a == null) {
|
if (a || a == null) {
|
||||||
props.onAutocompleteChange(optionsToString(value));
|
props.onAutocompleteChange(value);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<div style={{ height: '5px' }}></div>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
placement='top'
|
placement='top'
|
||||||
enterTouchDelay={300}
|
enterTouchDelay={props.tooltipTitle == '' ? 99999 : 300}
|
||||||
enterDelay={700}
|
enterDelay={props.tooltipTitle == '' ? 99999 : 700}
|
||||||
enterNextDelay={2000}
|
enterNextDelay={2000}
|
||||||
title={props.tooltipTitle}
|
title={props.tooltipTitle}
|
||||||
>
|
>
|
||||||
<div {...getRootProps()}>
|
<div {...getRootProps()}>
|
||||||
<Label {...getInputLabelProps()} error={props.error ? 'error' : null}>
|
<Fade
|
||||||
{' '}
|
appear={false}
|
||||||
{props.label}
|
in={fewerOptions.length == 0 && value.length == 0 && val.length == 0}
|
||||||
</Label>
|
>
|
||||||
|
<div style={{ height: 0, display: 'flex', alignItems: 'flex-start' }}>
|
||||||
|
<Label
|
||||||
|
{...getInputLabelProps()}
|
||||||
|
sx={{ top: '0.72em', ...(props.labelProps ? props.labelProps.sx : {}) }}
|
||||||
|
error={props.error ? 'error' : null}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</Fade>
|
||||||
<InputWrapper
|
<InputWrapper
|
||||||
ref={setAnchorEl}
|
ref={setAnchorEl}
|
||||||
error={props.error ? 'error' : null}
|
error={props.error ? 'error' : null}
|
||||||
className={focused ? 'focused' : ''}
|
className={focused ? 'focused' : ''}
|
||||||
|
sx={{
|
||||||
|
minHeight: '2.9em',
|
||||||
|
maxHeight: '8.6em',
|
||||||
|
hoverBorderColor: '#ffffff',
|
||||||
|
...props.sx,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{value.map((option, index) => (
|
{value.map((option, index) => (
|
||||||
<StyledTag label={t(option.name)} icon={option.icon} {...getTagProps({ index })} />
|
<StyledTag
|
||||||
|
label={t(option.name)}
|
||||||
|
icon={option.icon}
|
||||||
|
sx={{ height: '2.1em', ...(props.tagProps ? props.tagProps.sx : {}) }}
|
||||||
|
{...getTagProps({ index })}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<input {...getInputProps()} value={val || ''} />
|
{value.length > 0 && props.isFilter ? null : <input {...getInputProps()} value={val} />}
|
||||||
</InputWrapper>
|
</InputWrapper>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{groupedOptions.length > 0 ? (
|
<Grow in={fewerOptions.length > 0}>
|
||||||
<Listbox {...getListboxProps()}>
|
<Listbox sx={props.listBoxProps ? props.listBoxProps.sx : undefined} {...getListboxProps()}>
|
||||||
<div
|
{!props.isFilter ? (
|
||||||
style={{
|
<div
|
||||||
position: 'fixed',
|
style={{
|
||||||
minHeight: '20px',
|
position: 'fixed',
|
||||||
marginLeft: 120 - props.listHeaderText.length * 3,
|
minHeight: '1.428em',
|
||||||
marginTop: '-13px',
|
marginLeft: `${3 - props.listHeaderText.length * 0.05}em`,
|
||||||
}}
|
marginTop: '-0.928em',
|
||||||
>
|
}}
|
||||||
<ListHeader>
|
>
|
||||||
<i>{props.listHeaderText + ' '} </i>{' '}
|
<ListHeader>
|
||||||
</ListHeader>
|
<i>{props.listHeaderText + ' '} </i>{' '}
|
||||||
</div>
|
</ListHeader>
|
||||||
{groupedOptions.map((option, index) => (
|
</div>
|
||||||
|
) : null}
|
||||||
|
{fewerOptions.map((option, index) => (
|
||||||
<li key={option.name} {...getOptionProps({ option, index })}>
|
<li key={option.name} {...getOptionProps({ option, index })}>
|
||||||
<Button
|
<Button
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
@ -301,34 +319,38 @@ export default function AutocompletePayments(props) {
|
|||||||
sx={{ textTransform: 'none' }}
|
sx={{ textTransform: 'none' }}
|
||||||
style={{ justifyContent: 'flex-start' }}
|
style={{ justifyContent: 'flex-start' }}
|
||||||
>
|
>
|
||||||
<div style={{ position: 'relative', right: '4px', top: '4px' }}>
|
<div style={{ padding: '0.286em', position: 'relative', top: '0.35em' }}>
|
||||||
<AddIcon style={{ color: '#1976d2' }} sx={{ width: 18, height: 18 }} />
|
<PaymentIcon width={22} height={22} icon={option.icon} />
|
||||||
</div>
|
</div>
|
||||||
{t(option.name)}
|
<Typography variant='inherit' align='left'>
|
||||||
|
{t(option.name)}
|
||||||
|
</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
<div style={{ position: 'relative', top: '5px' }}>
|
<div style={{ position: 'relative', top: '0.357em' }}>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{val != null ? (
|
{val != null || !props.isFilter ? (
|
||||||
val.length > 2 ? (
|
val.length > 2 ? (
|
||||||
<Button size='small' fullWidth={true} onClick={() => handleAddNew(getInputProps())}>
|
<Button size='small' fullWidth={true} onClick={() => handleAddNew(getInputProps())}>
|
||||||
<DashboardCustomizeIcon sx={{ width: 18, height: 18 }} />
|
<DashboardCustomizeIcon sx={{ width: '1em', height: '1em' }} />
|
||||||
{props.addNewButtonText}
|
{props.addNewButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
) : null
|
) : null
|
||||||
) : null}
|
) : null}
|
||||||
</Listbox>
|
</Listbox>
|
||||||
) : // Here goes what happens if there is no groupedOptions
|
</Grow>
|
||||||
getInputProps().value.length > 0 ? (
|
|
||||||
|
{/* Here goes what happens if there is no fewerOptions */}
|
||||||
|
<Grow in={getInputProps().value.length > 0 && !props.isFilter && fewerOptions.length === 0}>
|
||||||
<Listbox {...getListboxProps()}>
|
<Listbox {...getListboxProps()}>
|
||||||
<Button fullWidth={true} onClick={() => handleAddNew(getInputProps())}>
|
<Button fullWidth={true} onClick={() => handleAddNew(getInputProps())}>
|
||||||
<DashboardCustomizeIcon sx={{ width: 20, height: 20 }} />
|
<DashboardCustomizeIcon sx={{ width: '1.28em', height: '1.28em' }} />
|
||||||
{props.addNewButtonText}
|
{props.addNewButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
) : null}
|
</Grow>
|
||||||
</Root>
|
</Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
970
frontend/src/components/MakerPage/MakerForm.tsx
Normal file
970
frontend/src/components/MakerPage/MakerForm.tsx
Normal file
@ -0,0 +1,970 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
InputAdornment,
|
||||||
|
LinearProgress,
|
||||||
|
ButtonGroup,
|
||||||
|
Slider,
|
||||||
|
Switch,
|
||||||
|
Tooltip,
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Select,
|
||||||
|
FormHelperText,
|
||||||
|
MenuItem,
|
||||||
|
FormControl,
|
||||||
|
Radio,
|
||||||
|
FormControlLabel,
|
||||||
|
RadioGroup,
|
||||||
|
Box,
|
||||||
|
useTheme,
|
||||||
|
Collapse,
|
||||||
|
IconButton,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import { LimitList, Maker, defaultMaker } from '../../models';
|
||||||
|
|
||||||
|
import { LocalizationProvider, TimePicker } from '@mui/x-date-pickers';
|
||||||
|
import DateFnsUtils from '@date-io/date-fns';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { StoreTokenDialog, NoRobotDialog } from '../Dialogs';
|
||||||
|
import { apiClient } from '../../services/api';
|
||||||
|
import { systemClient } from '../../services/System';
|
||||||
|
|
||||||
|
import FlagWithProps from '../FlagWithProps';
|
||||||
|
import AutocompletePayments from './AutocompletePayments';
|
||||||
|
import AmountRange from './AmountRange';
|
||||||
|
import currencyDict from '../../../static/assets/currencies.json';
|
||||||
|
import { pn } from '../../utils/prettyNumbers';
|
||||||
|
|
||||||
|
import { SelfImprovement, Lock, HourglassTop, DeleteSweep, Edit } from '@mui/icons-material';
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
|
||||||
|
interface MakerFormProps {
|
||||||
|
limits: LimitList;
|
||||||
|
fetchLimits: (loading) => void;
|
||||||
|
loadingLimits: boolean;
|
||||||
|
pricingMethods: boolean;
|
||||||
|
maker: Maker;
|
||||||
|
type: number;
|
||||||
|
currency: number;
|
||||||
|
setAppState: (state: object) => void;
|
||||||
|
setMaker: (state: Maker) => void;
|
||||||
|
disableRequest?: boolean;
|
||||||
|
collapseAll?: boolean;
|
||||||
|
onSubmit?: () => void;
|
||||||
|
onReset?: () => void;
|
||||||
|
submitButtonLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MakerForm = ({
|
||||||
|
limits,
|
||||||
|
fetchLimits,
|
||||||
|
loadingLimits,
|
||||||
|
pricingMethods,
|
||||||
|
currency,
|
||||||
|
type,
|
||||||
|
setAppState,
|
||||||
|
maker,
|
||||||
|
setMaker,
|
||||||
|
disableRequest = false,
|
||||||
|
collapseAll = false,
|
||||||
|
onSubmit = () => {},
|
||||||
|
onReset = () => {},
|
||||||
|
submitButtonLabel = 'Create Order',
|
||||||
|
}: MakerFormProps): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
const history = useHistory();
|
||||||
|
const [badRequest, setBadRequest] = useState<string | null>(null);
|
||||||
|
const [advancedOptions, setAdvancedOptions] = useState<boolean>(false);
|
||||||
|
const [amountLimits, setAmountLimits] = useState<number[]>([1, 1000]);
|
||||||
|
const [satoshisLimits, setSatoshisLimits] = useState<number[]>([20000, 4000000]);
|
||||||
|
const [currentPrice, setCurrentPrice] = useState<number | string>('...');
|
||||||
|
const [currencyCode, setCurrencyCode] = useState<string>('USD');
|
||||||
|
|
||||||
|
const [openDialogs, setOpenDialogs] = useState<boolean>(false);
|
||||||
|
const [submittingRequest, setSubmittingRequest] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const maxRangeAmountMultiple = 7.8;
|
||||||
|
const minRangeAmountMultiple = 1.6;
|
||||||
|
const amountSafeThresholds = [1.03, 0.98];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrencyCode(currencyDict[currency == 0 ? 1 : currency]);
|
||||||
|
if (Object.keys(limits).length === 0) {
|
||||||
|
setAppState({ loadingLimits: true });
|
||||||
|
fetchLimits(true).then((data) => {
|
||||||
|
updateAmountLimits(data, currency, maker.premium);
|
||||||
|
updateCurrentPrice(data, currency, maker.premium);
|
||||||
|
updateSatoshisLimits(data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updateAmountLimits(limits, currency, maker.premium);
|
||||||
|
updateCurrentPrice(limits, currency, maker.premium);
|
||||||
|
updateSatoshisLimits(limits);
|
||||||
|
|
||||||
|
fetchLimits(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateAmountLimits = function (limits: LimitList, currency: number, premium: number) {
|
||||||
|
const index = currency === 0 ? 1 : currency;
|
||||||
|
let minAmountLimit: number = limits[index].min_amount * (1 + premium / 100);
|
||||||
|
let maxAmountLimit: number = limits[index].max_amount * (1 + premium / 100);
|
||||||
|
|
||||||
|
// apply thresholds to ensure good request
|
||||||
|
minAmountLimit = minAmountLimit * amountSafeThresholds[0];
|
||||||
|
maxAmountLimit = maxAmountLimit * amountSafeThresholds[1];
|
||||||
|
setAmountLimits([minAmountLimit, maxAmountLimit]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSatoshisLimits = function (limits: LimitList) {
|
||||||
|
const minAmount: number = limits[1000].min_amount * 100000000;
|
||||||
|
const maxAmount: number = limits[1000].max_amount * 100000000;
|
||||||
|
setSatoshisLimits([minAmount, maxAmount]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCurrentPrice = function (limits: LimitList, currency: number, premium: number) {
|
||||||
|
const index = currency === 0 ? 1 : currency;
|
||||||
|
let price = '...';
|
||||||
|
if (maker.is_explicit && maker.amount > 0 && maker.satoshis > 0) {
|
||||||
|
price = maker.amount / (maker.satoshis / 100000000);
|
||||||
|
} else if (!maker.is_explicit) {
|
||||||
|
price = limits[index].price * (1 + premium / 100);
|
||||||
|
}
|
||||||
|
setCurrentPrice(parseFloat(Number(price).toPrecision(5)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCurrencyChange = function (newCurrency: number) {
|
||||||
|
const currencyCode: string = currencyDict[newCurrency];
|
||||||
|
setCurrencyCode(currencyCode);
|
||||||
|
setAppState({
|
||||||
|
currency: newCurrency,
|
||||||
|
bookCurrencyCode: currencyCode,
|
||||||
|
});
|
||||||
|
updateAmountLimits(limits, newCurrency, maker.premium);
|
||||||
|
updateCurrentPrice(limits, newCurrency, maker.premium);
|
||||||
|
if (advancedOptions) {
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
minAmount: parseFloat(Number(limits[newCurrency].max_amount * 0.25).toPrecision(2)),
|
||||||
|
maxAmount: parseFloat(Number(limits[newCurrency].max_amount * 0.75).toPrecision(2)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaymentMethodChange = function (paymentArray: string[]) {
|
||||||
|
let str = '';
|
||||||
|
const arrayLength = paymentArray.length;
|
||||||
|
for (let i = 0; i < arrayLength; i++) {
|
||||||
|
str += paymentArray[i].name + ' ';
|
||||||
|
}
|
||||||
|
const paymentMethodText = str.slice(0, -1);
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
paymentMethods: paymentArray,
|
||||||
|
paymentMethodsText: paymentMethodText,
|
||||||
|
badPaymentMethod: paymentMethodText.length > 50,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMinAmountChange = function (e) {
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
minAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMaxAmountChange = function (e) {
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
maxAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePremiumChange = function (e: object) {
|
||||||
|
const max = 999;
|
||||||
|
const min = -100;
|
||||||
|
const newPremium = Math.floor(e.target.value * Math.pow(10, 2)) / Math.pow(10, 2);
|
||||||
|
let premium: number = newPremium;
|
||||||
|
let badPremiumText: string = '';
|
||||||
|
if (newPremium > 999) {
|
||||||
|
badPremiumText = t('Must be less than {{max}}%', { max });
|
||||||
|
premium = 999;
|
||||||
|
} else if (newPremium <= -100) {
|
||||||
|
badPremiumText = t('Must be more than {{min}}%', { min });
|
||||||
|
premium = -99.99;
|
||||||
|
}
|
||||||
|
updateCurrentPrice(limits, currency, premium);
|
||||||
|
updateAmountLimits(limits, currency, premium);
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
premium,
|
||||||
|
badPremiumText,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSatoshisChange = function (e: object) {
|
||||||
|
const newSatoshis = e.target.value;
|
||||||
|
let badSatoshisText: string = '';
|
||||||
|
let satoshis: string = newSatoshis;
|
||||||
|
if (newSatoshis > satoshisLimits[1]) {
|
||||||
|
badSatoshisText = t('Must be less than {{maxSats}', { maxSats: pn(satoshisLimits[1]) });
|
||||||
|
satoshis = satoshisLimits[1];
|
||||||
|
}
|
||||||
|
if (newSatoshis < satoshisLimits[0]) {
|
||||||
|
badSatoshisText = t('Must be more than {{minSats}}', { minSats: pn(satoshisLimits[0]) });
|
||||||
|
satoshis = satoshisLimits[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
satoshis,
|
||||||
|
badSatoshisText,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickRelative = function () {
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
isExplicit: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickExplicit = function () {
|
||||||
|
if (!advancedOptions) {
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
isExplicit: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateOrder = function () {
|
||||||
|
if (!disableRequest) {
|
||||||
|
setSubmittingRequest(true);
|
||||||
|
const body = {
|
||||||
|
type: type == 0 ? 1 : 0,
|
||||||
|
currency: currency == 0 ? 1 : currency,
|
||||||
|
amount: advancedOptions ? null : maker.amount,
|
||||||
|
has_range: advancedOptions,
|
||||||
|
min_amount: advancedOptions ? maker.minAmount : null,
|
||||||
|
max_amount: advancedOptions ? maker.maxAmount : null,
|
||||||
|
payment_method:
|
||||||
|
maker.paymentMethodsText === '' ? 'not specified' : maker.paymentMethodsText,
|
||||||
|
is_explicit: maker.isExplicit,
|
||||||
|
premium: maker.isExplicit ? null : maker.premium == '' ? 0 : maker.premium,
|
||||||
|
satoshis: maker.isExplicit ? maker.satoshis : null,
|
||||||
|
public_duration: maker.publicDuration,
|
||||||
|
escrow_duration: maker.escrowDuration,
|
||||||
|
bond_size: maker.bondSize,
|
||||||
|
};
|
||||||
|
apiClient.post('/api/make/', body).then((data: object) => {
|
||||||
|
setBadRequest(data.bad_request);
|
||||||
|
data.id ? history.push('/order/' + data.id) : '';
|
||||||
|
setSubmittingRequest(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setOpenDialogs(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePublicDuration = function (date: Date) {
|
||||||
|
const d = new Date(date);
|
||||||
|
const hours: number = d.getHours();
|
||||||
|
const minutes: number = d.getMinutes();
|
||||||
|
|
||||||
|
const total_secs: number = hours * 60 * 60 + minutes * 60;
|
||||||
|
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
publicExpiryTime: date,
|
||||||
|
publicDuration: total_secs,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeEscrowDuration = function (date: Date) {
|
||||||
|
const d = new Date(date);
|
||||||
|
const hours: number = d.getHours();
|
||||||
|
const minutes: number = d.getMinutes();
|
||||||
|
|
||||||
|
const total_secs: number = hours * 60 * 60 + minutes * 60;
|
||||||
|
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
escrowExpiryTime: date,
|
||||||
|
escrowDuration: total_secs,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickAdvanced = function () {
|
||||||
|
if (advancedOptions) {
|
||||||
|
handleClickRelative();
|
||||||
|
} else {
|
||||||
|
resetRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
setAdvancedOptions(!advancedOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const minAmountError = function () {
|
||||||
|
return (
|
||||||
|
maker.minAmount < amountLimits[0] * 0.99 ||
|
||||||
|
maker.maxAmount < maker.minAmount ||
|
||||||
|
maker.minAmount < maker.maxAmount / (maxRangeAmountMultiple + 0.15) ||
|
||||||
|
maker.minAmount * (minRangeAmountMultiple - 0.1) > maker.maxAmount
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const maxAmountError = function () {
|
||||||
|
return (
|
||||||
|
maker.maxAmount > amountLimits[1] * 1.01 ||
|
||||||
|
maker.maxAmount < maker.minAmount ||
|
||||||
|
maker.minAmount < maker.maxAmount / (maxRangeAmountMultiple + 0.15) ||
|
||||||
|
maker.minAmount * (minRangeAmountMultiple - 0.1) > maker.maxAmount
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetRange = function () {
|
||||||
|
const index = currency === 0 ? 1 : currency;
|
||||||
|
const minAmount = maker.amount
|
||||||
|
? parseFloat((maker.amount / 2).toPrecision(2))
|
||||||
|
: parseFloat(Number(limits[index].max_amount * 0.25).toPrecision(2));
|
||||||
|
const maxAmount = maker.amount
|
||||||
|
? parseFloat(maker.amount)
|
||||||
|
: parseFloat(Number(limits[index].max_amount * 0.75).toPrecision(2));
|
||||||
|
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
minAmount,
|
||||||
|
maxAmount,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRangeAmountChange = function (e: any, newValue, activeThumb: number) {
|
||||||
|
let minAmount = e.target.value[0];
|
||||||
|
let maxAmount = e.target.value[1];
|
||||||
|
|
||||||
|
minAmount = Math.min(
|
||||||
|
(amountLimits[1] * amountSafeThresholds[1]) / minRangeAmountMultiple,
|
||||||
|
minAmount,
|
||||||
|
);
|
||||||
|
maxAmount = Math.max(
|
||||||
|
minRangeAmountMultiple * amountLimits[0] * amountSafeThresholds[0],
|
||||||
|
maxAmount,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (minAmount > maxAmount / minRangeAmountMultiple) {
|
||||||
|
if (activeThumb === 0) {
|
||||||
|
maxAmount = minRangeAmountMultiple * minAmount;
|
||||||
|
} else {
|
||||||
|
minAmount = maxAmount / minRangeAmountMultiple;
|
||||||
|
}
|
||||||
|
} else if (minAmount < maxAmount / maxRangeAmountMultiple) {
|
||||||
|
if (activeThumb === 0) {
|
||||||
|
maxAmount = maxRangeAmountMultiple * minAmount;
|
||||||
|
} else {
|
||||||
|
minAmount = maxAmount / maxRangeAmountMultiple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMaker({
|
||||||
|
...maker,
|
||||||
|
minAmount: parseFloat(Number(minAmount).toPrecision(minAmount < 100 ? 2 : 3)),
|
||||||
|
maxAmount: parseFloat(Number(maxAmount).toPrecision(maxAmount < 100 ? 2 : 3)),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableSubmit = function () {
|
||||||
|
return (
|
||||||
|
type == null ||
|
||||||
|
(maker.amount != '' &&
|
||||||
|
!advancedOptions &&
|
||||||
|
(maker.amount < amountLimits[0] || maker.amount > amountLimits[1])) ||
|
||||||
|
(maker.amount == null && (!advancedOptions || loadingLimits)) ||
|
||||||
|
(advancedOptions && (minAmountError() || maxAmountError())) ||
|
||||||
|
(maker.amount <= 0 && !advancedOptions) ||
|
||||||
|
(maker.isExplicit && (maker.badSatoshisText != '' || maker.satoshis == '')) ||
|
||||||
|
(!maker.isExplicit && maker.badPremiumText != '')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearMaker = function () {
|
||||||
|
setAppState({ type: null });
|
||||||
|
setMaker(defaultMaker);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SummaryText = function () {
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
component='h2'
|
||||||
|
variant='subtitle2'
|
||||||
|
align='center'
|
||||||
|
color={disableSubmit() ? 'text.secondary' : 'text.primary'}
|
||||||
|
>
|
||||||
|
{type == null ? t('Order for ') : type == 1 ? t('Buy order for ') : t('Sell order for ')}
|
||||||
|
{advancedOptions && maker.minAmount != ''
|
||||||
|
? pn(maker.minAmount) + '-' + pn(maker.maxAmount)
|
||||||
|
: pn(maker.amount)}
|
||||||
|
{' ' + currencyCode}
|
||||||
|
{maker.isExplicit
|
||||||
|
? t(' of {{satoshis}} Satoshis', { satoshis: pn(maker.satoshis) })
|
||||||
|
: maker.premium == 0
|
||||||
|
? t(' at market price')
|
||||||
|
: maker.premium > 0
|
||||||
|
? t(' at a {{premium}}% premium', { premium: maker.premium })
|
||||||
|
: t(' at a {{discount}}% discount', { discount: -maker.premium })}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfirmationDialogs = function () {
|
||||||
|
return systemClient.getCookie('robot_token') ? (
|
||||||
|
<StoreTokenDialog
|
||||||
|
open={openDialogs}
|
||||||
|
onClose={() => setOpenDialogs(false)}
|
||||||
|
onClickCopy={() => systemClient.copyToClipboard(systemClient.getCookie('robot_token'))}
|
||||||
|
copyIconColor={'primary'}
|
||||||
|
onClickBack={() => setOpenDialogs(false)}
|
||||||
|
onClickDone={handleCreateOrder}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<NoRobotDialog open={openDialogs} onClose={() => setOpenDialogs(false)} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<ConfirmationDialogs />
|
||||||
|
<Collapse in={loadingLimits}>
|
||||||
|
<div style={{ display: loadingLimits ? '' : 'none' }}>
|
||||||
|
<LinearProgress />
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
<Collapse in={!(loadingLimits || collapseAll)}>
|
||||||
|
<Grid container justifyContent='space-between' spacing={0} sx={{ maxHeight: '1em' }}>
|
||||||
|
<Grid item>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
width: '1.3em',
|
||||||
|
height: '1.3em',
|
||||||
|
position: 'relative',
|
||||||
|
bottom: '0.2em',
|
||||||
|
right: '0.2em',
|
||||||
|
color: 'text.secondary',
|
||||||
|
}}
|
||||||
|
onClick={clearMaker}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
placement='top'
|
||||||
|
enterTouchDelay={500}
|
||||||
|
enterDelay={700}
|
||||||
|
enterNextDelay={2000}
|
||||||
|
title={t('Clear form')}
|
||||||
|
>
|
||||||
|
<DeleteSweep sx={{ width: '1em', height: '1em' }} />
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip enterTouchDelay={0} placement='top' title={t('Enable advanced options')}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '4em',
|
||||||
|
height: '1.1em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
size='small'
|
||||||
|
disabled={loadingLimits}
|
||||||
|
checked={advancedOptions}
|
||||||
|
onChange={handleClickAdvanced}
|
||||||
|
/>
|
||||||
|
<SelfImprovement sx={{ color: 'text.secondary' }} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<Collapse in={!collapseAll}>
|
||||||
|
<Grid container spacing={1} justifyContent='center' alignItems='center'>
|
||||||
|
<Grid item>
|
||||||
|
<FormControl component='fieldset'>
|
||||||
|
<FormHelperText sx={{ textAlign: 'center' }}>
|
||||||
|
{t('Buy or Sell Bitcoin?')}
|
||||||
|
</FormHelperText>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
size={advancedOptions ? 'small' : 'large'}
|
||||||
|
variant='contained'
|
||||||
|
onClick={() =>
|
||||||
|
setAppState({
|
||||||
|
type: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disableElevation={type == 1}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: type == 1 ? 'primary.main' : 'background.paper',
|
||||||
|
color: type == 1 ? 'background.paper' : 'text.secondary',
|
||||||
|
':hover': {
|
||||||
|
color: 'background.paper',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Buy')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={advancedOptions ? 'small' : 'large'}
|
||||||
|
variant='contained'
|
||||||
|
onClick={() =>
|
||||||
|
setAppState({
|
||||||
|
type: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disableElevation={type == 0}
|
||||||
|
color='secondary'
|
||||||
|
sx={{
|
||||||
|
backgroundColor: type == 0 ? 'secondary.main' : 'background.paper',
|
||||||
|
color: type == 0 ? 'background.secondary' : 'text.secondary',
|
||||||
|
':hover': {
|
||||||
|
color: 'background.paper',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Sell')}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item>
|
||||||
|
<Collapse in={advancedOptions}>
|
||||||
|
<AmountRange
|
||||||
|
minAmount={maker.minAmount}
|
||||||
|
handleRangeAmountChange={handleRangeAmountChange}
|
||||||
|
currency={currency}
|
||||||
|
currencyCode={currencyCode}
|
||||||
|
handleCurrencyChange={handleCurrencyChange}
|
||||||
|
amountLimits={amountLimits}
|
||||||
|
maxAmount={maker.maxAmount}
|
||||||
|
minAmountError={minAmountError()}
|
||||||
|
maxAmountError={maxAmountError()}
|
||||||
|
handleMinAmountChange={handleMinAmountChange}
|
||||||
|
handleMaxAmountChange={handleMaxAmountChange}
|
||||||
|
/>
|
||||||
|
</Collapse>
|
||||||
|
<Collapse in={!advancedOptions}>
|
||||||
|
<Grid item>
|
||||||
|
<Grid container alignItems='stretch' style={{ display: 'flex' }}>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<Tooltip
|
||||||
|
placement='top'
|
||||||
|
enterTouchDelay={500}
|
||||||
|
enterDelay={700}
|
||||||
|
enterNextDelay={2000}
|
||||||
|
title={t('Amount of fiat to exchange for bitcoin')}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
disabled={advancedOptions}
|
||||||
|
variant={advancedOptions ? 'filled' : 'outlined'}
|
||||||
|
error={
|
||||||
|
maker.amount != '' &&
|
||||||
|
(maker.amount < amountLimits[0] || maker.amount > amountLimits[1])
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
maker.amount < amountLimits[0] && maker.amount != ''
|
||||||
|
? t('Must be more than {{minAmount}}', {
|
||||||
|
minAmount: pn(parseFloat(amountLimits[0].toPrecision(2))),
|
||||||
|
})
|
||||||
|
: maker.amount > amountLimits[1] && maker.amount != ''
|
||||||
|
? t('Must be less than {{maxAmount}}', {
|
||||||
|
maxAmount: pn(parseFloat(amountLimits[1].toPrecision(2))),
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
label={t('Amount')}
|
||||||
|
required={true}
|
||||||
|
value={maker.amount}
|
||||||
|
type='number'
|
||||||
|
inputProps={{
|
||||||
|
min: 0,
|
||||||
|
style: {
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onChange={(e) => setMaker({ ...maker, amount: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<Select
|
||||||
|
fullWidth
|
||||||
|
sx={{ backgroundColor: theme.palette.background.paper, borderRadius: '4px' }}
|
||||||
|
required={true}
|
||||||
|
inputProps={{
|
||||||
|
style: { textAlign: 'center' },
|
||||||
|
}}
|
||||||
|
value={currency == 0 ? 1 : currency}
|
||||||
|
onChange={(e) => handleCurrencyChange(e.target.value)}
|
||||||
|
>
|
||||||
|
{Object.entries(currencyDict).map(([key, value]) => (
|
||||||
|
<MenuItem key={key} value={parseInt(key)}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<FlagWithProps code={value} />
|
||||||
|
{' ' + value}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Collapse>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<AutocompletePayments
|
||||||
|
onAutocompleteChange={handlePaymentMethodChange}
|
||||||
|
// listBoxProps={{ sx: { width: '15.3em', maxHeight: '20em' } }}
|
||||||
|
optionsType={currency == 1000 ? 'swap' : 'fiat'}
|
||||||
|
error={maker.badPaymentMethod}
|
||||||
|
helperText={maker.badPaymentMethod ? t('Must be shorter than 65 characters') : ''}
|
||||||
|
label={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')}
|
||||||
|
asFilter={false}
|
||||||
|
value={maker.paymentMethods}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{!advancedOptions && pricingMethods ? (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: '0.5em',
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
border: '1px solid',
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#ffffff' : '#2f2f2f',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl component='fieldset'>
|
||||||
|
<FormHelperText sx={{ textAlign: 'center', position: 'relative', top: '0.2em' }}>
|
||||||
|
{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')}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
value='relative'
|
||||||
|
control={<Radio color='primary' />}
|
||||||
|
label={t('Relative')}
|
||||||
|
labelPlacement='end'
|
||||||
|
onClick={handleClickRelative}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
placement='top'
|
||||||
|
enterTouchDelay={0}
|
||||||
|
enterDelay={1000}
|
||||||
|
enterNextDelay={2000}
|
||||||
|
title={t('Set a fix amount of satoshis')}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
disabled={advancedOptions}
|
||||||
|
value='explicit'
|
||||||
|
control={<Radio color='secondary' />}
|
||||||
|
label={t('Exact')}
|
||||||
|
labelPlacement='end'
|
||||||
|
onClick={handleClickExplicit}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div style={{ display: maker.isExplicit ? '' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label={t('Satoshis')}
|
||||||
|
error={maker.badSatoshisText != ''}
|
||||||
|
helperText={maker.badSatoshisText === '' ? null : maker.badSatoshisText}
|
||||||
|
type='number'
|
||||||
|
required={true}
|
||||||
|
value={maker.satoshis}
|
||||||
|
inputProps={{
|
||||||
|
min: satoshisLimits[0],
|
||||||
|
max: satoshisLimits[1],
|
||||||
|
style: {
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onChange={handleSatoshisChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: maker.isExplicit ? 'none' : '' }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
error={maker.badPremiumText != ''}
|
||||||
|
helperText={maker.badPremiumText === '' ? null : maker.badPremiumText}
|
||||||
|
label={t('Premium over Market (%)')}
|
||||||
|
type='number'
|
||||||
|
value={maker.premium}
|
||||||
|
inputProps={{
|
||||||
|
min: -100,
|
||||||
|
max: 999,
|
||||||
|
style: {
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onChange={handlePremiumChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Collapse in={advancedOptions}>
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<LocalizationProvider dateAdapter={DateFnsUtils}>
|
||||||
|
<TimePicker
|
||||||
|
ampm={false}
|
||||||
|
openTo='hours'
|
||||||
|
views={['hours', 'minutes']}
|
||||||
|
inputFormat='HH:mm'
|
||||||
|
mask='__:__'
|
||||||
|
components={{
|
||||||
|
OpenPickerIcon: HourglassTop,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position='end'>
|
||||||
|
<HourglassTop />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
renderInput={(props) => <TextField {...props} />}
|
||||||
|
label={t('Public Duration (HH:mm)')}
|
||||||
|
value={maker.publicExpiryTime}
|
||||||
|
onChange={handleChangePublicDuration}
|
||||||
|
minTime={new Date(0, 0, 0, 0, 10)}
|
||||||
|
maxTime={new Date(0, 0, 0, 23, 59)}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<LocalizationProvider dateAdapter={DateFnsUtils}>
|
||||||
|
<TimePicker
|
||||||
|
ampm={false}
|
||||||
|
openTo='hours'
|
||||||
|
views={['hours', 'minutes']}
|
||||||
|
inputFormat='HH:mm'
|
||||||
|
mask='__:__'
|
||||||
|
components={{
|
||||||
|
OpenPickerIcon: HourglassTop,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position='end'>
|
||||||
|
<HourglassTop />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
renderInput={(props) => <TextField {...props} />}
|
||||||
|
label={t('Escrow/Invoice Timer (HH:mm)')}
|
||||||
|
value={maker.escrowExpiryTime}
|
||||||
|
onChange={handleChangeEscrowDuration}
|
||||||
|
minTime={new Date(0, 0, 0, 1, 0)}
|
||||||
|
maxTime={new Date(0, 0, 0, 8, 0)}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: '0.5em',
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
border: '1px solid',
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#ffffff' : '#2f2f2f',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid container direction='column' alignItems='center' spacing={0.5}>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
enterDelay={800}
|
||||||
|
enterTouchDelay={0}
|
||||||
|
placement='top'
|
||||||
|
title={t(
|
||||||
|
'Set the skin-in-the-game, increase for higher safety assurance',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant='caption'
|
||||||
|
sx={{
|
||||||
|
color: 'text.secondary',
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Fidelity Bond Size')}{' '}
|
||||||
|
<Lock sx={{ height: '0.8em', width: '0.8em' }} />
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ width: 'calc(100% - 2em)' }}>
|
||||||
|
<Slider
|
||||||
|
sx={{ width: '100%', align: 'center' }}
|
||||||
|
aria-label='Bond Size (%)'
|
||||||
|
defaultValue={3}
|
||||||
|
value={maker.bondSize}
|
||||||
|
valueLabelDisplay='auto'
|
||||||
|
valueLabelFormat={(x: string) => x + '%'}
|
||||||
|
step={0.25}
|
||||||
|
marks={[
|
||||||
|
{ value: 2, label: '2%' },
|
||||||
|
{ value: 5, label: '5%' },
|
||||||
|
{ value: 10, label: '10%' },
|
||||||
|
{ value: 15, label: '15%' },
|
||||||
|
]}
|
||||||
|
min={2}
|
||||||
|
max={15}
|
||||||
|
onChange={(e) => setMaker({ ...maker, bondSize: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Collapse>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<Grid container direction='column' alignItems='center'>
|
||||||
|
<Grid item>
|
||||||
|
<SummaryText />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item>
|
||||||
|
<Grid container direction='row' justifyItems='center' alignItems='center' spacing={1}>
|
||||||
|
<Grid item>
|
||||||
|
{/* conditions to disable the make button */}
|
||||||
|
{disableSubmit() ? (
|
||||||
|
<Tooltip enterTouchDelay={0} title={t('You must fill the form correctly')}>
|
||||||
|
<div>
|
||||||
|
<Button disabled color='primary' variant='contained'>
|
||||||
|
{t(submitButtonLabel)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<LoadingButton
|
||||||
|
loading={submittingRequest}
|
||||||
|
color='primary'
|
||||||
|
variant='contained'
|
||||||
|
onClick={() => {
|
||||||
|
disableRequest ? onSubmit() : setOpenDialogs(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(submitButtonLabel)}
|
||||||
|
</LoadingButton>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
{collapseAll ? (
|
||||||
|
<Grid item>
|
||||||
|
<Collapse in={collapseAll} orientation='vertical'>
|
||||||
|
<IconButton onClick={onReset}>
|
||||||
|
<Tooltip
|
||||||
|
placement='top'
|
||||||
|
enterTouchDelay={500}
|
||||||
|
enterDelay={700}
|
||||||
|
enterNextDelay={2000}
|
||||||
|
title={t('Edit order')}
|
||||||
|
>
|
||||||
|
<Edit sx={{ width: '1.5em', height: '1.5em' }} />
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
|
</Collapse>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item>
|
||||||
|
<Typography align='center' component='h2' variant='subtitle2' color='secondary'>
|
||||||
|
{badRequest}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Collapse in={!loadingLimits}>
|
||||||
|
<Tooltip
|
||||||
|
placement='top'
|
||||||
|
enterTouchDelay={0}
|
||||||
|
enterDelay={1000}
|
||||||
|
enterNextDelay={2000}
|
||||||
|
title={
|
||||||
|
maker.isExplicit
|
||||||
|
? t('Your order fixed exchange rate')
|
||||||
|
: t("Your order's current exchange rate. Rate will move with the market.")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography align='center' variant='caption' color='text.secondary'>
|
||||||
|
{(maker.isExplicit ? t('Order rate:') : t('Order current rate:')) +
|
||||||
|
` ${pn(currentPrice)} ${currencyCode}/BTC`}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Collapse>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MakerForm;
|
109
frontend/src/components/MakerPage/MakerPage.tsx
Normal file
109
frontend/src/components/MakerPage/MakerPage.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Button, Grid, Paper, Collapse, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
import { LimitList, Maker, Order, defaultMaker } from '../../models';
|
||||||
|
import MakerForm from './MakerForm';
|
||||||
|
import BookTable from '../BookPage/BookTable';
|
||||||
|
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import filterOrders from '../../utils/filterOrders';
|
||||||
|
|
||||||
|
interface MakerPageProps {
|
||||||
|
limits: LimitList;
|
||||||
|
fetchLimits: () => void;
|
||||||
|
orders: Order[];
|
||||||
|
loadingLimits: boolean;
|
||||||
|
type: number;
|
||||||
|
windowHeight: number;
|
||||||
|
windowWidth: number;
|
||||||
|
currency: number;
|
||||||
|
setAppState: (state: object) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MakerPage = ({
|
||||||
|
limits,
|
||||||
|
fetchLimits,
|
||||||
|
orders,
|
||||||
|
loadingLimits,
|
||||||
|
currency,
|
||||||
|
type,
|
||||||
|
setAppState,
|
||||||
|
windowHeight,
|
||||||
|
windowWidth,
|
||||||
|
}: MakerPageProps): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const [maker, setMaker] = useState<Maker>(defaultMaker);
|
||||||
|
const maxHeight = windowHeight ? windowHeight * 0.85 - 7 : 1000;
|
||||||
|
const [showMatches, setShowMatches] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const matches = filterOrders({
|
||||||
|
orders,
|
||||||
|
baseFilter: { currency: currency == 0 ? 1 : currency, type },
|
||||||
|
paymentMethods: maker.paymentMethods,
|
||||||
|
amountFilter: {
|
||||||
|
amount: maker.amount,
|
||||||
|
minAmount: maker.minAmount,
|
||||||
|
maxAmount: maker.maxAmount,
|
||||||
|
threshold: 0.7,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container direction='column' alignItems='center' spacing={1}>
|
||||||
|
<Grid item>
|
||||||
|
<Collapse in={matches.length > 0 && showMatches}>
|
||||||
|
<Grid container direction='column' alignItems='center' spacing={1}>
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant='h5'>{t('Existing orders match yours!')}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<BookTable
|
||||||
|
orders={matches.slice(0, matches.length > 4 ? 4 : matches.length)}
|
||||||
|
type={type}
|
||||||
|
currency={currency}
|
||||||
|
maxWidth={Math.min(windowWidth, 60)} // EM units
|
||||||
|
maxHeight={Math.min(matches.length * 3.25 + 3.575, 16.575)} // EM units
|
||||||
|
defaultFullscreen={false}
|
||||||
|
showControls={false}
|
||||||
|
showFooter={false}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Collapse>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Paper
|
||||||
|
elevation={12}
|
||||||
|
style={{ padding: 8, width: '17.25em', maxHeight: `${maxHeight}em`, overflow: 'auto' }}
|
||||||
|
>
|
||||||
|
<MakerForm
|
||||||
|
limits={limits}
|
||||||
|
fetchLimits={fetchLimits}
|
||||||
|
loadingLimits={loadingLimits}
|
||||||
|
pricingMethods={false}
|
||||||
|
setAppState={setAppState}
|
||||||
|
maker={maker}
|
||||||
|
setMaker={setMaker}
|
||||||
|
type={type}
|
||||||
|
currency={currency}
|
||||||
|
disableRequest={matches.length > 0 && !showMatches}
|
||||||
|
collapseAll={showMatches}
|
||||||
|
onSubmit={() => setShowMatches(matches.length > 0)}
|
||||||
|
onReset={() => setShowMatches(false)}
|
||||||
|
submitButtonLabel={matches.length > 0 && !showMatches ? 'Submit' : 'Create order'}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Button color='secondary' variant='contained' onClick={() => history.push('/')}>
|
||||||
|
{t('Back')}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MakerPage;
|
5
frontend/src/components/MakerPage/index.ts
Normal file
5
frontend/src/components/MakerPage/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import MakerPage from './MakerPage';
|
||||||
|
export default MakerPage;
|
||||||
|
|
||||||
|
export { default as MakerForm } from './MakerForm';
|
||||||
|
export { default as AutocompletePayments } from './AutocompletePayments';
|
@ -59,7 +59,7 @@ const TorConnection = (): JSX.Element => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!window?.NativeRobosats) {
|
if (window?.NativeRobosats == null) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,20 +425,23 @@ export default class PaymentIcon extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.icon === 'custom') {
|
if (this.props.icon === undefined) {
|
||||||
|
return null;
|
||||||
|
} else if (this.props.icon === 'custom') {
|
||||||
return (
|
return (
|
||||||
<DashboardCustomizeIcon
|
<DashboardCustomizeIcon
|
||||||
sx={{ ...this.props, filter: 'drop-shadow(1.5px 1.5px 1.5px rgba(0, 0, 0, 0.2))' }}
|
sx={{ ...this.props, filter: 'drop-shadow(1.5px 1.5px 1.5px rgba(0, 0, 0, 0.2))' }}
|
||||||
color='primary'
|
color='primary'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
{...this.props}
|
||||||
|
src={icons[this.props.icon].image}
|
||||||
|
style={{ borderRadius: '23%', filter: 'drop-shadow(1.5px 1.5px 2px rgba(0, 0, 0, 0.2))' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<img
|
|
||||||
{...this.props}
|
|
||||||
src={icons[this.props.icon].image}
|
|
||||||
style={{ borderRadius: '23%', filter: 'drop-shadow(1.5px 1.5px 2px rgba(0, 0, 0, 0.2))' }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
frontend/src/models/Maker.model.ts
Normal file
39
frontend/src/models/Maker.model.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
export interface Maker {
|
||||||
|
isExplicit: boolean;
|
||||||
|
amount: string;
|
||||||
|
paymentMethods: string[];
|
||||||
|
paymentMethodsText: string;
|
||||||
|
badPaymentMethod: boolean;
|
||||||
|
premium: number | string;
|
||||||
|
satoshis: string;
|
||||||
|
publicExpiryTime: Date;
|
||||||
|
publicDuration: number;
|
||||||
|
escrowExpiryTime: Date;
|
||||||
|
escrowDuration: number;
|
||||||
|
bondSize: number;
|
||||||
|
minAmount: string;
|
||||||
|
maxAmount: string;
|
||||||
|
badSatoshisText: string;
|
||||||
|
badPremiumText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultMaker: Maker = {
|
||||||
|
isExplicit: false,
|
||||||
|
amount: '',
|
||||||
|
paymentMethods: [],
|
||||||
|
paymentMethodsText: 'not specified',
|
||||||
|
badPaymentMethod: false,
|
||||||
|
premium: '',
|
||||||
|
satoshis: '',
|
||||||
|
publicExpiryTime: new Date(0, 0, 0, 23, 59),
|
||||||
|
publicDuration: 86340,
|
||||||
|
escrowExpiryTime: new Date(0, 0, 0, 3, 0),
|
||||||
|
escrowDuration: 10800,
|
||||||
|
bondSize: 3,
|
||||||
|
minAmount: '',
|
||||||
|
maxAmount: '',
|
||||||
|
badPremiumText: '',
|
||||||
|
badSatoshisText: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Maker;
|
5
frontend/src/models/index.ts
Normal file
5
frontend/src/models/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { default as LimitList } from './Limit.model';
|
||||||
|
export { Limit } from './Limit.model';
|
||||||
|
export { default as Maker } from './Maker.model';
|
||||||
|
export { defaultMaker as defaultMaker } from './Maker.model';
|
||||||
|
export { default as Order } from './Order.model';
|
@ -79,7 +79,7 @@ class ApiNativeClient implements ApiClient {
|
|||||||
if (this.assetsCache[path]) {
|
if (this.assetsCache[path]) {
|
||||||
return this.assetsCache[path];
|
return this.assetsCache[path];
|
||||||
} else if (path in this.assetsPromises) {
|
} else if (path in this.assetsPromises) {
|
||||||
return this.assetsPromises[path];
|
return await this.assetsPromises[path];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.assetsPromises[path] = new Promise<string>(async (resolve, reject) => {
|
this.assetsPromises[path] = new Promise<string>(async (resolve, reject) => {
|
||||||
@ -95,7 +95,7 @@ class ApiNativeClient implements ApiClient {
|
|||||||
resolve(this.assetsCache[path]);
|
resolve(this.assetsCache[path]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.assetsPromises[path];
|
return await this.assetsPromises[path];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
67
frontend/src/utils/filterOrders.ts
Normal file
67
frontend/src/utils/filterOrders.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import Order from '../models/Order.model';
|
||||||
|
|
||||||
|
interface BaseFilter {
|
||||||
|
currency: number;
|
||||||
|
type: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AmountFilter {
|
||||||
|
amount: string;
|
||||||
|
minAmount: string;
|
||||||
|
maxAmount: string;
|
||||||
|
threshold: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterOrders {
|
||||||
|
orders: Order[];
|
||||||
|
baseFilter: BaseFilter;
|
||||||
|
amountFilter?: AmountFilter | null;
|
||||||
|
paymentMethods?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterByPayment = function (order: Order, paymentMethods: string[]) {
|
||||||
|
if (paymentMethods.length === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
let result = false;
|
||||||
|
paymentMethods.forEach((method) => {
|
||||||
|
result = result || order.payment_method.includes(method.name);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByAmount = function (order: Order, filter: AmountFilter) {
|
||||||
|
const filterMaxAmount = filter.amount != '' ? filter.amount : filter.maxAmount;
|
||||||
|
const filterMinAmount = filter.amount != '' ? filter.amount : filter.minAmount;
|
||||||
|
const orderMinAmount =
|
||||||
|
order.amount === '' || order.amount === null ? order.min_amount : order.amount;
|
||||||
|
const orderMaxAmount =
|
||||||
|
order.amount === '' || order.amount === null ? order.max_amount : order.amount;
|
||||||
|
|
||||||
|
return (
|
||||||
|
orderMaxAmount < filterMaxAmount * (1 + filter.threshold) &&
|
||||||
|
orderMinAmount > filterMinAmount * (1 - filter.threshold)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOrders = function ({
|
||||||
|
orders,
|
||||||
|
baseFilter,
|
||||||
|
paymentMethods = [],
|
||||||
|
amountFilter = null,
|
||||||
|
}: FilterOrders) {
|
||||||
|
const filteredOrders = orders.filter((order) => {
|
||||||
|
const typeChecks = order.type == baseFilter.type || baseFilter.type == null;
|
||||||
|
const currencyChecks = order.currency == baseFilter.currency || baseFilter.currency == 0;
|
||||||
|
const paymentMethodChecks =
|
||||||
|
paymentMethods.length > 0 ? filterByPayment(order, paymentMethods) : true;
|
||||||
|
const amountChecks = amountFilter != null ? filterByAmount(order, amountFilter) : true;
|
||||||
|
|
||||||
|
return typeChecks && currencyChecks && paymentMethodChecks && amountChecks;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filteredOrders;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default filterOrders;
|
@ -94,8 +94,7 @@
|
|||||||
"Buyer": "Compra",
|
"Buyer": "Compra",
|
||||||
"I want to": "Vull",
|
"I want to": "Vull",
|
||||||
"Select Order Type": "Selecciona tipus d'ordre",
|
"Select Order Type": "Selecciona tipus d'ordre",
|
||||||
"ANY_type": "TOT",
|
"ANY": "TOT",
|
||||||
"ANY_currency": "TOT",
|
|
||||||
"BUY": "COMPRAR",
|
"BUY": "COMPRAR",
|
||||||
"SELL": "VENDRE",
|
"SELL": "VENDRE",
|
||||||
"and receive": "i rebre",
|
"and receive": "i rebre",
|
||||||
|
@ -92,8 +92,7 @@
|
|||||||
"Buyer": "Kupující",
|
"Buyer": "Kupující",
|
||||||
"I want to": "Já chci",
|
"I want to": "Já chci",
|
||||||
"Select Order Type": "Vybrat druh nabídky",
|
"Select Order Type": "Vybrat druh nabídky",
|
||||||
"ANY_type": "VŠE",
|
"ANY": "VŠE",
|
||||||
"ANY_currency": "VŠE",
|
|
||||||
"BUY": "KOUPĚ",
|
"BUY": "KOUPĚ",
|
||||||
"SELL": "PRODEJ",
|
"SELL": "PRODEJ",
|
||||||
"and receive": "získat",
|
"and receive": "získat",
|
||||||
|
@ -92,8 +92,7 @@
|
|||||||
"Buyer": "Käufer",
|
"Buyer": "Käufer",
|
||||||
"I want to": "Ich möchte",
|
"I want to": "Ich möchte",
|
||||||
"Select Order Type": "Order Typ auswählen",
|
"Select Order Type": "Order Typ auswählen",
|
||||||
"ANY_type": "ALLE",
|
"ANY": "ALLE",
|
||||||
"ANY_currency": "ALLE",
|
|
||||||
"BUY": "KAUFEN",
|
"BUY": "KAUFEN",
|
||||||
"SELL": "VERKAUFEN",
|
"SELL": "VERKAUFEN",
|
||||||
"and receive": "und erhalte",
|
"and receive": "und erhalte",
|
||||||
|
@ -61,17 +61,15 @@
|
|||||||
"to": "to",
|
"to": "to",
|
||||||
"Expiry Timers": "Expiry Timers",
|
"Expiry Timers": "Expiry Timers",
|
||||||
"Public Duration (HH:mm)": "Public Duration (HH:mm)",
|
"Public Duration (HH:mm)": "Public Duration (HH:mm)",
|
||||||
"Escrow Deposit Time-Out (HH:mm)": "Escrow Deposit Time-Out (HH:mm)",
|
"Escrow/Invoice Timer (HH:mm)": "Escrow/Invoice Timer (HH:mm)",
|
||||||
"Set the skin-in-the-game, increase for higher safety assurance": "Set the skin-in-the-game, increase for higher safety assurance",
|
"Set the skin-in-the-game, increase for higher safety assurance": "Set the skin-in-the-game, increase for higher safety assurance",
|
||||||
"Fidelity Bond Size": "Fidelity Bond Size",
|
"Fidelity Bond Size": "Fidelity Bond Size",
|
||||||
"Allow bondless takers": "Allow bondless takers",
|
|
||||||
"COMING SOON - High risk! Limited to {{limitSats}}K Sats": "COMING SOON - High risk! Limited to {{limitSats}}K Sats",
|
|
||||||
"You must fill the order correctly": "You must fill the order correctly",
|
"You must fill the order correctly": "You must fill the order correctly",
|
||||||
"Create Order": "Create Order",
|
"Create Order": "Create Order",
|
||||||
"Back": "Back",
|
"Back": "Back",
|
||||||
"Create an order for ": "Create an order for ",
|
"Create an order for ": "Create an order for ",
|
||||||
"Create a BTC buy order for ": "Create a BTC buy order for ",
|
"Buy order for ": "Buy order for ",
|
||||||
"Create a BTC sell order for ": "Create a BTC sell order for ",
|
"Sell order for ": "Sell order for ",
|
||||||
" of {{satoshis}} Satoshis": " of {{satoshis}} Satoshis",
|
" of {{satoshis}} Satoshis": " of {{satoshis}} Satoshis",
|
||||||
" at market price": " at market price",
|
" at market price": " at market price",
|
||||||
" at a {{premium}}% premium": " at a {{premium}}% premium",
|
" at a {{premium}}% premium": " at a {{premium}}% premium",
|
||||||
@ -85,6 +83,10 @@
|
|||||||
"Done": "Done",
|
"Done": "Done",
|
||||||
"You do not have a robot avatar": "You do not have a robot avatar",
|
"You do not have a robot avatar": "You do not have a robot avatar",
|
||||||
"You need to generate a robot avatar in order to become an order maker": "You need to generate a robot avatar in order to become an order maker",
|
"You need to generate a robot avatar in order to become an order maker": "You need to generate a robot avatar in order to become an order maker",
|
||||||
|
"Edit order": "Edit order",
|
||||||
|
"Existing orders match yours!": "Existing orders match yours!",
|
||||||
|
"Enable advanced options": "Enable advanced options",
|
||||||
|
"Clear form": "Clear form",
|
||||||
|
|
||||||
"PAYMENT METHODS - autocompletePayments.js": "Payment method strings",
|
"PAYMENT METHODS - autocompletePayments.js": "Payment method strings",
|
||||||
"not specified": "Not specified",
|
"not specified": "Not specified",
|
||||||
@ -94,17 +96,14 @@
|
|||||||
"Cash F2F": "Cash F2F",
|
"Cash F2F": "Cash F2F",
|
||||||
"On-Chain BTC": "On-Chain BTC",
|
"On-Chain BTC": "On-Chain BTC",
|
||||||
|
|
||||||
"BOOK PAGE - BookPage.js": "The Book Order page",
|
"BOOK PAGE - BookPage": "The Book Order page",
|
||||||
"Seller": "Seller",
|
"Seller": "Seller",
|
||||||
"Buyer": "Buyer",
|
"Buyer": "Buyer",
|
||||||
"I want to": "I want to",
|
"I want to": "I want to",
|
||||||
"Select Order Type": "Select Order Type",
|
"Select Order Type": "Select Order Type",
|
||||||
"ANY_type": "ANY",
|
"ANY": "ANY",
|
||||||
"ANY_currency": "ANY",
|
|
||||||
"BUY": "BUY",
|
"BUY": "BUY",
|
||||||
"SELL": "SELL",
|
"SELL": "SELL",
|
||||||
"and receive": "and receive",
|
|
||||||
"and pay with": "and pay with",
|
|
||||||
"and use": "and use",
|
"and use": "and use",
|
||||||
"Select Payment Currency": "Select Payment Currency",
|
"Select Payment Currency": "Select Payment Currency",
|
||||||
"Robot": "Robot",
|
"Robot": "Robot",
|
||||||
@ -122,7 +121,6 @@
|
|||||||
"Filter has no results": "Filter has no results",
|
"Filter has no results": "Filter has no results",
|
||||||
"Be the first one to create an order": "Be the first one to create an order",
|
"Be the first one to create an order": "Be the first one to create an order",
|
||||||
"Orders per page:": "Orders per page:",
|
"Orders per page:": "Orders per page:",
|
||||||
"No rows": "No rows",
|
|
||||||
"No results found.": "No results found.",
|
"No results found.": "No results found.",
|
||||||
"An error occurred.": "An error occurred.",
|
"An error occurred.": "An error occurred.",
|
||||||
"Columns": "Columns",
|
"Columns": "Columns",
|
||||||
@ -168,6 +166,15 @@
|
|||||||
"no": "no",
|
"no": "no",
|
||||||
"Depth chart": "Depth chart",
|
"Depth chart": "Depth chart",
|
||||||
"Chart": "Chart",
|
"Chart": "Chart",
|
||||||
|
"List": "List",
|
||||||
|
"pay with": "pay with",
|
||||||
|
"Timer": "Timer",
|
||||||
|
"Expiry": "Expiry",
|
||||||
|
"Sats now": "Sats now",
|
||||||
|
"Bond": "Bond",
|
||||||
|
"swap to": "swap to",
|
||||||
|
"DESTINATION": "DESTINATION",
|
||||||
|
"METHOD": "METHOD",
|
||||||
|
|
||||||
"BOTTOM BAR AND MISC - BottomBar.js": "Bottom Bar user profile and miscellaneous dialogs",
|
"BOTTOM BAR AND MISC - BottomBar.js": "Bottom Bar user profile and miscellaneous dialogs",
|
||||||
"Stats For Nerds": "Stats For Nerds",
|
"Stats For Nerds": "Stats For Nerds",
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
"to": "a ",
|
"to": "a ",
|
||||||
"Expiry Timers": "Temporizadores",
|
"Expiry Timers": "Temporizadores",
|
||||||
"Public Duration (HH:mm)": "Duración pública (HH:mm)",
|
"Public Duration (HH:mm)": "Duración pública (HH:mm)",
|
||||||
"Escrow Deposit Time-Out (HH:mm)": "Plazo límite depósito (HH:mm)",
|
"Escrow/Invoice Timer (HH:mm)": "Plazo depósito/factura (HH:mm)",
|
||||||
"Set the skin-in-the-game, increase for higher safety assurance": "Establece la implicación requerida (aumentar para mayor seguridad)",
|
"Set the skin-in-the-game, increase for higher safety assurance": "Establece la implicación requerida (aumentar para mayor seguridad)",
|
||||||
"Fidelity Bond Size": "Tamaño de la fianza",
|
"Fidelity Bond Size": "Tamaño de la fianza",
|
||||||
"Allow bondless takers": "Permitir tomadores sin fianza",
|
"Allow bondless takers": "Permitir tomadores sin fianza",
|
||||||
@ -65,8 +65,8 @@
|
|||||||
"Create Order": "Crear orden",
|
"Create Order": "Crear orden",
|
||||||
"Back": "Volver",
|
"Back": "Volver",
|
||||||
"Create an order for ": "Crear una orden por ",
|
"Create an order for ": "Crear una orden por ",
|
||||||
"Create a BTC buy order for ": "Crear orden de compra de BTC por ",
|
"Buy order for ": "Compra de BTC por ",
|
||||||
"Create a BTC sell order for ": "Crear orden de venta de BTC por ",
|
"Sell order for ": "Venta de BTC por ",
|
||||||
" of {{satoshis}} Satoshis": " de {{satoshis}} Sats",
|
" of {{satoshis}} Satoshis": " de {{satoshis}} Sats",
|
||||||
" at market price": " a precio de mercado",
|
" at market price": " a precio de mercado",
|
||||||
" at a {{premium}}% premium": " con una prima del {{premium}}%",
|
" at a {{premium}}% premium": " con una prima del {{premium}}%",
|
||||||
@ -80,6 +80,10 @@
|
|||||||
"Done": "Hecho",
|
"Done": "Hecho",
|
||||||
"You do not have a robot avatar": "No tienes un avatar robot",
|
"You do not have a robot avatar": "No tienes un avatar robot",
|
||||||
"You need to generate a robot avatar in order to become an order maker": "Necesitas generar un avatar robot antes de crear una orden",
|
"You need to generate a robot avatar in order to become an order maker": "Necesitas generar un avatar robot antes de crear una orden",
|
||||||
|
"Edit order": "Editar orden",
|
||||||
|
"Existing orders match yours!": "¡Existen órdenes que coinciden!",
|
||||||
|
"Enable advanced options": "Activar opciones avanzadas",
|
||||||
|
"Clear form": "Borrar campos",
|
||||||
|
|
||||||
"PAYMENT METHODS - autocompletePayments.js": "Payment method strings",
|
"PAYMENT METHODS - autocompletePayments.js": "Payment method strings",
|
||||||
"not specified": "Sin especificar",
|
"not specified": "Sin especificar",
|
||||||
@ -96,8 +100,7 @@
|
|||||||
"Buyer": "Compra",
|
"Buyer": "Compra",
|
||||||
"I want to": "Quiero",
|
"I want to": "Quiero",
|
||||||
"Select Order Type": "Selecciona tipo de orden",
|
"Select Order Type": "Selecciona tipo de orden",
|
||||||
"ANY_type": "TODO",
|
"ANY": "TODO",
|
||||||
"ANY_currency": "TODO",
|
|
||||||
"BUY": "COMPRAR",
|
"BUY": "COMPRAR",
|
||||||
"SELL": "VENDER",
|
"SELL": "VENDER",
|
||||||
"and receive": "y recibir",
|
"and receive": "y recibir",
|
||||||
@ -165,6 +168,15 @@
|
|||||||
"no": "no",
|
"no": "no",
|
||||||
"Depth chart": "Gráfico de profundidad",
|
"Depth chart": "Gráfico de profundidad",
|
||||||
"Chart": "Gráfico",
|
"Chart": "Gráfico",
|
||||||
|
"List": "Tabla",
|
||||||
|
"pay with": "pagar con",
|
||||||
|
"Timer": "Plazo",
|
||||||
|
"Expiry": "Caduca en",
|
||||||
|
"Sats now": "Sats ahora",
|
||||||
|
"Bond": "Fianza",
|
||||||
|
"swap to": "swap a",
|
||||||
|
"DESTINATION": "DESTINO",
|
||||||
|
"METHOD": "MÉTODO",
|
||||||
|
|
||||||
"BOTTOM BAR AND MISC - BottomBar.js": "Bottom Bar user profile and miscellaneous dialogs",
|
"BOTTOM BAR AND MISC - BottomBar.js": "Bottom Bar user profile and miscellaneous dialogs",
|
||||||
"Stats For Nerds": "Estadísticas para nerds",
|
"Stats For Nerds": "Estadísticas para nerds",
|
||||||
|
@ -95,7 +95,6 @@
|
|||||||
"I want to": "Nahi dut",
|
"I want to": "Nahi dut",
|
||||||
"Select Order Type": "Aukeratu eskaera mota",
|
"Select Order Type": "Aukeratu eskaera mota",
|
||||||
"ANY_type": "EDOZEIN",
|
"ANY_type": "EDOZEIN",
|
||||||
"ANY_currency": "EDOZEIN",
|
|
||||||
"BUY": "EROSI",
|
"BUY": "EROSI",
|
||||||
"SELL": "SALDU",
|
"SELL": "SALDU",
|
||||||
"and receive": "eta jaso",
|
"and receive": "eta jaso",
|
||||||
|
@ -92,8 +92,7 @@
|
|||||||
"Buyer": "Acquirente",
|
"Buyer": "Acquirente",
|
||||||
"I want to": "Voglio",
|
"I want to": "Voglio",
|
||||||
"Select Order Type": "Selezione il tipo di ordine",
|
"Select Order Type": "Selezione il tipo di ordine",
|
||||||
"ANY_type": "QUALUNQUE",
|
"ANY": "QUALUNQUE",
|
||||||
"ANY_currency": "QUALUNQUE",
|
|
||||||
"BUY": "COMPRA",
|
"BUY": "COMPRA",
|
||||||
"SELL": "VENDI",
|
"SELL": "VENDI",
|
||||||
"and receive": "e ricevi",
|
"and receive": "e ricevi",
|
||||||
|
@ -76,8 +76,7 @@
|
|||||||
"Buyer": "Kupujący",
|
"Buyer": "Kupujący",
|
||||||
"I want to": "chcę",
|
"I want to": "chcę",
|
||||||
"Select Order Type": "Wybierz typ zamówienia",
|
"Select Order Type": "Wybierz typ zamówienia",
|
||||||
"ANY_type": "KAŻDY",
|
"ANY": "KAŻDY",
|
||||||
"ANY_currency": "KAŻDY",
|
|
||||||
"BUY": "KUPIĆ",
|
"BUY": "KUPIĆ",
|
||||||
"SELL": "SPRZEDAĆ",
|
"SELL": "SPRZEDAĆ",
|
||||||
"and receive": "i odbierz",
|
"and receive": "i odbierz",
|
||||||
|
@ -88,8 +88,7 @@
|
|||||||
"Buyer": "Comprador",
|
"Buyer": "Comprador",
|
||||||
"I want to": "Eu quero",
|
"I want to": "Eu quero",
|
||||||
"Select Order Type": "Selecione o tipo de ordem",
|
"Select Order Type": "Selecione o tipo de ordem",
|
||||||
"ANY_type": "QUALQUER",
|
"ANY": "QUALQUER",
|
||||||
"ANY_currency": "QUALQUER",
|
|
||||||
"BUY": "COMPRAR",
|
"BUY": "COMPRAR",
|
||||||
"SELL": "VENDER",
|
"SELL": "VENDER",
|
||||||
"and receive": "e receber",
|
"and receive": "e receber",
|
||||||
|
@ -92,8 +92,7 @@
|
|||||||
"Buyer": "Покупатель",
|
"Buyer": "Покупатель",
|
||||||
"I want to": "Я хочу",
|
"I want to": "Я хочу",
|
||||||
"Select Order Type": "Выбрать тип ордера",
|
"Select Order Type": "Выбрать тип ордера",
|
||||||
"ANY_type": "Любой тип",
|
"ANY": "Любую валюту",
|
||||||
"ANY_currency": "Любую валюту",
|
|
||||||
"BUY": "Купить",
|
"BUY": "Купить",
|
||||||
"SELL": "Продать",
|
"SELL": "Продать",
|
||||||
"and receive": "и получить",
|
"and receive": "и получить",
|
||||||
|
@ -92,8 +92,7 @@
|
|||||||
"Buyer": "Köpare",
|
"Buyer": "Köpare",
|
||||||
"I want to": "Jag vill",
|
"I want to": "Jag vill",
|
||||||
"Select Order Type": "Välj ordertyp",
|
"Select Order Type": "Välj ordertyp",
|
||||||
"ANY_type": "ALLA",
|
"ANY": "ALLA",
|
||||||
"ANY_currency": "ALLA",
|
|
||||||
"BUY": "Köp",
|
"BUY": "Köp",
|
||||||
"SELL": "Sälj",
|
"SELL": "Sälj",
|
||||||
"and receive": "och ta emot",
|
"and receive": "och ta emot",
|
||||||
|
@ -94,8 +94,7 @@
|
|||||||
"Buyer": "ผู้ซื้อ",
|
"Buyer": "ผู้ซื้อ",
|
||||||
"I want to": "ฉันต้องการ",
|
"I want to": "ฉันต้องการ",
|
||||||
"Select Order Type": "เลือกชนิดรายการ",
|
"Select Order Type": "เลือกชนิดรายการ",
|
||||||
"ANY_type": "ANY",
|
"ANY": "ANY",
|
||||||
"ANY_currency": "ANY",
|
|
||||||
"BUY": "ซื้อ",
|
"BUY": "ซื้อ",
|
||||||
"SELL": "ขาย",
|
"SELL": "ขาย",
|
||||||
"and receive": "และรับเงินเป็น",
|
"and receive": "และรับเงินเป็น",
|
||||||
|
Loading…
Reference in New Issue
Block a user