diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index cfa5e921..f9a4fd32 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,7 +16,7 @@ on: branches: [ "main" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + # branches: [ "main" ] schedule: - cron: '39 10 * * 2' diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 00000000..b84124aa --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,39 @@ +name: Lint + +on: + # Trigger the workflow on push or pull request, + # but only for the main branch + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + run-linters: + name: Run linters + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: 'Setup node' + uses: actions/setup-node@v3 + with: + node-version: 16.17.0 + cache: npm + cache-dependency-path: frontend/package-lock.json + + # ESLint and Prettier must be in `package.json` + - name: 'Install NPM Dependencies' + run: | + cd frontend + npm install -f + + - name: Run linters + uses: wearerequired/lint-action@v2 + with: + prettier: true + prettier_dir: frontend \ No newline at end of file diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js index 51895943..e4b3330b 100644 --- a/frontend/src/components/App.js +++ b/frontend/src/components/App.js @@ -1,13 +1,13 @@ -import React, { Component , Suspense } from "react"; +import React, { Component, Suspense } from 'react'; import ReactDOM from 'react-dom/client'; -import HomePage from "./HomePage"; -import { CssBaseline, IconButton , Link} from "@mui/material"; +import HomePage from './HomePage'; +import { CssBaseline, IconButton, Link } from '@mui/material'; import { ThemeProvider, createTheme } from '@mui/material/styles'; -import UnsafeAlert from "./UnsafeAlert"; -import { LearnDialog } from "./Dialogs"; +import UnsafeAlert from './UnsafeAlert'; +import { LearnDialog } from './Dialogs'; -import { I18nextProvider } from "react-i18next"; -import i18n from "./i18n"; +import { I18nextProvider } from 'react-i18next'; +import i18n from './i18n'; //Icons import DarkModeIcon from '@mui/icons-material/DarkMode'; @@ -24,63 +24,77 @@ export default class App extends Component { dark: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches, expandedSettings: false, openLearn: false, - theme: {typography: { fontSize: 14 }}, - } + theme: { typography: { fontSize: 14 } }, + }; } - lightTheme = createTheme ({}); + lightTheme = createTheme({}); darkTheme = createTheme({ palette: { mode: 'dark', background: { - default: "#070707" + default: '#070707', }, }, }); onSettingsClick = () => { this.setState({ - expandedSettings: ! this.state.expandedSettings - }) - } + expandedSettings: !this.state.expandedSettings, + }); + }; onZoomClick = (direction) => { let zoomChange; - direction === "out" ? zoomChange = -1 : zoomChange = 1; - this.setState(({theme}) => ({ - theme: { + direction === 'out' ? (zoomChange = -1) : (zoomChange = 1); + this.setState(({ theme }) => ({ + theme: { ...theme, typography: { fontSize: this.state.theme.typography.fontSize + zoomChange, }, - } + }, })); - } + }; render() { return ( - - - - - this.setState({openLearn:false})}/> - this.setState({openLearn:true})}> - this.setState({dark:!this.state.dark})}> - {this.state.dark ? :} - - this.setState({openLearn:true})}> - - - - + + + + + this.setState({ openLearn: false })} + /> + this.setState({ openLearn: true })} + > + + + this.setState({ dark: !this.state.dark })} + > + {this.state.dark ? : } + + this.setState({ openLearn: true })} + > + + + + + + ); } } -const root = ReactDOM.createRoot( - document.getElementById("app") -); +const root = ReactDOM.createRoot(document.getElementById('app')); root.render(); diff --git a/frontend/src/components/AutocompletePayments.js b/frontend/src/components/AutocompletePayments.js index 73c43c66..b688e38b 100644 --- a/frontend/src/components/AutocompletePayments.js +++ b/frontend/src/components/AutocompletePayments.js @@ -1,30 +1,30 @@ -import React, { useState } from "react"; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { useAutocomplete } from '@mui/base/AutocompleteUnstyled'; import { styled } from '@mui/material/styles'; -import { Button, Tooltip } from "@mui/material"; -import { paymentMethods, swapDestinations } from "./payment-methods/Methods"; +import { Button, Tooltip } from '@mui/material'; +import { paymentMethods, swapDestinations } from './payment-methods/Methods'; // Icons import DashboardCustomizeIcon from '@mui/icons-material/DashboardCustomize'; import AddIcon from '@mui/icons-material/Add'; -import PaymentIcon from './payment-methods/Icons' +import PaymentIcon from './payment-methods/Icons'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; const Root = styled('div')( ({ theme }) => ` - 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)'}; font-size: 14px; `, ); const Label = styled('label')( - ({ theme , error}) => ` - color: ${theme.palette.mode === 'dark' ? (error? '#f44336': '#cfcfcf') : (error? '#dd0000':'#717171')}; + ({ theme, error }) => ` + color: ${ + theme.palette.mode === 'dark' ? (error ? '#f44336' : '#cfcfcf') : error ? '#dd0000' : '#717171' + }; align: center; padding: 0 0 4px; line-height: 1.5; f44336 @@ -34,11 +34,13 @@ const Label = styled('label')( ); const InputWrapper = styled('div')( - ({ theme , error}) => ` + ({ theme, error }) => ` width: 244px; min-height: 44px; max-height: 124px; - border: 1px solid ${theme.palette.mode === 'dark' ? (error? '#f44336': '#434343') : (error? '#dd0000':'#c4c4c4')}; + border: 1px solid ${ + theme.palette.mode === 'dark' ? (error ? '#f44336' : '#434343') : error ? '#dd0000' : '#c4c4c4' + }; background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'}; border-radius: 4px; padding: 1px; @@ -47,18 +49,32 @@ const InputWrapper = styled('div')( overflow-y:auto; &:hover { - border-color: ${theme.palette.mode === 'dark' ? (error? '#f44336':'#ffffff') : (error? '#dd0000' :'#2f2f2f')}; + border-color: ${ + theme.palette.mode === 'dark' + ? error + ? '#f44336' + : '#ffffff' + : error + ? '#dd0000' + : '#2f2f2f' + }; } &.focused { - border: 2px solid ${theme.palette.mode === 'dark' ? (error? '#f44336':'#90caf9') : (error? '#dd0000' :'#1976d2')}; + border: 2px solid ${ + theme.palette.mode === 'dark' + ? error + ? '#f44336' + : '#90caf9' + : error + ? '#dd0000' + : '#1976d2' + }; } & input { 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; box-sizing: border-box; padding: 4px 6px; @@ -78,10 +94,10 @@ function Tag(props) { const { label, icon, onDelete, ...other } = props; return (
-
- -
- {label} +
+ +
+ {label}
); @@ -100,9 +116,7 @@ const StyledTag = styled(Tag)( height: 34px; margin: 2px; line-height: 22px; - 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-radius: 2px; box-sizing: content-box; @@ -131,7 +145,7 @@ const StyledTag = styled(Tag)( ); const ListHeader = styled('span')( - ({ theme }) => ` + ({ theme }) => ` color: ${theme.palette.mode === 'dark' ? '#90caf9' : '#1976d2'}; align: left; line-height:10px; @@ -202,83 +216,119 @@ export default function AutocompletePayments(props) { getOptionProps, groupedOptions, value, - focused="true", + focused = 'true', setAnchorEl, } = useAutocomplete({ - sx: {width:'200px', align:'left'}, + sx: { width: '200px', align: 'left' }, id: 'payment-methods', multiple: true, - options: props.optionsType=="fiat" ? paymentMethods : swapDestinations, + options: props.optionsType == 'fiat' ? paymentMethods : swapDestinations, 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)), - onClose: () => (setVal(() => "")), + onClose: () => setVal(() => ''), }); const [val, setVal] = useState(); - function optionsToString(newValue){ + function optionsToString(newValue) { var str = ''; var arrayLength = newValue.length; - for (var i = 0; i < arrayLength; i++) { - str += newValue[i].name + ' '; - } + for (var i = 0; i < arrayLength; i++) { + str += newValue[i].name + ' '; + } return str.slice(0, -1); } - function handleAddNew(inputProps){ - paymentMethods.push({name: inputProps.value, icon:'custom'}) - var a = value.push({name: inputProps.value, icon:'custom'}); - setVal(() => ""); + function handleAddNew(inputProps) { + paymentMethods.push({ name: inputProps.value, icon: 'custom' }); + var a = value.push({ name: inputProps.value, icon: 'custom' }); + setVal(() => ''); - if(a || a == null){props.onAutocompleteChange(optionsToString(value))} - return false - }; + if (a || a == null) { + props.onAutocompleteChange(optionsToString(value)); + } + return false; + } return ( -
- -
- - +
+ +
+ + {value.map((option, index) => ( ))} - +
{groupedOptions.length > 0 ? ( -
- {props.listHeaderText+" "} -
+
+ + {props.listHeaderText + ' '} {' '} + +
{groupedOptions.map((option, index) => (
  • - -
    +
    + +
  • ))} - {val != null? - (val.length > 2 ? - - :null) - :null} + {val != null ? ( + val.length > 2 ? ( + + ) : null + ) : null}
    - ) : - //Here goes what happens if there is no groupedOptions - (getInputProps().value.length > 0 ? + ) : //Here goes what happens if there is no groupedOptions + getInputProps().value.length > 0 ? ( - + - :null) - } + ) : null} ); -} \ No newline at end of file +} diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index add0e31d..99d09ba9 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -1,526 +1,729 @@ -import React, { Component } from "react"; -import { withTranslation } from "react-i18next"; -import { Badge, Tooltip, Stack, Paper, Button, FormControlLabel, Checkbox, RadioGroup, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, ListItemText, ListItemAvatar, IconButton, ButtonGroup} from "@mui/material"; -import { Link } from 'react-router-dom' +import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; +import { + Badge, + Tooltip, + Stack, + Paper, + Button, + FormControlLabel, + Checkbox, + RadioGroup, + ListItemButton, + Typography, + Grid, + Select, + MenuItem, + FormControl, + FormHelperText, + ListItemText, + ListItemAvatar, + IconButton, + ButtonGroup, +} from '@mui/material'; +import { Link } from 'react-router-dom'; import { DataGrid } from '@mui/x-data-grid'; import currencyDict from '../../static/assets/currencies.json'; -import MediaQuery from 'react-responsive' -import FlagWithProps from './FlagWithProps' -import { pn, amountToString } from "../utils/prettyNumbers"; -import PaymentText from './PaymentText' -import DepthChart from './Charts/DepthChart' -import RobotAvatar from './Robots/RobotAvatar' +import MediaQuery from 'react-responsive'; +import FlagWithProps from './FlagWithProps'; +import { pn, amountToString } from '../utils/prettyNumbers'; +import PaymentText from './PaymentText'; +import DepthChart from './Charts/DepthChart'; +import RobotAvatar from './Robots/RobotAvatar'; // Icons import { BarChart, FormatListBulleted, Refresh } from '@mui/icons-material'; -import { BuySatsCheckedIcon, BuySatsIcon, SellSatsCheckedIcon, SellSatsIcon} from "./Icons"; +import { BuySatsCheckedIcon, BuySatsIcon, SellSatsCheckedIcon, SellSatsIcon } from './Icons'; class BookPage extends Component { constructor(props) { super(props); this.state = { pageSize: 6, - view: 'list' + view: 'list', }; } componentDidMount() { - this.getOrderDetails(2, 0) + this.getOrderDetails(2, 0); } getOrderDetails(type, currency) { - this.props.setAppState({bookLoading: true}); - fetch('/api/book' + '?currency=' + currency + "&type=" + type) + this.props.setAppState({ bookLoading: true }); + fetch('/api/book' + '?currency=' + currency + '&type=' + type) .then((response) => response.json()) - .then((data) => (this.props.setAppState({ - bookNotFound: data.not_found, - bookLoading: false, - bookOrders: data, - }))); + .then((data) => + this.props.setAppState({ + bookNotFound: data.not_found, + bookLoading: false, + bookOrders: data, + }), + ); } - handleRowClick=(e)=>{ + handleRowClick = (e) => { this.props.history.push('/order/' + e); - } + }; - handleCurrencyChange=(e)=>{ + handleCurrencyChange = (e) => { var currency = e.target.value; this.props.setAppState({ currency: currency, bookCurrencyCode: this.getCurrencyCode(currency), - }) - } + }); + }; - getCurrencyCode(val){ + getCurrencyCode(val) { const { t } = this.props; - if (val){ - return val == 0 ? t('ANY_currency') : currencyDict[val.toString()] - }else{ - return t('ANY_currency') + if (val) { + return val == 0 ? t('ANY_currency') : currencyDict[val.toString()]; + } else { + return t('ANY_currency'); } } - // Colors for the status badges - statusBadgeColor(status){ - if(status=='Active'){return("success")} - if(status=='Seen recently'){return("warning")} - if(status=='Inactive'){return('error')} - } - - dataGridLocaleText=()=> { - const { t } = this.props; - return { - MuiTablePagination:{labelRowsPerPage:t('Orders per page:')}, - noRowsLabel:t('No rows'), - noResultsOverlayLabel:t('No results found.'), - errorOverlayDefaultLabel:t('An error occurred.'), - toolbarColumns:t('Columns'), - toolbarColumnsLabel:t('Select columns'), - columnsPanelTextFieldLabel:t('Find column'), - columnsPanelTextFieldPlaceholder:t('Column title'), - columnsPanelDragIconLabel:t('Reorder column'), - columnsPanelShowAllButton:t('Show all'), - columnsPanelHideAllButton:t('Hide all'), - filterPanelAddFilter:t('Add filter'), - filterPanelDeleteIconLabel:t('Delete'), - filterPanelLinkOperator:t('Logic operator'), - filterPanelOperators:t('Operator'), // TODO v6: rename to filterPanelOperator - filterPanelOperatorAnd:t('And'), - filterPanelOperatorOr:t('Or'), - filterPanelColumns:t('Columns'), - filterPanelInputLabel:t('Value'), - filterPanelInputPlaceholder:t('Filter value'), - filterOperatorContains:t('contains'), - filterOperatorEquals:t('equals'), - filterOperatorStartsWith:t('starts with'), - filterOperatorEndsWith:t('ends with'), - filterOperatorIs:t('is'), - filterOperatorNot:t('is not'), - filterOperatorAfter:t('is after'), - filterOperatorOnOrAfter:t('is on or after'), - filterOperatorBefore:t('is before'), - filterOperatorOnOrBefore:t('is on or before'), - filterOperatorIsEmpty:t('is empty'), - filterOperatorIsNotEmpty:t('is not empty'), - filterOperatorIsAnyOf:t('is any of'), - filterValueAny:t('any'), - filterValueTrue:t('true'), - filterValueFalse:t('false'), - columnMenuLabel:t('Menu'), - columnMenuShowColumns:t('Show columns'), - columnMenuFilter:t('Filter'), - columnMenuHideColumn:t('Hide'), - columnMenuUnsort:t('Unsort'), - columnMenuSortAsc:t('Sort by ASC'), - columnMenuSortDesc:t('Sort by DESC'), - columnHeaderFiltersLabel:t('Show filters'), - columnHeaderSortIconLabel:t('Sort'), - booleanCellTrueLabel:t('yes'), - booleanCellFalseLabel:t('no'), + statusBadgeColor(status) { + if (status == 'Active') { + return 'success'; + } + if (status == 'Seen recently') { + return 'warning'; + } + if (status == 'Inactive') { + return 'error'; } } - bookListTableDesktop=()=>{ + dataGridLocaleText = () => { const { t } = this.props; - return ( -
    - - (order.type == this.props.type || this.props.type == 2) && - (order.currency == this.props.currency || this.props.currency == 0) - ) - } - loading={this.props.bookLoading} - columns={[ - // { field: 'id', headerName: 'ID', width: 40 }, - { - field: 'maker_nick', headerName: t("Robot"), width: 240, - renderCell: (params) => { - return ( - - - - - - - ); - } - }, - { field: 'type', headerName: t("Is"), width: 60, renderCell: (params) => params.row.type ? t("Seller"): t("Buyer") }, - { field: 'amount', headerName: t("Amount"), type: 'number', width: 90, - renderCell: (params) => { - return ( -
    - {amountToString(params.row.amount,params.row.has_range, params.row.min_amount, params.row.max_amount)} -
    - ) - } - }, - { field: 'currency', headerName: t("Currency"), width: 100, - renderCell: (params) => { - const currencyCode = this.getCurrencyCode(params.row.currency) - return ( -
    - {currencyCode + " "} - -
    - ) - } - }, - { field: 'payment_method', headerName: t("Payment Method"), width: 180 , - renderCell: (params) => { - return ( -
    - )} - }, - { field: 'price', headerName: t("Price"), type: 'number', width: 140, - renderCell: (params) => {return ( -
    {pn(params.row.price) + " " + params.row.currency + "/BTC" }
    - )} - }, - { field: 'premium', headerName: t("Premium"), type: 'number', width: 100, - renderCell: (params) => {return ( -
    {parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }
    - )} - }]} + return { + MuiTablePagination: { labelRowsPerPage: t('Orders per page:') }, + noRowsLabel: t('No rows'), + noResultsOverlayLabel: t('No results found.'), + errorOverlayDefaultLabel: t('An error occurred.'), + toolbarColumns: t('Columns'), + toolbarColumnsLabel: t('Select columns'), + columnsPanelTextFieldLabel: t('Find column'), + columnsPanelTextFieldPlaceholder: t('Column title'), + columnsPanelDragIconLabel: t('Reorder column'), + columnsPanelShowAllButton: t('Show all'), + columnsPanelHideAllButton: t('Hide all'), + filterPanelAddFilter: t('Add filter'), + filterPanelDeleteIconLabel: t('Delete'), + filterPanelLinkOperator: t('Logic operator'), + filterPanelOperators: t('Operator'), // TODO v6: rename to filterPanelOperator + filterPanelOperatorAnd: t('And'), + filterPanelOperatorOr: t('Or'), + filterPanelColumns: t('Columns'), + filterPanelInputLabel: t('Value'), + filterPanelInputPlaceholder: t('Filter value'), + filterOperatorContains: t('contains'), + filterOperatorEquals: t('equals'), + filterOperatorStartsWith: t('starts with'), + filterOperatorEndsWith: t('ends with'), + filterOperatorIs: t('is'), + filterOperatorNot: t('is not'), + filterOperatorAfter: t('is after'), + filterOperatorOnOrAfter: t('is on or after'), + filterOperatorBefore: t('is before'), + filterOperatorOnOrBefore: t('is on or before'), + filterOperatorIsEmpty: t('is empty'), + filterOperatorIsNotEmpty: t('is not empty'), + filterOperatorIsAnyOf: t('is any of'), + filterValueAny: t('any'), + filterValueTrue: t('true'), + filterValueFalse: t('false'), + columnMenuLabel: t('Menu'), + columnMenuShowColumns: t('Show columns'), + columnMenuFilter: t('Filter'), + columnMenuHideColumn: t('Hide'), + columnMenuUnsort: t('Unsort'), + columnMenuSortAsc: t('Sort by ASC'), + columnMenuSortDesc: t('Sort by DESC'), + columnHeaderFiltersLabel: t('Show filters'), + columnHeaderSortIconLabel: t('Sort'), + booleanCellTrueLabel: t('yes'), + booleanCellFalseLabel: t('no'), + }; + }; - components={{ - NoRowsOverlay: () => ( - -
    - {this.NoOrdersFound()} - - ), - NoResultsOverlay: () => ( - - {t("Filter has no results")} - - ) - }} - pageSize={this.props.bookLoading ? 0 : this.state.pageSize} - rowsPerPageOptions={[0,6,20,50]} - onPageSizeChange={(newPageSize) => this.setState({pageSize:newPageSize})} - onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. - /> -
    - ); - } - - bookListTablePhone=()=>{ + bookListTableDesktop = () => { const { t } = this.props; return (
    - - (order.type == this.props.type || this.props.type == 2) && - (order.currency == this.props.currency || this.props.currency == 0) - ) - } - columns={[ - // { field: 'id', headerName: 'ID', width: 40 }, - { field: 'maker_nick', headerName: t("Robot"), width: 64, - renderCell: (params) => { - return ( -
    - -
    - ) - } - }, - { field: 'amount', headerName: t("Amount"), type: 'number', width: 84, - renderCell: (params) => {return ( - -
    - {amountToString(params.row.amount, params.row.has_range, params.row.min_amount, params.row.max_amount)} -
    -
    - )} - }, - { field: 'currency', headerName: t("Currency"), width: 85, - renderCell: (params) => { - const currencyCode = this.getCurrencyCode(params.row.currency) - return ( -
    - {currencyCode + " "} - -
    - ) - } - }, - { field: 'payment_method', headerName: t("Payment Method"), width: 180, hide:'true'}, - { field: 'payment_icons', headerName: t("Pay"), width: 75 , - renderCell: (params) => {return ( -
    - )} - }, - { field: 'price', headerName: t("Price"), type: 'number', width: 140, hide:'true', - renderCell: (params) => {return ( -
    {pn(params.row.price) + " " + params.row.currency+ "/BTC" }
    - )} - }, - { field: 'premium', headerName: t("Premium"), type: 'number', width: 85, - renderCell: (params) => {return ( - -
    {parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }
    -
    - )} - }]} - - components={{ - NoRowsOverlay: () => ( - -
    - {this.NoOrdersFound()} - - ), - NoResultsOverlay: () => ( - - {t("Local filter returns no result")} - - ) - }} - pageSize={this.props.bookLoading ? 0 : this.state.pageSize} - rowsPerPageOptions={[0,6,20,50]} - onPageSizeChange={(newPageSize) => this.setState({pageSize:newPageSize})} - onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. - - /> -
    + + (order.type == this.props.type || this.props.type == 2) && + (order.currency == this.props.currency || this.props.currency == 0), + )} + loading={this.props.bookLoading} + columns={[ + // { field: 'id', headerName: 'ID', width: 40 }, + { + field: 'maker_nick', + headerName: t('Robot'), + width: 240, + renderCell: (params) => { + return ( + + + + + + + ); + }, + }, + { + field: 'type', + headerName: t('Is'), + width: 60, + renderCell: (params) => (params.row.type ? t('Seller') : t('Buyer')), + }, + { + field: 'amount', + headerName: t('Amount'), + type: 'number', + width: 90, + renderCell: (params) => { + return ( +
    + {amountToString( + params.row.amount, + params.row.has_range, + params.row.min_amount, + params.row.max_amount, + )} +
    + ); + }, + }, + { + field: 'currency', + headerName: t('Currency'), + width: 100, + renderCell: (params) => { + const currencyCode = this.getCurrencyCode(params.row.currency); + return ( +
    + {currencyCode + ' '} + +
    + ); + }, + }, + { + field: 'payment_method', + headerName: t('Payment Method'), + width: 180, + renderCell: (params) => { + return ( +
    + +
    + ); + }, + }, + { + field: 'price', + headerName: t('Price'), + type: 'number', + width: 140, + renderCell: (params) => { + return ( +
    + {pn(params.row.price) + ' ' + params.row.currency + '/BTC'} +
    + ); + }, + }, + { + field: 'premium', + headerName: t('Premium'), + type: 'number', + width: 100, + renderCell: (params) => { + return ( +
    + {parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'} +
    + ); + }, + }, + ]} + components={{ + NoRowsOverlay: () => ( + +
    + {this.NoOrdersFound()} + + ), + NoResultsOverlay: () => ( + + {t('Filter has no results')} + + ), + }} + pageSize={this.props.bookLoading ? 0 : this.state.pageSize} + rowsPerPageOptions={[0, 6, 20, 50]} + onPageSizeChange={(newPageSize) => this.setState({ pageSize: newPageSize })} + onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. + /> +
    ); - } + }; - handleTypeChange=(buyChecked, sellChecked)=>{ - this.props.setAppState({buyChecked: buyChecked, sellChecked: sellChecked}) + bookListTablePhone = () => { + const { t } = this.props; + return ( +
    + + (order.type == this.props.type || this.props.type == 2) && + (order.currency == this.props.currency || this.props.currency == 0), + )} + columns={[ + // { field: 'id', headerName: 'ID', width: 40 }, + { + field: 'maker_nick', + headerName: t('Robot'), + width: 64, + renderCell: (params) => { + return ( +
    + +
    + ); + }, + }, + { + field: 'amount', + headerName: t('Amount'), + type: 'number', + width: 84, + renderCell: (params) => { + return ( + +
    + {amountToString( + params.row.amount, + params.row.has_range, + params.row.min_amount, + params.row.max_amount, + )} +
    +
    + ); + }, + }, + { + field: 'currency', + headerName: t('Currency'), + width: 85, + renderCell: (params) => { + const currencyCode = this.getCurrencyCode(params.row.currency); + return ( +
    + {currencyCode + ' '} + +
    + ); + }, + }, + { field: 'payment_method', headerName: t('Payment Method'), width: 180, hide: 'true' }, + { + field: 'payment_icons', + headerName: t('Pay'), + width: 75, + renderCell: (params) => { + return ( +
    + +
    + ); + }, + }, + { + field: 'price', + headerName: t('Price'), + type: 'number', + width: 140, + hide: 'true', + renderCell: (params) => { + return ( +
    + {pn(params.row.price) + ' ' + params.row.currency + '/BTC'} +
    + ); + }, + }, + { + field: 'premium', + headerName: t('Premium'), + type: 'number', + width: 85, + renderCell: (params) => { + return ( + +
    + {parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'} +
    +
    + ); + }, + }, + ]} + components={{ + NoRowsOverlay: () => ( + +
    + {this.NoOrdersFound()} + + ), + NoResultsOverlay: () => ( + + {t('Local filter returns no result')} + + ), + }} + pageSize={this.props.bookLoading ? 0 : this.state.pageSize} + rowsPerPageOptions={[0, 6, 20, 50]} + onPageSizeChange={(newPageSize) => this.setState({ pageSize: newPageSize })} + onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. + /> +
    + ); + }; + + handleTypeChange = (buyChecked, sellChecked) => { + this.props.setAppState({ buyChecked: buyChecked, sellChecked: sellChecked }); if (buyChecked & sellChecked || !buyChecked & !sellChecked) { - var type = 2 + var type = 2; } else if (buyChecked) { - var type = 1 + var type = 1; } else if (sellChecked) { - var type = 0 + var type = 0; } - this.props.setAppState({type: type}) - } + this.props.setAppState({ type: type }); + }; - handleClickBuy=(e)=>{ - var buyChecked = e.target.checked - var sellChecked = this.props.sellChecked + handleClickBuy = (e) => { + var buyChecked = e.target.checked; + var sellChecked = this.props.sellChecked; this.handleTypeChange(buyChecked, sellChecked); - } + }; - handleClickView=()=>{ - this.setState({ view: this.state.view == 'depth' ? 'list' : 'depth' }) - } + handleClickView = () => { + this.setState({ view: this.state.view == 'depth' ? 'list' : 'depth' }); + }; - handleClickSell=(e)=>{ - var buyChecked = this.props.buyChecked - var sellChecked = e.target.checked + handleClickSell = (e) => { + var buyChecked = this.props.buyChecked; + var sellChecked = e.target.checked; this.handleTypeChange(buyChecked, sellChecked); - } + }; - NoOrdersFound=()=>{ + NoOrdersFound = () => { const { t } = this.props; - return( - - - - {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}) - } + return ( + + + + {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, + })} -
    +
    - + - - {t("Be the first one to create an order")} -
    -
    -
    + + {t('Be the first one to create an order')} +
    +
    +
    - ) - } + ); + }; mainView = () => { - if (this.props.bookNotFound) { return this.NoOrdersFound() } + if (this.props.bookNotFound) { + return this.NoOrdersFound(); + } - const components = this.state.view == 'depth' ? [ - , - - ] : [ - this.bookListTableDesktop(), - this.bookListTablePhone() - ] + const components = + this.state.view == 'depth' + ? [ + , + , + ] + : [this.bookListTableDesktop(), this.bookListTablePhone()]; return ( <> {/* Desktop */} - -
    - {components[0]} -
    + +
    {components[0]}
    {/* Smartphone */} - -
    - {components[1]} -
    + +
    {components[1]}
    - ) - } + ); + }; getTitle = () => { const { t } = this.props; if (this.state.view == 'list') { - 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") + 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") + return t('Depth chart'); } - } + }; render() { const { t } = this.props; - return ( - + return ( + + this.setState({ loading: true }) & this.getOrderDetails(2, 0)} + > + + - this.setState({loading: true}) & this.getOrderDetails(2, 0)}> - - - - - - - {t("I want to")} - - -
    - } checkedIcon={}/>} - label={ -
    - {this.props.buyChecked ? - {t("Buy")} - : - {t("Buy")} - } -
    - } - labelPlacement="bottom" - checked={this.props.buyChecked} - onChange={this.handleClickBuy} - /> -
    - } checkedIcon={}/>} - label={ -
    - {this.props.sellChecked ? - {t("Sell")} - : - {t("Sell")} - } -
    - } - labelPlacement="bottom" - checked={this.props.sellChecked} - onChange={this.handleClickSell} - /> -
    -
    -
    - - - - - {this.props.type == 0 ? t("and receive") : (this.props.type == 1 ? t("and pay with") : t("and use") )} - - - - - { this.props.bookNotFound ? <> : - - - {this.getTitle()} - - - } - - {this.mainView()} - - - - { !this.props.bookNotFound ? - <> - - - - : null - } - - - + /> + } + label={ +
    + {this.props.sellChecked ? ( + + {t('Sell')} + + ) : ( + + {t('Sell')} + + )} +
    + } + labelPlacement='bottom' + checked={this.props.sellChecked} + onChange={this.handleClickSell} + /> + +
    + + + + + {this.props.type == 0 + ? t('and receive') + : this.props.type == 1 + ? t('and pay with') + : t('and use')} + + + + + {this.props.bookNotFound ? ( + <> + ) : ( + + + {this.getTitle()} + + + )} + + {this.mainView()} + + + + {!this.props.bookNotFound ? ( + <> + + + + ) : null} + + + +
    ); } } diff --git a/frontend/src/components/BottomBar.js b/frontend/src/components/BottomBar.js index 152c5c58..f5812565 100644 --- a/frontend/src/components/BottomBar.js +++ b/frontend/src/components/BottomBar.js @@ -1,9 +1,23 @@ -import React, { Component } from 'react' -import { withTranslation } from "react-i18next"; -import { Badge, Tooltip, ListItemAvatar, Avatar,Paper, Grid, IconButton, Select, MenuItem, ListItemText, ListItem, ListItemIcon, ListItemButton } from "@mui/material"; -import MediaQuery from 'react-responsive' -import Flags from 'country-flag-icons/react/3x2' -import { Link as LinkRouter } from "react-router-dom"; +import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; +import { + Badge, + Tooltip, + ListItemAvatar, + Avatar, + Paper, + Grid, + IconButton, + Select, + MenuItem, + ListItemText, + ListItem, + ListItemIcon, + ListItemButton, +} from '@mui/material'; +import MediaQuery from 'react-responsive'; +import Flags from 'country-flag-icons/react/3x2'; +import { Link as LinkRouter } from 'react-router-dom'; // Icons import BarChartIcon from '@mui/icons-material/BarChart'; @@ -15,518 +29,660 @@ import PercentIcon from '@mui/icons-material/Percent'; import PriceChangeIcon from '@mui/icons-material/PriceChange'; // Missing flags -import { CataloniaFlag, BasqueCountryFlag} from "./Icons"; +import { CataloniaFlag, BasqueCountryFlag } from './Icons'; -import { - CommunityDialog, - ExchangeSummaryDialog, - ProfileDialog, - StatsDialog, -} from './Dialogs'; +import { CommunityDialog, ExchangeSummaryDialog, ProfileDialog, StatsDialog } from './Dialogs'; -import { getCookie } from "../utils/cookies"; +import { getCookie } from '../utils/cookies'; class BottomBar extends Component { - constructor(props) { - super(props); - this.state = { - openStatsForNerds: false, - openCommuniy: false, - openExchangeSummary:false, - openClaimRewards: false, - num_public_buy_orders: 0, - num_public_sell_orders: 0, - book_liquidity: 0, - active_robots_today: 0, - maker_fee: 0, - taker_fee: 0, - last_day_nonkyc_btc_premium: 0, - last_day_volume: 0, - lifetime_volume: 0, - robosats_running_commit_hash: '000000000000000', - openProfile: false, - profileShown: false, - alternative_site: 'robosats...', - node_id: '00000000', - showRewards: false, - rewardInvoice: null, - badInvoice: false, - showRewardsSpinner: false, - withdrawn: false, - }; - } - - componentDidMount() { - this.getInfo(); - } - - getInfo() { - this.setState(null) - fetch('/api/info/') - .then((response) => response.json()) - .then((data) => this.setState(data) - & this.props.setAppState({nickname:data.nickname, - loading:false, - activeOrderId: data.active_order_id ? data.active_order_id : null, - lastOrderId: data.last_order_id ? data.last_order_id : null, - referralCode: data.referral_code, - earnedRewards: data.earned_rewards, - lastDayPremium: data.last_day_nonkyc_btc_premium})); - } - - handleClickOpenStatsForNerds = () => { - this.setState({openStatsForNerds: true}); + constructor(props) { + super(props); + this.state = { + openStatsForNerds: false, + openCommuniy: false, + openExchangeSummary: false, + openClaimRewards: false, + num_public_buy_orders: 0, + num_public_sell_orders: 0, + book_liquidity: 0, + active_robots_today: 0, + maker_fee: 0, + taker_fee: 0, + last_day_nonkyc_btc_premium: 0, + last_day_volume: 0, + lifetime_volume: 0, + robosats_running_commit_hash: '000000000000000', + openProfile: false, + profileShown: false, + alternative_site: 'robosats...', + node_id: '00000000', + showRewards: false, + rewardInvoice: null, + badInvoice: false, + showRewardsSpinner: false, + withdrawn: false, }; + } - handleClickCloseStatsForNerds = () => { - this.setState({openStatsForNerds: false}); - }; + componentDidMount() { + this.getInfo(); + } - handleClickOpenCommunity = () => { - this.setState({openCommuniy: true}); - }; - handleClickCloseCommunity = () => { - this.setState({openCommuniy: false}); - }; + getInfo() { + this.setState(null); + fetch('/api/info/') + .then((response) => response.json()) + .then( + (data) => + this.setState(data) & + this.props.setAppState({ + nickname: data.nickname, + loading: false, + activeOrderId: data.active_order_id ? data.active_order_id : null, + lastOrderId: data.last_order_id ? data.last_order_id : null, + referralCode: data.referral_code, + earnedRewards: data.earned_rewards, + lastDayPremium: data.last_day_nonkyc_btc_premium, + }), + ); + } - handleClickOpenProfile = () => { - this.getInfo(); - this.setState({openProfile: true, profileShown: true}); - }; - handleClickCloseProfile = () => { - this.setState({openProfile: false}); - }; + handleClickOpenStatsForNerds = () => { + this.setState({ openStatsForNerds: true }); + }; - handleSubmitInvoiceClicked=(e, rewardInvoice)=>{ - this.setState({ - badInvoice:false, - showRewardsSpinner: true, - }); + handleClickCloseStatsForNerds = () => { + this.setState({ openStatsForNerds: false }); + }; - const requestOptions = { - method: 'POST', - headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, - body: JSON.stringify({ - 'invoice': rewardInvoice, - }), - }; - fetch('/api/reward/', requestOptions) - .then((response) => response.json()) - .then((data) => this.setState({ - badInvoice:data.bad_invoice, + handleClickOpenCommunity = () => { + this.setState({ openCommuniy: true }); + }; + handleClickCloseCommunity = () => { + this.setState({ openCommuniy: false }); + }; + + handleClickOpenProfile = () => { + this.getInfo(); + this.setState({ openProfile: true, profileShown: true }); + }; + handleClickCloseProfile = () => { + this.setState({ openProfile: false }); + }; + + handleSubmitInvoiceClicked = (e, rewardInvoice) => { + this.setState({ + badInvoice: false, + showRewardsSpinner: true, + }); + + const requestOptions = { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, + body: JSON.stringify({ + invoice: rewardInvoice, + }), + }; + fetch('/api/reward/', requestOptions) + .then((response) => response.json()) + .then( + (data) => + this.setState({ + badInvoice: data.bad_invoice, openClaimRewards: data.successful_withdrawal ? false : true, withdrawn: data.successful_withdrawal ? true : false, showRewardsSpinner: false, - }) - & this.props.setAppState({ + }) & + this.props.setAppState({ earnedRewards: data.successful_withdrawal ? 0 : this.props.earnedRewards, - }) - ); - e.preventDefault(); - } + }), + ); + e.preventDefault(); + }; - handleSetStealthInvoice = (wantsStealth) => { - const requestOptions = { - method: 'PUT', - headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')}, - body: JSON.stringify({wantsStealth: wantsStealth}), - }; - fetch('/api/stealth/', requestOptions) - .then((response) => response.json()) - .then((data) => this.props.setAppState({stealthInvoices: data.wantsStealth})); - } + handleSetStealthInvoice = (wantsStealth) => { + const requestOptions = { + method: 'PUT', + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, + body: JSON.stringify({ wantsStealth: wantsStealth }), + }; + fetch('/api/stealth/', requestOptions) + .then((response) => response.json()) + .then((data) => this.props.setAppState({ stealthInvoices: data.wantsStealth })); + }; - getHost(){ - var url = (window.location != window.parent.location) ? this.getHost(document.referrer) : document.location.href; - return url.split('/')[2] - } + getHost() { + var url = + window.location != window.parent.location + ? this.getHost(document.referrer) + : document.location.href; + return url.split('/')[2]; + } - showProfileButton = () =>{ - return (this.props.avatarLoaded && (this.props.token ? getCookie('robot_token')==this.props.token : true ) && (getCookie('sessionid'))) - } + showProfileButton = () => { + return ( + this.props.avatarLoaded && + (this.props.token ? getCookie('robot_token') == this.props.token : true) && + getCookie('sessionid') + ); + }; -bottomBarDesktop =()=>{ + bottomBarDesktop = () => { const { t } = this.props; - var hasRewards = this.props.earnedRewards > 0 ? true: false; - var hasOrder = this.props.activeOrderId > 0 & !this.state.profileShown & this.props.avatarLoaded ? true : false; + var hasRewards = this.props.earnedRewards > 0 ? true : false; + var hasOrder = + (this.props.activeOrderId > 0) & !this.state.profileShown & this.props.avatarLoaded + ? true + : false; const fontSize = this.props.theme.typography.fontSize; const fontSizeFactor = fontSize / 14; // default fontSize is 14 const typographyProps = { - primaryTypographyProps: {fontSize: fontSize}, - secondaryTypographyProps: {fontSize: fontSize * 12/14} - } - return( - - - -
    - - - - 0 & !this.props.profileShown) ? "": null} color="primary"> - this.props.setAppState({avatarLoaded: true}), - }} - src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null} - /> - - - - - -
    -
    - - - - - this.props.setAppState({buyChecked: false, sellChecked: true, type:0}) & this.getInfo()} - to={`/book/`} - component={LinkRouter} > - - - - - - - - - - - this.props.setAppState({buyChecked: true, sellChecked: false, type:1}) & this.getInfo()} - to={`/book/`} - component={LinkRouter} > - - - - - - - - - - - this.getInfo()} - to={`/`} - component={LinkRouter} > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {this.LangSelect()} - - - - - - - - - - - - - - - - - -
    -
    - ) -} - handleChangeLang=(e)=>{ - const { i18n } = this.props; - i18n.changeLanguage(e.target.value) - } - - LangSelect = () => { - const { i18n } = this.props; - const lang = i18n.resolvedLanguage == null ? 'en' : i18n.resolvedLanguage.substring(0,2); - const flagProps = { - width: 20, - height: 20, - }; - - return( - - ) - } - - handleClickOpenExchangeSummary = () => { - // avoid calling getInfo while sessionid not yet set. Temporary fix. - if(getCookie('sessionid')){ - this.getInfo(); - } - this.setState({openExchangeSummary: true}); + primaryTypographyProps: { fontSize: fontSize }, + secondaryTypographyProps: { fontSize: (fontSize * 12) / 14 }, }; - handleClickCloseExchangeSummary = () => { - this.setState({openExchangeSummary: false}); - }; - -bottomBarPhone =()=>{ - const { t } = this.props; - var hasRewards = this.props.earnedRewards > 0 ? true: false; - var hasOrder = this.state.active_order_id > 0 & !this.state.profileShown & this.props.avatarLoaded ? true : false; - return( - - - - -
    - - - 0 & !this.state.profileShown) ? "": null} color="primary"> - this.props.setAppState({avatarLoaded: true}), - }} - src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null} - /> - - - -
    -
    - - - - this.props.setAppState({buyChecked: false, sellChecked: true, type:0}) & this.getInfo()} - to={`/book/`} - component={LinkRouter} > - - - - - - - - - - this.props.setAppState({buyChecked: true, sellChecked: false, type:1}) & this.getInfo()} - to={`/book/`} - component={LinkRouter} > - - - - - - - - - - this.getInfo()} - to={`/`} - component={LinkRouter} > - - - - - - - - - - - - - - - - - - - - {this.LangSelect()} - - - - - - - - - - - - - - - - -
    -
    - ) -} - - render() { - return ( -
    - - - - - - - - - - {this.bottomBarDesktop()} - - - - {this.bottomBarPhone()} - + return ( + + + +
    + + + + 0) & !this.props.profileShown ? '' : null + } + color='primary' + > + this.props.setAppState({ avatarLoaded: true }), + }} + src={ + this.props.nickname + ? window.location.origin + + '/static/assets/avatars/' + + this.props.nickname + + '.png' + : null + } + /> + + + + +
    - ) +
    + + + + + + this.props.setAppState({ buyChecked: false, sellChecked: true, type: 0 }) & + this.getInfo() + } + to={`/book/`} + component={LinkRouter} + > + + + + + + + + + + + + this.props.setAppState({ buyChecked: true, sellChecked: false, type: 1 }) & + this.getInfo() + } + to={`/book/`} + component={LinkRouter} + > + + + + + + + + + + + this.getInfo()} + to={`/`} + component={LinkRouter} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {this.LangSelect()} + + + + + + + + + + + + + + + + +
    +
    + ); + }; + handleChangeLang = (e) => { + const { i18n } = this.props; + i18n.changeLanguage(e.target.value); + }; + + LangSelect = () => { + const { i18n } = this.props; + const lang = i18n.resolvedLanguage == null ? 'en' : i18n.resolvedLanguage.substring(0, 2); + const flagProps = { + width: 20, + height: 20, + }; + + return ( + + ); + }; + + handleClickOpenExchangeSummary = () => { + // avoid calling getInfo while sessionid not yet set. Temporary fix. + if (getCookie('sessionid')) { + this.getInfo(); } + this.setState({ openExchangeSummary: true }); + }; + handleClickCloseExchangeSummary = () => { + this.setState({ openExchangeSummary: false }); + }; + + bottomBarPhone = () => { + const { t } = this.props; + var hasRewards = this.props.earnedRewards > 0 ? true : false; + var hasOrder = + (this.state.active_order_id > 0) & !this.state.profileShown & this.props.avatarLoaded + ? true + : false; + return ( + + + +
    + + + 0) & !this.state.profileShown ? '' : null + } + color='primary' + > + this.props.setAppState({ avatarLoaded: true }), + }} + src={ + this.props.nickname + ? window.location.origin + + '/static/assets/avatars/' + + this.props.nickname + + '.png' + : null + } + /> + + + +
    +
    + + + + + this.props.setAppState({ buyChecked: false, sellChecked: true, type: 0 }) & + this.getInfo() + } + to={`/book/`} + component={LinkRouter} + > + + + + + + + + + + + this.props.setAppState({ buyChecked: true, sellChecked: false, type: 1 }) & + this.getInfo() + } + to={`/book/`} + component={LinkRouter} + > + + + + + + + + + + this.getInfo()} + to={`/`} + component={LinkRouter} + > + + + + + + + + + + + + + + + + + + + + {this.LangSelect()} + + + + + + + + + + + + + + + + +
    +
    + ); + }; + + render() { + return ( +
    + + + + + + + + + {this.bottomBarDesktop()} + + {this.bottomBarPhone()} +
    + ); + } } export default withTranslation()(BottomBar); diff --git a/frontend/src/components/Charts/DepthChart/index.tsx b/frontend/src/components/Charts/DepthChart/index.tsx index b7921317..d00817be 100644 --- a/frontend/src/components/Charts/DepthChart/index.tsx +++ b/frontend/src/components/Charts/DepthChart/index.tsx @@ -1,55 +1,77 @@ -import React, { useEffect, useState } from "react" -import { ResponsiveLine, Serie, Datum, PointTooltipProps, PointMouseHandler, Point, CustomLayer } from '@nivo/line' -import { Box, CircularProgress, Grid, IconButton, MenuItem, Paper, Select, useTheme } from "@mui/material" +import React, { useEffect, useState } from 'react'; +import { + ResponsiveLine, + Serie, + Datum, + PointTooltipProps, + PointMouseHandler, + Point, + CustomLayer, +} from '@nivo/line'; +import { + Box, + CircularProgress, + Grid, + IconButton, + MenuItem, + Paper, + Select, + useTheme, +} from '@mui/material'; import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material'; -import { useTranslation } from "react-i18next"; -import { useHistory } from "react-router-dom" -import { Order } from "../../../models/Order.model"; -import { LimitList } from "../../../models/Limit.model"; -import RobotAvatar from '../../Robots/RobotAvatar' -import { amountToString } from "../../../utils/prettyNumbers"; +import { useTranslation } from 'react-i18next'; +import { useHistory } from 'react-router-dom'; +import { Order } from '../../../models/Order.model'; +import { LimitList } from '../../../models/Limit.model'; +import RobotAvatar from '../../Robots/RobotAvatar'; +import { amountToString } from '../../../utils/prettyNumbers'; import currencyDict from '../../../../static/assets/currencies.json'; -import PaymentText from "../../PaymentText"; -import getNivoScheme from "../NivoScheme" -import median from "../../../utils/match"; +import PaymentText from '../../PaymentText'; +import getNivoScheme from '../NivoScheme'; +import median from '../../../utils/match'; interface DepthChartProps { - bookLoading: boolean - orders: Order[] - lastDayPremium: number | undefined - currency: number - setAppState: (state: object) => void - limits: LimitList - compact?: boolean + bookLoading: boolean; + orders: Order[]; + lastDayPremium: number | undefined; + currency: number; + setAppState: (state: object) => void; + limits: LimitList; + compact?: boolean; } -const DepthChart: React.FC = ({ - bookLoading, orders, lastDayPremium, currency, setAppState, limits, compact +const DepthChart: React.FC = ({ + bookLoading, + orders, + lastDayPremium, + currency, + setAppState, + limits, + compact, }) => { - const { t } = useTranslation() - const history = useHistory() - const theme = useTheme() - const [enrichedOrders, setEnrichedOrders] = useState([]) - const [series, setSeries] = useState([]) - const [xRange, setXRange] = useState(8) - const [xType, setXType] = useState("premium") - const [currencyCode, setCurrencyCode] = useState(1) - const [center, setCenter] = useState(0) + const { t } = useTranslation(); + const history = useHistory(); + const theme = useTheme(); + const [enrichedOrders, setEnrichedOrders] = useState([]); + const [series, setSeries] = useState([]); + const [xRange, setXRange] = useState(8); + const [xType, setXType] = useState('premium'); + const [currencyCode, setCurrencyCode] = useState(1); + const [center, setCenter] = useState(0); useEffect(() => { if (Object.keys(limits).length === 0) { fetch('/api/limits/') .then((response) => response.json()) .then((data) => { - setAppState({ limits: data }) - }) + setAppState({ limits: data }); + }); } - }, []) - + }, []); useEffect(() => { - setCurrencyCode(currency === 0 ? 1 : currency) - }, [currency]) + setCurrencyCode(currency === 0 ? 1 : currency); + }, [currency]); useEffect(() => { if (Object.keys(limits).length > 0) { @@ -57,172 +79,176 @@ const DepthChart: React.FC = ({ // We need to transform all currencies to the same base (ex. USD), we don't have the exchange rate // for EUR -> USD, but we know the rate of both to BTC, so we get advantage of it and apply a // simple rule of three - order.base_amount = (order.price * limits[currencyCode].price) / limits[order.currency].price - return order - }) - setEnrichedOrders(enriched) + order.base_amount = + (order.price * limits[currencyCode].price) / limits[order.currency].price; + return order; + }); + setEnrichedOrders(enriched); } - }, [limits, orders, currencyCode]) + }, [limits, orders, currencyCode]); useEffect(() => { - if (enrichedOrders.length > 0) { - generateSeries() + if (enrichedOrders.length > 0) { + generateSeries(); } - }, [enrichedOrders, xRange]) + }, [enrichedOrders, xRange]); useEffect(() => { - if (xType === 'base_amount') { - const prices: number[] = enrichedOrders.map((order) => order?.base_amount || 0) - setCenter(~~median(prices)) - setXRange(1500) + if (xType === 'base_amount') { + const prices: number[] = enrichedOrders.map((order) => order?.base_amount || 0); + setCenter(~~median(prices)); + setXRange(1500); } else if (lastDayPremium) { - setCenter(lastDayPremium) - setXRange(8) + setCenter(lastDayPremium); + setXRange(8); } - }, [enrichedOrders, xType, lastDayPremium, currencyCode]) + }, [enrichedOrders, xType, lastDayPremium, currencyCode]); const calculateBtc = (order: Order): number => { - const amount = parseInt(order.amount) || order.max_amount - return amount / order.price - } + const amount = parseInt(order.amount) || order.max_amount; + return amount / order.price; + }; - const generateSeries:() => void = () => { - let sortedOrders: Order[] = xType === 'base_amount' ? - enrichedOrders.sort((order1, order2) => (order1?.base_amount || 0) - (order2?.base_amount || 0) ) - : enrichedOrders.sort((order1, order2) => order1.premium - order2.premium ) + const generateSeries: () => void = () => { + let sortedOrders: Order[] = + xType === 'base_amount' + ? enrichedOrders.sort( + (order1, order2) => (order1?.base_amount || 0) - (order2?.base_amount || 0), + ) + : enrichedOrders.sort((order1, order2) => order1.premium - order2.premium); - const sortedBuyOrders: Order[] = sortedOrders.filter((order) => order.type == 0).reverse() - const sortedSellOrders: Order[] = sortedOrders.filter((order) => order.type == 1) + const sortedBuyOrders: Order[] = sortedOrders.filter((order) => order.type == 0).reverse(); + const sortedSellOrders: Order[] = sortedOrders.filter((order) => order.type == 1); - const buySerie: Datum[] = generateSerie(sortedBuyOrders) - const sellSerie: Datum[] = generateSerie(sortedSellOrders) + const buySerie: Datum[] = generateSerie(sortedBuyOrders); + const sellSerie: Datum[] = generateSerie(sortedSellOrders); - const maxX: number = center + xRange - const minX: number = center - xRange + const maxX: number = center + xRange; + const minX: number = center - xRange; setSeries([ { - id: "buy", - data: closeSerie(buySerie, maxX, minX) - }, + id: 'buy', + data: closeSerie(buySerie, maxX, minX), + }, { - id: "sell", - data: closeSerie(sellSerie, minX, maxX) - } - ]) - } + id: 'sell', + data: closeSerie(sellSerie, minX, maxX), + }, + ]); + }; const generateSerie = (orders: Order[]): Datum[] => { - if (!center) { return [] } + if (!center) { + return []; + } - let sumOrders: number = 0 - let serie: Datum[] = [] + let sumOrders: number = 0; + let serie: Datum[] = []; orders.forEach((order) => { - const lastSumOrders = sumOrders - sumOrders += calculateBtc(order) + const lastSumOrders = sumOrders; + sumOrders += calculateBtc(order); const datum: Datum[] = [ - { // Vertical Line - x: xType === 'base_amount' ? order.base_amount : order.premium, - y: lastSumOrders + { + // Vertical Line + x: xType === 'base_amount' ? order.base_amount : order.premium, + y: lastSumOrders, }, - { // Order Point - x: xType === 'base_amount' ? order.base_amount : order.premium, + { + // Order Point + x: xType === 'base_amount' ? order.base_amount : order.premium, y: sumOrders, - order: order - } - ] + order: order, + }, + ]; - serie = [...serie, ...datum] - }) - const inlineSerie = serie.filter((datum: Datum) => { - return (Number(datum.x) > center - xRange) && - (Number(datum.x) < center + xRange) - }) + serie = [...serie, ...datum]; + }); + const inlineSerie = serie.filter((datum: Datum) => { + return Number(datum.x) > center - xRange && Number(datum.x) < center + xRange; + }); - return inlineSerie - } + return inlineSerie; + }; - const closeSerie = (serie: Datum[], limitBottom: number, limitTop: number): Datum[] =>{ - if (serie.length == 0) { return [] } + const closeSerie = (serie: Datum[], limitBottom: number, limitTop: number): Datum[] => { + if (serie.length == 0) { + return []; + } // If the bottom is not 0, exdens the horizontal bottom line if (serie[0].y !== 0) { const startingPoint: Datum = { x: limitBottom, - y: serie[0].y - } - serie.unshift(startingPoint) + y: serie[0].y, + }; + serie.unshift(startingPoint); } // exdens the horizontal top line const endingPoint: Datum = { x: limitTop, - y: serie[serie.length - 1].y - } + y: serie[serie.length - 1].y, + }; - return [...serie, endingPoint] - } + return [...serie, endingPoint]; + }; const centerLine: CustomLayer = (props) => ( - ) + ); - const generateTooltip: React.FunctionComponent = (pointTooltip: PointTooltipProps) => { - const order: Order = pointTooltip.point.data.order + const generateTooltip: React.FunctionComponent = ( + pointTooltip: PointTooltipProps, + ) => { + const order: Order = pointTooltip.point.data.order; return order ? ( - + - + - - - {order.maker_nick} - + + {order.maker_nick} - {amountToString(order.amount, order.has_range, order.min_amount, order.max_amount)} - {' '} + {amountToString( + order.amount, + order.has_range, + order.min_amount, + order.max_amount, + )}{' '} {currencyDict[order.currency]} - @@ -232,69 +258,67 @@ const DepthChart: React.FC = ({ - ) : <> - } + ) : ( + <> + ); + }; const formatAxisX = (value: number): string => { if (xType === 'base_amount') { - return value.toString() + return value.toString(); } - return `${value}%` - } - const formatAxisY = (value: number): string => `${value}BTC` + return `${value}%`; + }; + const formatAxisY = (value: number): string => `${value}BTC`; - const rangeSteps = xType === 'base_amount' ? 200 : 0.5 + const rangeSteps = xType === 'base_amount' ? 200 : 0.5; const handleOnClick: PointMouseHandler = (point: Point) => { history.push('/order/' + point.data?.order?.id); - } - + }; + return bookLoading || !center || enrichedOrders.length < 1 ? ( -
    +
    ) : ( - - - setXType(e.target.value)}> + +
    + {t('Premium')}
    - -
    - {t("Price")} + +
    + {t('Price')}
    - - + + setXRange(xRange + rangeSteps)}> - + {xType === 'base_amount' ? `${center} ${currencyDict[currencyCode]}` : `${center}%`} @@ -306,42 +330,42 @@ const DepthChart: React.FC = ({ - Number(value).toFixed(0)} lineWidth={3} theme={getNivoScheme(theme)} - colors={[theme.palette.secondary.main,theme.palette.primary.main]} + colors={[theme.palette.secondary.main, theme.palette.primary.main]} xScale={{ type: 'linear', min: center - xRange, - max: center + xRange + max: center + xRange, }} layers={['axes', 'areas', 'crosshair', 'lines', centerLine, 'slices', 'mesh']} /> - ) -} + ); +}; -export default DepthChart +export default DepthChart; diff --git a/frontend/src/components/Charts/NivoScheme/index.ts b/frontend/src/components/Charts/NivoScheme/index.ts index 4c34a2eb..4fb90451 100644 --- a/frontend/src/components/Charts/NivoScheme/index.ts +++ b/frontend/src/components/Charts/NivoScheme/index.ts @@ -1,61 +1,61 @@ -import { light } from "@mui/material/styles/createPalette" -import { palette } from "@mui/system" -import { Theme as NivoTheme } from "@nivo/core" -import { Theme as MuiTheme } from './createTheme' +import { light } from '@mui/material/styles/createPalette'; +import { palette } from '@mui/system'; +import { Theme as NivoTheme } from '@nivo/core'; +import { Theme as MuiTheme } from './createTheme'; export const getNivoScheme: (theme: MuiTheme) => NivoTheme = (theme) => { const lightMode = { markers: { - lineColor: "rgb(0, 0, 0)", - lineStrokeWidth: 1 + lineColor: 'rgb(0, 0, 0)', + lineStrokeWidth: 1, }, axis: { ticks: { line: { - strokeWidth: "1", - stroke: "rgb(0, 0, 0)" - } + strokeWidth: '1', + stroke: 'rgb(0, 0, 0)', + }, }, domain: { line: { - strokeWidth: "1", - stroke: "rgb(0, 0, 0)" - } - } - } - } + strokeWidth: '1', + stroke: 'rgb(0, 0, 0)', + }, + }, + }, + }; const darkMode = { markers: { - lineColor: "rgb(255, 255, 255)", - lineStrokeWidth: 1 + lineColor: 'rgb(255, 255, 255)', + lineStrokeWidth: 1, }, axis: { ticks: { text: { - fill: "rgb(255, 255, 255)" + fill: 'rgb(255, 255, 255)', }, line: { - strokeWidth: "1", - stroke: "rgb(255, 255, 255)" - } + strokeWidth: '1', + stroke: 'rgb(255, 255, 255)', + }, }, domain: { line: { - strokeWidth: "1", - stroke: "rgb(255, 255, 255)" - } - } + strokeWidth: '1', + stroke: 'rgb(255, 255, 255)', + }, + }, }, crosshair: { line: { strokeWidth: 1, - stroke: "rgb(255, 255, 255)" - } - } - } + stroke: 'rgb(255, 255, 255)', + }, + }, + }; - return theme.palette.mode === 'dark' ? darkMode : lightMode -} + return theme.palette.mode === 'dark' ? darkMode : lightMode; +}; -export default getNivoScheme +export default getNivoScheme; diff --git a/frontend/src/components/Dialogs/AuditPGP.tsx b/frontend/src/components/Dialogs/AuditPGP.tsx index 49ea6f82..1e5c5a9d 100644 --- a/frontend/src/components/Dialogs/AuditPGP.tsx +++ b/frontend/src/components/Dialogs/AuditPGP.tsx @@ -1,51 +1,52 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, DialogTitle, - Tooltip, + Tooltip, IconButton, TextField, - DialogActions, + DialogActions, DialogContent, DialogContentText, - Button, + Button, Grid, Link, -} from "@mui/material" +} from '@mui/material'; -import { saveAsJson } from "../../utils/saveFile"; -import { copyToClipboard } from "../../utils/clipboard"; +import { saveAsJson } from '../../utils/saveFile'; +import { copyToClipboard } from '../../utils/clipboard'; // Icons import KeyIcon from '@mui/icons-material/Key'; -import ContentCopy from "@mui/icons-material/ContentCopy"; +import ContentCopy from '@mui/icons-material/ContentCopy'; import ForumIcon from '@mui/icons-material/Forum'; import { ExportIcon, NewTabIcon } from '../Icons'; -function CredentialTextfield(props){ - return( - - +function CredentialTextfield(props) { + return ( + + {props.label}} value={props.value} variant='filled' size='small' InputProps={{ - endAdornment: + endAdornment: ( - copyToClipboard(props.value)}> - + copyToClipboard(props.value)}> + - , - }} - /> + + ), + }} + /> - ) + ); } type Props = { @@ -53,15 +54,15 @@ type Props = { onClose: () => void; orderId: number; messages: array; - own_pub_key: string; + own_pub_key: string; own_enc_priv_key: string; peer_pub_key: string; passphrase: string; onClickBack: () => void; -} +}; const AuditPGPDialog = ({ - open, + open, onClose, orderId, messages, @@ -74,103 +75,129 @@ const AuditPGPDialog = ({ const { t } = useTranslation(); return ( - - - {t("Don't trust, verify")} - + + {t("Don't trust, verify")} - {t("Your communication is end-to-end encrypted with OpenPGP. You can verify the privacy of this chat using any tool based on the OpenPGP standard.")} + {t( + 'Your communication is end-to-end encrypted with OpenPGP. You can verify the privacy of this chat using any tool based on the OpenPGP standard.', + )} - - - - + + + - + copiedTitle={t('Copied!')} + /> - + copiedTitle={t('Copied!')} + /> - + copiedTitle={t('Copied!')} + /> - - -
    - - - - - - - - - - + copiedTitle={t('Copied!')} + /> +
    + + + + + + + + +
    - + -
    - ) -} + ); +}; export default AuditPGPDialog; diff --git a/frontend/src/components/Dialogs/Community.tsx b/frontend/src/components/Dialogs/Community.tsx index a1f92720..13be9acb 100644 --- a/frontend/src/components/Dialogs/Community.tsx +++ b/frontend/src/components/Dialogs/Community.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, DialogContent, @@ -12,22 +12,19 @@ import { ListItemButton, Tooltip, Typography, -} from "@mui/material"; +} from '@mui/material'; import SendIcon from '@mui/icons-material/Send'; import GitHubIcon from '@mui/icons-material/GitHub'; import TwitterIcon from '@mui/icons-material/Twitter'; import RedditIcon from '@mui/icons-material/Reddit'; -import Flags from 'country-flag-icons/react/3x2' +import Flags from 'country-flag-icons/react/3x2'; type Props = { isOpen: boolean; handleClickCloseCommunity: () => void; -} +}; -const CommunityDialog = ({ - isOpen, - handleClickCloseCommunity, -}: Props): JSX.Element => { +const CommunityDialog = ({ isOpen, handleClickCloseCommunity }: Props): JSX.Element => { const { t } = useTranslation(); const flagProps = { @@ -35,7 +32,7 @@ const CommunityDialog = ({ height: 30, opacity: 0.85, style: { - filter: "drop-shadow(2px 2px 2px #444444)", + filter: 'drop-shadow(2px 2px 2px #444444)', }, }; @@ -43,136 +40,139 @@ const CommunityDialog = ({ - - {t("Community")} + + {t('Community')} - -

    {t("Support is only offered via public channels. Join our Telegram community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!")}

    + +

    + {t( + 'Support is only offered via public channels. Join our Telegram community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!', + )} +

    - + - + - + - + - + - + - - + + - + - + - + - + - - + + component='a' + target='_blank' + href='https://github.com/Reckless-Satoshi/robosats/issues' + rel='noreferrer' + > - + diff --git a/frontend/src/components/Dialogs/ExchangeSummary.tsx b/frontend/src/components/Dialogs/ExchangeSummary.tsx index 4ba930de..051aaf3b 100644 --- a/frontend/src/components/Dialogs/ExchangeSummary.tsx +++ b/frontend/src/components/Dialogs/ExchangeSummary.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, @@ -11,7 +11,7 @@ import { ListItem, ListItemIcon, Typography, -} from "@mui/material"; +} from '@mui/material'; import InventoryIcon from '@mui/icons-material/Inventory'; import SellIcon from '@mui/icons-material/Sell'; @@ -21,7 +21,7 @@ import PriceChangeIcon from '@mui/icons-material/PriceChange'; import BookIcon from '@mui/icons-material/Book'; import LinkIcon from '@mui/icons-material/Link'; -import { pn } from "../../utils/prettyNumbers"; +import { pn } from '../../utils/prettyNumbers'; type Props = { isOpen: boolean; @@ -34,7 +34,7 @@ type Props = { makerFee: number; takerFee: number; swapFeeRate: number; -} +}; const ExchangeSummaryDialog = ({ isOpen, @@ -50,106 +50,108 @@ const ExchangeSummaryDialog = ({ }: Props): JSX.Element => { const { t } = useTranslation(); if (swapFeeRate === null || swapFeeRate === undefined) { - swapFeeRate = 0 + swapFeeRate = 0; } return ( - {t("Exchange Summary")} + + {t('Exchange Summary')} + - + - + - + - + - + - + - + - + - + - + - + {(makerFee * 100).toFixed(3)}% @@ -157,9 +159,9 @@ const ExchangeSummaryDialog = ({ {(takerFee * 100).toFixed(3)}% @@ -169,19 +171,18 @@ const ExchangeSummaryDialog = ({ - + - diff --git a/frontend/src/components/Dialogs/Info.tsx b/frontend/src/components/Dialogs/Info.tsx index 5f12f3d3..049f2b4c 100644 --- a/frontend/src/components/Dialogs/Info.tsx +++ b/frontend/src/components/Dialogs/Info.tsx @@ -1,19 +1,19 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, - Typography, - Link, - DialogActions, - DialogContent, - Button, + Typography, + Link, + DialogActions, + DialogContent, + Button, Grid, Accordion, AccordionDetails, AccordionSummary, -} from "@mui/material" -import SmoothImage from 'react-smooth-image' -import MediaQuery from 'react-responsive' +} from '@mui/material'; +import SmoothImage from 'react-smooth-image'; +import MediaQuery from 'react-responsive'; // Icons import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; @@ -22,213 +22,292 @@ type Props = { maxAmount: string; open: boolean; onClose: () => void; -} +}; -const InfoDialog = ({ - maxAmount, - open, - onClose, -}: Props): JSX.Element => { +const InfoDialog = ({ maxAmount, open, onClose }: Props): JSX.Element => { const { t } = useTranslation(); return ( + aria-labelledby='info-dialog-title' + aria-describedby='info-dialog-description' + scroll='paper' + > - - - - {t("What is RoboSats?")} - -

    {t("It is a BTC/FIAT peer-to-peer exchange over lightning.")}
    - {t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}

    + + + + {t('What is RoboSats?')} + + +

    + {t('It is a BTC/FIAT peer-to-peer exchange over lightning.')}
    + {t( + 'It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.', + )} +

    -

    {t("RoboSats is an open source project ")} {t("(GitHub).")} -

    -
    +

    + {t('RoboSats is an open source project ')}{' '} + {t('(GitHub).')} +

    +
    +
    + + +
    - - - -
    -
    +
    - {t("What is RoboSats?")} - -

    {t("It is a BTC/FIAT peer-to-peer exchange over lightning.")+" "} {t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}

    + + {t('What is RoboSats?')} + + +

    + {t('It is a BTC/FIAT peer-to-peer exchange over lightning.') + ' '}{' '} + {t( + 'It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.', + )} +

    -

    {t("RoboSats is an open source project ")} {t("(GitHub).")} -

    -
    +

    + {t('RoboSats is an open source project ')}{' '} + {t('(GitHub).')} +

    +
    - }> - - {t("How does it work?")} - + }> + {t('How does it work?')} - -

    {t("AnonymousAlice01 wants to sell bitcoin. She posts a sell order. BafflingBob02 wants to buy bitcoin and he takes Alice's order. Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning hold invoice. RoboSats locks the invoice until Alice confirms she received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, Bob!")}

    -

    {t("At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, RoboSats staff will help resolving the dispute.")} - {t("You can find a step-by-step description of the trade pipeline in ")} - {t("How it works")}. - {" "+t("You can also check the full guide in ")} - {t("How to use")}.

    + +

    + {' '} + {t( + "AnonymousAlice01 wants to sell bitcoin. She posts a sell order. BafflingBob02 wants to buy bitcoin and he takes Alice's order. Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning hold invoice. RoboSats locks the invoice until Alice confirms she received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, Bob!", + )} +

    +

    + {t( + 'At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, RoboSats staff will help resolving the dispute.', + )} + {t('You can find a step-by-step description of the trade pipeline in ')} + + {t('How it works')} + + .{' ' + t('You can also check the full guide in ')} + + {t('How to use')} + + . +

    - }> - - {t("What payment methods are accepted?") - } + }> + {t('What payment methods are accepted?')} - -

    {t("All of them as long as they are fast. You can write down your preferred payment method(s). You will have to match with a peer who also accepts that method. The step to exchange fiat has a expiry time of 24 hours before a dispute is automatically open. We highly recommend using instant fiat payment rails.")}

    + +

    + {t( + 'All of them as long as they are fast. You can write down your preferred payment method(s). You will have to match with a peer who also accepts that method. The step to exchange fiat has a expiry time of 24 hours before a dispute is automatically open. We highly recommend using instant fiat payment rails.', + )}{' '} +

    - }> - - {t("Are there trade limits?")} - + }> + {t('Are there trade limits?')} - -

    {t("Maximum single trade size is {{maxAmount}} Satoshis to minimize lightning routing failure. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously in different browsers (remember to back up your robot tokens!).", {maxAmount: maxAmount})}

    + +

    + {t( + 'Maximum single trade size is {{maxAmount}} Satoshis to minimize lightning routing failure. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously in different browsers (remember to back up your robot tokens!).', + { maxAmount: maxAmount }, + )}{' '} +

    - }> - - {t("What are the fees?")} - + }> + {t('What are the fees?')} - -

    {t("RoboSats total fee for an order is {{tradeFee}}%. This fee is split to be covered by both: the order maker ({{makerFee}}%) and the order taker ({{takerFee}}%). In case an onchain address is used to received the Sats a variable swap fee applies. Check the exchange details by tapping on the bottom bar icon to see the current swap fee.",{tradeFee:"0.2", makerFee:"0.025", takerFee: "0.175"})}

    -

    {t("Be aware your fiat payment provider might charge extra fees. In any case, the buyer bears the costs of sending fiat. That includes banking charges, transfer fees and foreign exchange spreads. The seller must receive exactly the amount stated in the order details.")}

    + +

    + {t( + 'RoboSats total fee for an order is {{tradeFee}}%. This fee is split to be covered by both: the order maker ({{makerFee}}%) and the order taker ({{takerFee}}%). In case an onchain address is used to received the Sats a variable swap fee applies. Check the exchange details by tapping on the bottom bar icon to see the current swap fee.', + { tradeFee: '0.2', makerFee: '0.025', takerFee: '0.175' }, + )}{' '} +

    +

    + {t( + 'Be aware your fiat payment provider might charge extra fees. In any case, the buyer bears the costs of sending fiat. That includes banking charges, transfer fees and foreign exchange spreads. The seller must receive exactly the amount stated in the order details.', + )}{' '} +

    - }> - - {t("Is RoboSats private?")} - + }> + {t('Is RoboSats private?')} - -

    {t("RoboSats will never ask you for your name, country or ID. RoboSats does not custody your funds and does not care who you are. RoboSats does not collect or custody any personal data. For best anonymity use Tor Browser and access the .onion hidden service.")}

    -

    {t("Your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.")}

    + +

    + {' '} + {t( + 'RoboSats will never ask you for your name, country or ID. RoboSats does not custody your funds and does not care who you are. RoboSats does not collect or custody any personal data. For best anonymity use Tor Browser and access the .onion hidden service.', + )}{' '} +

    +

    + {t( + 'Your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.', + )}{' '} +

    - }> - - {t("What are the risks?")} - + }> + {t('What are the risks?')} - -

    {t("This is an experimental application, things could go wrong. Trade small amounts!")}

    -

    {t("The seller faces the same charge-back risk as with any other peer-to-peer service. Paypal or credit cards are not recommended.")}

    + +

    + {' '} + {t( + 'This is an experimental application, things could go wrong. Trade small amounts!', + )} +

    +

    + {' '} + {t( + 'The seller faces the same charge-back risk as with any other peer-to-peer service. Paypal or credit cards are not recommended.', + )} +

    - }> - - {t("What is the trust model?")} - + }> + {t('What is the trust model?')} - -

    {t("The buyer and the seller never have to trust each other. Some trust on RoboSats is needed since linking the seller's hold invoice and buyer payment is not atomic (yet). In addition, disputes are solved by the RoboSats staff.")}

    -

    {t("To be totally clear. Trust requirements are minimized. However, there is still one way RoboSats could run away with your satoshis: by not releasing the satoshis to the buyer. It could be argued that such move is not in RoboSats' interest as it would damage the reputation for a small payout. However, you should hesitate and only trade small quantities at a time. For large amounts use an onchain escrow service such as Bisq")}

    -

    {t("You can build more trust on RoboSats by inspecting the source code.")} {t("Project source code")}.

    + +

    + {' '} + {t( + "The buyer and the seller never have to trust each other. Some trust on RoboSats is needed since linking the seller's hold invoice and buyer payment is not atomic (yet). In addition, disputes are solved by the RoboSats staff.", + )} +

    +

    + {' '} + {t( + "To be totally clear. Trust requirements are minimized. However, there is still one way RoboSats could run away with your satoshis: by not releasing the satoshis to the buyer. It could be argued that such move is not in RoboSats' interest as it would damage the reputation for a small payout. However, you should hesitate and only trade small quantities at a time. For large amounts use an onchain escrow service such as Bisq", + )} +

    +

    + {' '} + {t('You can build more trust on RoboSats by inspecting the source code.')}{' '} + + {' '} + {t('Project source code')} + + .{' '} +

    - }> - - {t("What happens if RoboSats suddenly disappears?")} - + }> + {t('What happens if RoboSats suddenly disappears?')} - -

    {t("Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if RoboSats goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if RoboSats disappears. This window is about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels.")}

    + +

    + {' '} + {t( + 'Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if RoboSats goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if RoboSats disappears. This window is about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels.', + )} +

    - }> - - {t("Is RoboSats legal in my country?")} - + }> + {t('Is RoboSats legal in my country?')} - -

    {t("In many countries using RoboSats is no different than using Ebay or Craiglist. Your regulation may vary. It is your responsibility to comply.")}

    + +

    + {' '} + {t( + 'In many countries using RoboSats is no different than using Ebay or Craiglist. Your regulation may vary. It is your responsibility to comply.', + )} +

    - }> - - {t("Disclaimer")} - + }> + {t('Disclaimer')} - -

    {t("This lightning application is provided as is. It is in active development: trade with the utmost caution. There is no private support. Support is only offered via public channels ")}{t("(Telegram)")}{t(". RoboSats will never contact you. RoboSats will definitely never ask for your robot token.")}

    + +

    + {' '} + {t( + 'This lightning application is provided as is. It is in active development: trade with the utmost caution. There is no private support. Support is only offered via public channels ', + )} + {t('(Telegram)')} + {t( + '. RoboSats will never contact you. RoboSats will definitely never ask for your robot token.', + )} +

    - + - - - ) -} + ); +}; export default InfoDialog; diff --git a/frontend/src/components/Dialogs/Learn.tsx b/frontend/src/components/Dialogs/Learn.tsx index e1814c38..94e69205 100644 --- a/frontend/src/components/Dialogs/Learn.tsx +++ b/frontend/src/components/Dialogs/Learn.tsx @@ -1,50 +1,49 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, DialogTitle, - DialogActions, + DialogActions, DialogContent, DialogContentText, Button, - Link, -} from "@mui/material" + Link, +} from '@mui/material'; type Props = { open: boolean; onClose: () => void; -} +}; -const LearnDialog = ({ - open, - onClose, -}: Props): JSX.Element => { +const LearnDialog = ({ open, onClose }: Props): JSX.Element => { const { t } = useTranslation(); return ( - - - {t("Learn RoboSats")} - + + {t('Learn RoboSats')} - {t("You are about to visit Learn RoboSats. It hosts tutorials and documentation to help you learn how to use RoboSats and understand how it works.")} + {t( + 'You are about to visit Learn RoboSats. It hosts tutorials and documentation to help you learn how to use RoboSats and understand how it works.', + )} - - + - - ) -} + ); +}; export default LearnDialog; diff --git a/frontend/src/components/Dialogs/NoRobot.tsx b/frontend/src/components/Dialogs/NoRobot.tsx index ab9577c0..051ffdb1 100644 --- a/frontend/src/components/Dialogs/NoRobot.tsx +++ b/frontend/src/components/Dialogs/NoRobot.tsx @@ -1,48 +1,43 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, DialogTitle, - DialogActions, + DialogActions, DialogContent, DialogContentText, - Button, -} from "@mui/material" -import { Link } from 'react-router-dom' + Button, +} from '@mui/material'; +import { Link } from 'react-router-dom'; type Props = { open: boolean; onClose: () => void; -} +}; -const NoRobotDialog = ({ - open, - onClose, -}: Props): JSX.Element => { +const NoRobotDialog = ({ open, onClose }: Props): JSX.Element => { const { t } = useTranslation(); return ( - - - {t("You do not have a robot avatar")} - + + {t('You do not have a robot avatar')} - {t("You need to generate a robot avatar in order to become an order maker")} + {t('You need to generate a robot avatar in order to become an order maker')} - - + + - - ) -} + ); +}; export default NoRobotDialog; diff --git a/frontend/src/components/Dialogs/Profile.tsx b/frontend/src/components/Dialogs/Profile.tsx index a2d4eec8..1c3fbba6 100644 --- a/frontend/src/components/Dialogs/Profile.tsx +++ b/frontend/src/components/Dialogs/Profile.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Link as LinkRouter } from "react-router-dom"; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link as LinkRouter } from 'react-router-dom'; import { Avatar, @@ -23,19 +23,19 @@ import { TextField, Tooltip, Typography, -} from "@mui/material"; +} from '@mui/material'; -import BoltIcon from "@mui/icons-material/Bolt"; -import NumbersIcon from "@mui/icons-material/Numbers"; -import PasswordIcon from "@mui/icons-material/Password"; -import ContentCopy from "@mui/icons-material/ContentCopy"; -import PersonAddAltIcon from "@mui/icons-material/PersonAddAlt"; -import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; -import { UserNinjaIcon, BitcoinIcon } from "../Icons"; +import BoltIcon from '@mui/icons-material/Bolt'; +import NumbersIcon from '@mui/icons-material/Numbers'; +import PasswordIcon from '@mui/icons-material/Password'; +import ContentCopy from '@mui/icons-material/ContentCopy'; +import PersonAddAltIcon from '@mui/icons-material/PersonAddAlt'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import { UserNinjaIcon, BitcoinIcon } from '../Icons'; -import { getCookie } from "../../utils/cookies"; -import { copyToClipboard } from "../../utils/clipboard"; -import { getWebln } from "../../utils/webln"; +import { getCookie } from '../../utils/cookies'; +import { copyToClipboard } from '../../utils/clipboard'; +import { getWebln } from '../../utils/webln'; type Props = { isOpen: boolean; @@ -44,7 +44,7 @@ type Props = { activeOrderId: string | number; lastOrderId: string | number; referralCode: string; - handleSubmitInvoiceClicked: (e:any, invoice: string) => void; + handleSubmitInvoiceClicked: (e: any, invoice: string) => void; host: string; showRewardsSpinner: boolean; withdrawn: boolean; @@ -53,7 +53,7 @@ type Props = { stealthInvoices: boolean; handleSetStealthInvoice: (stealth: boolean) => void; setAppState: (state: any) => void; // TODO: move to a ContextProvider -} +}; const ProfileDialog = ({ isOpen, @@ -74,24 +74,23 @@ const ProfileDialog = ({ }: Props): JSX.Element => { const { t } = useTranslation(); - const [rewardInvoice, setRewardInvoice] = useState(""); + const [rewardInvoice, setRewardInvoice] = useState(''); const [showRewards, setShowRewards] = useState(false); const [openClaimRewards, setOpenClaimRewards] = useState(false); - const [weblnEnabled, setWeblnEnabled] = useState(false) + const [weblnEnabled, setWeblnEnabled] = useState(false); useEffect(() => { - getWebln() - .then((webln) => { - setWeblnEnabled(webln !== undefined) - }) - }, [showRewards]) + getWebln().then((webln) => { + setWeblnEnabled(webln !== undefined); + }); + }, [showRewards]); const copyTokenHandler = () => { - const robotToken = getCookie("robot_token"); + const robotToken = getCookie('robot_token'); if (robotToken) { copyToClipboard(robotToken); - setAppState({copiedToken:true}); + setAppState({ copiedToken: true }); } }; @@ -99,59 +98,71 @@ const ProfileDialog = ({ copyToClipboard(`http://${host}/ref/${referralCode}`); }; - const handleWeblnInvoiceClicked = async (e: any) =>{ + const handleWeblnInvoiceClicked = async (e: any) => { e.preventDefault(); if (earnedRewards) { const webln = await getWebln(); const invoice = webln.makeInvoice(earnedRewards).then(() => { if (invoice) { - handleSubmitInvoiceClicked(e, invoice.paymentRequest) + handleSubmitInvoiceClicked(e, invoice.paymentRequest); } - }) + }); } - } + }; return ( - {t("Your Profile")} + + {t('Your Profile')} + - + - - - + + + {nickname ? ( -
    -
    - +
    +
    + {nickname} - +
    - ) - : null} + ) : null} - - + {activeOrderId ? ( - - + + - ) : - lastOrderId ? ( - - - - - - - ) : ( - - - - - - - ) - } + ) : lastOrderId ? ( + + + + + + + ) : ( + + + + + + + )} - - {getCookie("robot_token") ? ( + + {getCookie('robot_token') ? ( - - - - , - }} + endAdornment: ( + + + + + + ), + }} /> - ) : - t("Cannot remember") - } + ) : ( + t('Cannot remember') + )} - + - + - + handleSetStealthInvoice(!stealthInvoices) - } + onChange={() => handleSetStealthInvoice(!stealthInvoices)} /> } /> @@ -258,21 +269,19 @@ const ProfileDialog = ({ - + - {t("Rewards and compensations")} -
    } +
    + {t('Rewards and compensations')} +
    + } control={ - setShowRewards(!showRewards)} - /> + setShowRewards(!showRewards)} /> } /> @@ -285,24 +294,25 @@ const ProfileDialog = ({ - + - - - - , - }} - /> + endAdornment: ( + + + + + + ), + }} + /> @@ -312,7 +322,7 @@ const ProfileDialog = ({ {!openClaimRewards ? ( - + {`${earnedRewards} Sats`} @@ -322,54 +332,56 @@ const ProfileDialog = ({ ) : ( -
    - - + + + { + onChange={(e) => { setRewardInvoice(e.target.value); }} /> - + {weblnEnabled && ( - - + + @@ -379,15 +391,15 @@ const ProfileDialog = ({ {showRewardsSpinner && ( -
    +
    )} {withdrawn && ( -
    - - {t("There it goes, thank you!🥇")} +
    + + {t('There it goes, thank you!🥇')}
    )} diff --git a/frontend/src/components/Dialogs/Stats.tsx b/frontend/src/components/Dialogs/Stats.tsx index 9f77ddf9..e2324fb8 100644 --- a/frontend/src/components/Dialogs/Stats.tsx +++ b/frontend/src/components/Dialogs/Stats.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, @@ -11,19 +11,19 @@ import { ListItem, ListItemIcon, Typography, -} from "@mui/material"; +} from '@mui/material'; -import BoltIcon from "@mui/icons-material/Bolt"; -import PublicIcon from "@mui/icons-material/Public"; -import DnsIcon from "@mui/icons-material/Dns"; -import WebIcon from "@mui/icons-material/Web"; -import FavoriteIcon from "@mui/icons-material/Favorite"; -import GitHubIcon from "@mui/icons-material/GitHub"; -import EqualizerIcon from "@mui/icons-material/Equalizer"; +import BoltIcon from '@mui/icons-material/Bolt'; +import PublicIcon from '@mui/icons-material/Public'; +import DnsIcon from '@mui/icons-material/Dns'; +import WebIcon from '@mui/icons-material/Web'; +import FavoriteIcon from '@mui/icons-material/Favorite'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import EqualizerIcon from '@mui/icons-material/Equalizer'; -import { AmbossIcon, BitcoinSignIcon } from "../Icons"; +import { AmbossIcon, BitcoinSignIcon } from '../Icons'; -import { pn } from "../../utils/prettyNumbers"; +import { pn } from '../../utils/prettyNumbers'; type Props = { isOpen: boolean; @@ -37,7 +37,7 @@ type Props = { robosatsRunningCommitHash: string; lastDayVolume: number; lifetimeVolume: number; -} +}; const StatsDialog = ({ isOpen, @@ -58,11 +58,13 @@ const StatsDialog = ({ - {t("Stats For Nerds")} + + {t('Stats For Nerds')} + @@ -71,21 +73,21 @@ const StatsDialog = ({ - + - {network === "testnet" ? ( + {network === 'testnet' ? ( {`${nodeId.slice(0, 12)}... (1ML)`} @@ -97,11 +99,7 @@ const StatsDialog = ({ - + {`${nodeId.slice(0, 12)}... (AMBOSS)`} @@ -115,11 +113,7 @@ const StatsDialog = ({ - + {`${alternativeSite.slice(0, 12)}...onion`} @@ -131,11 +125,11 @@ const StatsDialog = ({ - + {`${robosatsRunningCommitHash.slice(0, 12)}...`} @@ -148,10 +142,17 @@ const StatsDialog = ({ - -
    + +
    {pn(lastDayVolume)} - +
    @@ -162,10 +163,17 @@ const StatsDialog = ({ - -
    + +
    {pn(lifetimeVolume)} - +
    @@ -177,14 +185,21 @@ const StatsDialog = ({ - {`${t("Made with")} `} - - {` ${t("and")} `} - +
    + {`${t('Made with')} `} + + {` ${t('and')} `} +
    } - secondary={t("... somewhere on Earth!")} + secondary={t('... somewhere on Earth!')} /> diff --git a/frontend/src/components/Dialogs/StoreToken.tsx b/frontend/src/components/Dialogs/StoreToken.tsx index 4e0b4ac4..dfbd806f 100644 --- a/frontend/src/components/Dialogs/StoreToken.tsx +++ b/frontend/src/components/Dialogs/StoreToken.tsx @@ -1,19 +1,19 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, DialogTitle, - Tooltip, + Tooltip, IconButton, TextField, - DialogActions, + DialogActions, DialogContent, DialogContentText, - Button, + Button, Grid, -} from "@mui/material" -import { getCookie } from "../../utils/cookies"; -import ContentCopy from "@mui/icons-material/ContentCopy"; +} from '@mui/material'; +import { getCookie } from '../../utils/cookies'; +import ContentCopy from '@mui/icons-material/ContentCopy'; type Props = { open: boolean; @@ -22,10 +22,10 @@ type Props = { onClickCopy: () => void; onClickBack: () => void; onClickDone: () => void; -} +}; const StoreTokenDialog = ({ - open, + open, onClose, copyIconColor, onClickCopy, @@ -35,46 +35,45 @@ const StoreTokenDialog = ({ const { t } = useTranslation(); return ( - - - {t("Store your robot token")} - + + {t('Store your robot token')} - {t("You might need to recover your robot avatar in the future: store it safely. You can simply copy it into another application.")} + {t( + 'You might need to recover your robot avatar in the future: store it safely. You can simply copy it into another application.', + )} -
    - +
    + - - - - , - }} - /> + endAdornment: ( + + + + + + ), + }} + />
    - - + + -
    - ) -} + ); +}; export default StoreTokenDialog; diff --git a/frontend/src/components/Dialogs/index.ts b/frontend/src/components/Dialogs/index.ts index f95a5189..254bd8ad 100644 --- a/frontend/src/components/Dialogs/index.ts +++ b/frontend/src/components/Dialogs/index.ts @@ -1,10 +1,9 @@ -export { default as AuditPGPDialog } from "./AuditPGP"; -export { default as CommunityDialog } from "./Community"; -export { default as InfoDialog } from "./Info"; -export { default as LearnDialog } from "./Learn"; -export { default as NoRobotDialog } from "./NoRobot"; -export { default as StoreTokenDialog } from "./StoreToken"; -export { default as ExchangeSummaryDialog } from "./ExchangeSummary"; -export { default as ProfileDialog } from "./Profile"; -export { default as StatsDialog } from "./Stats"; - +export { default as AuditPGPDialog } from './AuditPGP'; +export { default as CommunityDialog } from './Community'; +export { default as InfoDialog } from './Info'; +export { default as LearnDialog } from './Learn'; +export { default as NoRobotDialog } from './NoRobot'; +export { default as StoreTokenDialog } from './StoreToken'; +export { default as ExchangeSummaryDialog } from './ExchangeSummary'; +export { default as ProfileDialog } from './Profile'; +export { default as StatsDialog } from './Stats'; diff --git a/frontend/src/components/EncryptedChat.js b/frontend/src/components/EncryptedChat.js index 1622e1d6..cd0ab10a 100644 --- a/frontend/src/components/EncryptedChat.js +++ b/frontend/src/components/EncryptedChat.js @@ -1,17 +1,30 @@ import React, { Component } from 'react'; -import { withTranslation } from "react-i18next"; -import {Button, IconButton, Badge, Tooltip, TextField, Grid, Container, Card, CardHeader, Paper, Avatar, Typography} from "@mui/material"; +import { withTranslation } from 'react-i18next'; +import { + Button, + IconButton, + Badge, + Tooltip, + TextField, + Grid, + Container, + Card, + CardHeader, + Paper, + Avatar, + Typography, +} from '@mui/material'; import ReconnectingWebSocket from 'reconnecting-websocket'; -import { encryptMessage , decryptMessage} from "../utils/pgp"; -import { getCookie } from "../utils/cookies"; -import { saveAsJson } from "../utils/saveFile"; -import { copyToClipboard } from "../utils/clipboard"; -import { AuditPGPDialog } from "./Dialogs" +import { encryptMessage, decryptMessage } from '../utils/pgp'; +import { getCookie } from '../utils/cookies'; +import { saveAsJson } from '../utils/saveFile'; +import { copyToClipboard } from '../utils/clipboard'; +import { AuditPGPDialog } from './Dialogs'; // Icons import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; -import ContentCopy from "@mui/icons-material/ContentCopy"; +import ContentCopy from '@mui/icons-material/ContentCopy'; import VisibilityIcon from '@mui/icons-material/Visibility'; import CircularProgress from '@mui/material/CircularProgress'; import KeyIcon from '@mui/icons-material/Key'; @@ -23,120 +36,157 @@ class Chat extends Component { } state = { - own_pub_key: getCookie('pub_key').split('\\').join('\n'), + own_pub_key: getCookie('pub_key').split('\\').join('\n'), own_enc_priv_key: getCookie('enc_priv_key').split('\\').join('\n'), peer_pub_key: null, token: getCookie('robot_token'), messages: [], - value:'', + value: '', connected: false, peer_connected: false, audit: false, - showPGP: new Array, + showPGP: new Array(), waitingEcho: false, lastSent: '---BLANK---', latestIndex: 0, - scrollNow:false, + scrollNow: false, }; - rws = new ReconnectingWebSocket('ws://' + window.location.host + '/ws/chat/' + this.props.orderId + '/', [], {connectionTimeout: 15000}); - + rws = new ReconnectingWebSocket( + 'ws://' + window.location.host + '/ws/chat/' + this.props.orderId + '/', + [], + { connectionTimeout: 15000 }, + ); + componentDidMount() { this.rws.addEventListener('open', () => { console.log('Connected!'); - this.setState({connected: true}); - this.rws.send(JSON.stringify({ - type: "message", - message: this.state.own_pub_key, - nick: this.props.ur_nick, - })); + this.setState({ connected: true }); + this.rws.send( + JSON.stringify({ + type: 'message', + message: this.state.own_pub_key, + nick: this.props.ur_nick, + }), + ); }); this.rws.addEventListener('message', (message) => { - const dataFromServer = JSON.parse(message.data); console.log('Got reply!', dataFromServer.type); - console.log('PGP message index', dataFromServer.index, ' latestIndex ',this.state.latestIndex); - if (dataFromServer){ - console.log(dataFromServer) - this.setState({peer_connected: dataFromServer.peer_connected}) + console.log( + 'PGP message index', + dataFromServer.index, + ' latestIndex ', + this.state.latestIndex, + ); + if (dataFromServer) { + console.log(dataFromServer); + this.setState({ peer_connected: dataFromServer.peer_connected }); // If we receive our own key on a message - if (dataFromServer.message == this.state.own_pub_key){console.log("OWN PUB KEY RECEIVED!!")} + if (dataFromServer.message == this.state.own_pub_key) { + console.log('OWN PUB KEY RECEIVED!!'); + } // If we receive a public key other than ours (our peer key!) - if (dataFromServer.message.substring(0,36) == `-----BEGIN PGP PUBLIC KEY BLOCK-----` && dataFromServer.message != this.state.own_pub_key) { - if (dataFromServer.message == this.state.peer_pub_key){ - console.log("PEER HAS RECONNECTED USING HIS PREVIOUSLY KNOWN PUBKEY") - } else if (dataFromServer.message != this.state.peer_pub_key & this.state.peer_pub_key != null){ - console.log("PEER PUBKEY HAS CHANGED") + if ( + dataFromServer.message.substring(0, 36) == `-----BEGIN PGP PUBLIC KEY BLOCK-----` && + dataFromServer.message != this.state.own_pub_key + ) { + if (dataFromServer.message == this.state.peer_pub_key) { + console.log('PEER HAS RECONNECTED USING HIS PREVIOUSLY KNOWN PUBKEY'); + } else if ( + (dataFromServer.message != this.state.peer_pub_key) & + (this.state.peer_pub_key != null) + ) { + console.log('PEER PUBKEY HAS CHANGED'); } - console.log("PEER PUBKEY RECEIVED!!") - this.setState({peer_pub_key:dataFromServer.message}) + console.log('PEER PUBKEY RECEIVED!!'); + this.setState({ peer_pub_key: dataFromServer.message }); // After receiving the peer pubkey we ask the server for the historic messages if any - this.rws.send(JSON.stringify({ - type: "message", + this.rws.send( + JSON.stringify({ + type: 'message', message: `-----SERVE HISTORY-----`, nick: this.props.ur_nick, - })) - } else + }), + ); + } // If we receive an encrypted message - if (dataFromServer.message.substring(0,27) == `-----BEGIN PGP MESSAGE-----` && dataFromServer.index > this.state.latestIndex){ - + else if ( + dataFromServer.message.substring(0, 27) == `-----BEGIN PGP MESSAGE-----` && + dataFromServer.index > this.state.latestIndex + ) { decryptMessage( - dataFromServer.message.split('\\').join('\n'), - dataFromServer.user_nick == this.props.ur_nick ? this.state.own_pub_key : this.state.peer_pub_key, - this.state.own_enc_priv_key, - this.state.token) - .then((decryptedData) => - this.setState((state) => - ({ + dataFromServer.message.split('\\').join('\n'), + dataFromServer.user_nick == this.props.ur_nick + ? this.state.own_pub_key + : this.state.peer_pub_key, + this.state.own_enc_priv_key, + this.state.token, + ).then((decryptedData) => + this.setState((state) => ({ scrollNow: true, - waitingEcho: this.state.waitingEcho == true ? (decryptedData.decryptedMessage == this.state.lastSent ? false: true ) : false, - lastSent: decryptedData.decryptedMessage == this.state.lastSent ? '----BLANK----': this.state.lastSent, - latestIndex: dataFromServer.index > this.state.latestIndex ? dataFromServer.index : this.state.latestIndex, - messages: [...state.messages, - { - index: dataFromServer.index, - encryptedMessage: dataFromServer.message.split('\\').join('\n'), - plainTextMessage: decryptedData.decryptedMessage, - validSignature: decryptedData.validSignature, - userNick: dataFromServer.user_nick, - time: dataFromServer.time - }].sort(function(a,b) { + waitingEcho: + this.state.waitingEcho == true + ? decryptedData.decryptedMessage == this.state.lastSent + ? false + : true + : false, + lastSent: + decryptedData.decryptedMessage == this.state.lastSent + ? '----BLANK----' + : this.state.lastSent, + latestIndex: + dataFromServer.index > this.state.latestIndex + ? dataFromServer.index + : this.state.latestIndex, + messages: [ + ...state.messages, + { + index: dataFromServer.index, + encryptedMessage: dataFromServer.message.split('\\').join('\n'), + plainTextMessage: decryptedData.decryptedMessage, + validSignature: decryptedData.validSignature, + userNick: dataFromServer.user_nick, + time: dataFromServer.time, + }, + ].sort(function (a, b) { // order the message array by their index (increasing) - return a.index - b.index + return a.index - b.index; }), - }) - )); - - } else + })), + ); + } // We allow plaintext communication. The user must write # to start // If we receive an plaintext message - if (dataFromServer.message.substring(0,1) == "#"){ - console.log("Got plaintext message", dataFromServer.message) - this.setState((state) => - ({ - scrollNow: true, - messages: [...state.messages, - { + else if (dataFromServer.message.substring(0, 1) == '#') { + console.log('Got plaintext message', dataFromServer.message); + this.setState((state) => ({ + scrollNow: true, + messages: [ + ...state.messages, + { index: this.state.latestIndex + 0.001, encryptedMessage: dataFromServer.message, plainTextMessage: dataFromServer.message, - validSignature: false, + validSignature: false, userNick: dataFromServer.user_nick, - time: (new Date).toString(), - }]})); - } + time: new Date().toString(), + }, + ], + })); + } } }); this.rws.addEventListener('close', () => { console.log('Socket is closed. Reconnect will be attempted'); - this.setState({connected: false}); + this.setState({ connected: false }); }); this.rws.addEventListener('error', () => { @@ -145,216 +195,401 @@ class Chat extends Component { } componentDidUpdate() { - // Only fire the scroll and audio when the reason for Update is a new message - if (this.state.scrollNow){ - const audio = new Audio(`/static/assets/sounds/chat-open.mp3`) + if (this.state.scrollNow) { + const audio = new Audio(`/static/assets/sounds/chat-open.mp3`); audio.play(); this.scrollToBottom(); - this.setState({scrollNow:false}); + this.setState({ scrollNow: false }); } } scrollToBottom = () => { - this.messagesEnd.scrollIntoView({ behavior: "smooth" }); - } + this.messagesEnd.scrollIntoView({ behavior: 'smooth' }); + }; onButtonClicked = (e) => { // If input string contains token. Do not set message - if(this.state.value.indexOf(this.state.token) !== -1){ - alert(`Aye! You just sent your own robot token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`) - this.setState({value: ""}); + if (this.state.value.indexOf(this.state.token) !== -1) { + alert( + `Aye! You just sent your own robot token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`, + ); + this.setState({ value: '' }); } // If input string contains '#' send unencrypted and unlogged message - else if(this.state.value.substring(0,1)=='#'){ - this.rws.send(JSON.stringify({ - type: "message", + else if (this.state.value.substring(0, 1) == '#') { + this.rws.send( + JSON.stringify({ + type: 'message', message: this.state.value, nick: this.props.ur_nick, - })); - this.setState({value: ""}); + }), + ); + this.setState({ value: '' }); } - - // Else if message is not empty send message - else if(this.state.value!=''){ - this.setState({value: "", waitingEcho: true, lastSent:this.state.value}) - encryptMessage(this.state.value, this.state.own_pub_key, this.state.peer_pub_key, this.state.own_enc_priv_key, this.state.token) - .then((encryptedMessage) => - console.log("Sending Encrypted MESSAGE", encryptedMessage) & - this.rws.send(JSON.stringify({ - type: "message", - message: encryptedMessage.split('\n').join('\\'), - nick: this.props.ur_nick, - }) - ) + + // Else if message is not empty send message + else if (this.state.value != '') { + this.setState({ value: '', waitingEcho: true, lastSent: this.state.value }); + encryptMessage( + this.state.value, + this.state.own_pub_key, + this.state.peer_pub_key, + this.state.own_enc_priv_key, + this.state.token, + ).then( + (encryptedMessage) => + console.log('Sending Encrypted MESSAGE', encryptedMessage) & + this.rws.send( + JSON.stringify({ + type: 'message', + message: encryptedMessage.split('\n').join('\\'), + nick: this.props.ur_nick, + }), + ), ); } e.preventDefault(); - } + }; createJsonFile = () => { - return ({ - "credentials": { - "own_public_key": this.state.own_pub_key, - "peer_public_key":this.state.peer_pub_key, - "encrypted_private_key":this.state.own_enc_priv_key, - "passphrase":this.state.token}, - "messages": this.state.messages, - }) - } + return { + credentials: { + own_public_key: this.state.own_pub_key, + peer_public_key: this.state.peer_pub_key, + encrypted_private_key: this.state.own_enc_priv_key, + passphrase: this.state.token, + }, + messages: this.state.messages, + }; + }; messageCard = (props) => { const { t } = this.props; - return( - - + - + + src={ + window.location.origin + + '/static/assets/avatars/' + + props.message.userNick + + '.png' + } + /> } - style={{backgroundColor: props.cardColor}} + style={{ backgroundColor: props.cardColor }} title={ - -
    -
    + +
    +
    {props.message.userNick} - {props.message.validSignature ? - - : - - } + {props.message.validSignature ? ( + + ) : ( + + )}
    -
    - - this.setState(prevState => { - const newShowPGP = [...prevState.showPGP]; - newShowPGP[props.index] = !newShowPGP[props.index]; - return {showPGP: newShowPGP}; - })}> - +
    + + this.setState((prevState) => { + const newShowPGP = [...prevState.showPGP]; + newShowPGP[props.index] = !newShowPGP[props.index]; + return { showPGP: newShowPGP }; + }) + } + > +
    -
    - - copyToClipboard(this.state.showPGP[props.index] ? props.message.encryptedMessage : props.message.plainTextMessage)}> - +
    + + + copyToClipboard( + this.state.showPGP[props.index] + ? props.message.encryptedMessage + : props.message.plainTextMessage, + ) + } + > +
    } - subheader={this.state.showPGP[props.index] ? {props.message.time}
    {"Valid signature: " + props.message.validSignature}
    {props.message.encryptedMessage}
    : props.message.plainTextMessage} - subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444', fontSize: this.state.showPGP[props.index]? 11 : null }}} + subheader={ + this.state.showPGP[props.index] ? ( + + {' '} + {props.message.time}
    {'Valid signature: ' + props.message.validSignature}{' '} +
    {props.message.encryptedMessage}{' '} +
    + ) : ( + props.message.plainTextMessage + ) + } + subheaderTypographyProps={{ + sx: { + wordWrap: 'break-word', + width: '200px', + color: '#444444', + fontSize: this.state.showPGP[props.index] ? 11 : null, + }, + }} /> - ) - } + ); + }; render() { const { t } = this.props; return ( - + - + - - - {t("You")+": "}{this.state.connected ? t("connected"): t("disconnected")} - - - - - - - - {t("Peer")+": "}{this.state.peer_connected ? t("connected"): t("disconnected")} - - - - - -
    - - {this.state.messages.map((message, index) => -
  • - {message.userNick == this.props.ur_nick ? - - : - + )} -
    { this.messagesEnd = el; }}>
    + > + + {t('You') + ': '} + {this.state.connected ? t('connected') : t('disconnected')} + +
    + + + + + + {t('Peer') + ': '} + {this.state.peer_connected ? t('connected') : t('disconnected')} + + + + + +
    + + {this.state.messages.map((message, index) => ( +
  • + {message.userNick == this.props.ur_nick ? ( + + ) : ( + + )} +
  • + ))} +
    { + this.messagesEnd = el; + }} + >
    - - + + { + onChange={(e) => { this.setState({ value: e.target.value }); this.value = this.state.value; }} - sx={{width: 219}} + sx={{ width: 219 }} /> - -
    -
    +
    this.setState({audit:false})} + onClose={() => this.setState({ audit: false })} orderId={Number(this.props.orderId)} messages={this.state.messages} own_pub_key={this.state.own_pub_key} own_enc_priv_key={this.state.own_enc_priv_key} - peer_pub_key={this.state.peer_pub_key ? this.state.peer_pub_key : "Not received yet"} + peer_pub_key={this.state.peer_pub_key ? this.state.peer_pub_key : 'Not received yet'} passphrase={this.state.token} - onClickBack={() => this.setState({audit:false})} + onClickBack={() => this.setState({ audit: false })} /> - + - - + + - + - - + + - - ) + ); } } diff --git a/frontend/src/components/FlagWithProps/FlagWithProps.tsx b/frontend/src/components/FlagWithProps/FlagWithProps.tsx index ab0f6458..c5eafc49 100644 --- a/frontend/src/components/FlagWithProps/FlagWithProps.tsx +++ b/frontend/src/components/FlagWithProps/FlagWithProps.tsx @@ -1,11 +1,11 @@ -import React from "react"; -import Flags from 'country-flag-icons/react/3x2' +import React from 'react'; +import Flags from 'country-flag-icons/react/3x2'; import SwapCallsIcon from '@mui/icons-material/SwapCalls'; -import { GoldIcon, EarthIcon } from "../Icons"; +import { GoldIcon, EarthIcon } from '../Icons'; type Props = { code: string; -} +}; const FlagWithProps = ({ code }: Props): JSX.Element => { const defaultProps = { @@ -15,82 +15,80 @@ const FlagWithProps = ({ code }: Props): JSX.Element => { let flag: JSX.Element | null = null; - if(code === 'AED') flag = ; - if(code === 'AUD') flag = ; - if(code === 'ARS') flag = ; - if(code === 'BRL') flag = ; - if(code === 'BYN') flag = ; - if(code === 'CAD') flag = ; - if(code === 'CHF') flag = ; - if(code === 'CLP') flag = ; - if(code === 'CNY') flag = ; - if(code === 'EGP') flag = ; - if(code === 'EUR') flag = ; - if(code === 'HRK') flag = ; - if(code === 'CZK') flag = ; - if(code === 'DKK') flag = ; - if(code === 'GBP') flag = ; - if(code === 'HKD') flag = ; - if(code === 'HUF') flag = ; - if(code === 'INR') flag = ; - if(code === 'ISK') flag = ; - if(code === 'JPY') flag = ; - if(code === 'KRW') flag = ; - if(code === 'LKR') flag = ; - if(code === 'MAD') flag = ; - if(code === 'MXN') flag = ; - if(code === 'NOK') flag = ; - if(code === 'NZD') flag = ; - if(code === 'PLN') flag = ; - if(code === 'RON') flag = ; - if(code === 'RUB') flag = ; - if(code === 'SEK') flag = ; - if(code === 'SGD') flag = ; - if(code === 'VES') flag = ; - if(code === 'TRY') flag = ; - if(code === 'USD') flag = ; - if(code === 'ZAR') flag = ; - if(code === 'COP') flag = ; - if(code === 'PEN') flag = ; - if(code === 'UYU') flag = ; - if(code === 'PYG') flag = ; - if(code === 'BOB') flag = ; - if(code === 'IDR') flag = ; - if(code === 'ANG') flag = ; - if(code === 'CRC') flag = ; - if(code === 'CUP') flag = ; - if(code === 'DOP') flag = ; - if(code === 'GHS') flag = ; - if(code === 'GTQ') flag = ; - if(code === 'ILS') flag = ; - if(code === 'JMD') flag = ; - if(code === 'KES') flag = ; - if(code === 'KZT') flag = ; - if(code === 'MYR') flag = ; - if(code === 'NAD') flag = ; - if(code === 'NGN') flag = ; - if(code === 'AZN') flag = ; - if(code === 'PAB') flag = ; - if(code === 'PHP') flag = ; - if(code === 'PKR') flag = ; - if(code === 'QAR') flag = ; - if(code === 'SAR') flag = ; - if(code === 'THB') flag = ; - if(code === 'TTD') flag = ; - if(code === 'VND') flag = ; - if(code === 'XOF') flag = ; - if(code === 'TWD') flag = ; - if(code === 'TZS') flag = ; - if(code === 'XAF') flag = ; - if(code === 'UAH') flag = ; - if(code === 'TND') flag = ; - if(code === 'ANY') flag = ; - if(code === 'XAU') flag = ; - if(code === 'BTC') flag = ; + if (code === 'AED') flag = ; + if (code === 'AUD') flag = ; + if (code === 'ARS') flag = ; + if (code === 'BRL') flag = ; + if (code === 'BYN') flag = ; + if (code === 'CAD') flag = ; + if (code === 'CHF') flag = ; + if (code === 'CLP') flag = ; + if (code === 'CNY') flag = ; + if (code === 'EGP') flag = ; + if (code === 'EUR') flag = ; + if (code === 'HRK') flag = ; + if (code === 'CZK') flag = ; + if (code === 'DKK') flag = ; + if (code === 'GBP') flag = ; + if (code === 'HKD') flag = ; + if (code === 'HUF') flag = ; + if (code === 'INR') flag = ; + if (code === 'ISK') flag = ; + if (code === 'JPY') flag = ; + if (code === 'KRW') flag = ; + if (code === 'LKR') flag = ; + if (code === 'MAD') flag = ; + if (code === 'MXN') flag = ; + if (code === 'NOK') flag = ; + if (code === 'NZD') flag = ; + if (code === 'PLN') flag = ; + if (code === 'RON') flag = ; + if (code === 'RUB') flag = ; + if (code === 'SEK') flag = ; + if (code === 'SGD') flag = ; + if (code === 'VES') flag = ; + if (code === 'TRY') flag = ; + if (code === 'USD') flag = ; + if (code === 'ZAR') flag = ; + if (code === 'COP') flag = ; + if (code === 'PEN') flag = ; + if (code === 'UYU') flag = ; + if (code === 'PYG') flag = ; + if (code === 'BOB') flag = ; + if (code === 'IDR') flag = ; + if (code === 'ANG') flag = ; + if (code === 'CRC') flag = ; + if (code === 'CUP') flag = ; + if (code === 'DOP') flag = ; + if (code === 'GHS') flag = ; + if (code === 'GTQ') flag = ; + if (code === 'ILS') flag = ; + if (code === 'JMD') flag = ; + if (code === 'KES') flag = ; + if (code === 'KZT') flag = ; + if (code === 'MYR') flag = ; + if (code === 'NAD') flag = ; + if (code === 'NGN') flag = ; + if (code === 'AZN') flag = ; + if (code === 'PAB') flag = ; + if (code === 'PHP') flag = ; + if (code === 'PKR') flag = ; + if (code === 'QAR') flag = ; + if (code === 'SAR') flag = ; + if (code === 'THB') flag = ; + if (code === 'TTD') flag = ; + if (code === 'VND') flag = ; + if (code === 'XOF') flag = ; + if (code === 'TWD') flag = ; + if (code === 'TZS') flag = ; + if (code === 'XAF') flag = ; + if (code === 'UAH') flag = ; + if (code === 'TND') flag = ; + if (code === 'ANY') flag = ; + if (code === 'XAU') flag = ; + if (code === 'BTC') flag = ; - return ( -
    {flag}
    - ); + return
    {flag}
    ; }; export default FlagWithProps; diff --git a/frontend/src/components/FlagWithProps/index.ts b/frontend/src/components/FlagWithProps/index.ts index 815f70ae..0bb8a1fd 100644 --- a/frontend/src/components/FlagWithProps/index.ts +++ b/frontend/src/components/FlagWithProps/index.ts @@ -1 +1 @@ -export { default } from "./FlagWithProps"; +export { default } from './FlagWithProps'; diff --git a/frontend/src/components/HomePage.js b/frontend/src/components/HomePage.js index 6d683263..e2d91704 100644 --- a/frontend/src/components/HomePage.js +++ b/frontend/src/components/HomePage.js @@ -1,62 +1,128 @@ -import React, { Component } from "react"; -import { BrowserRouter as Router, Switch, Route, Link, Redirect,useHistory } from "react-router-dom"; +import React, { Component } from 'react'; +import { + BrowserRouter as Router, + Switch, + Route, + Link, + Redirect, + useHistory, +} from 'react-router-dom'; -import UserGenPage from "./UserGenPage"; -import MakerPage from "./MakerPage"; -import BookPage from "./BookPage"; -import OrderPage from "./OrderPage"; -import BottomBar from "./BottomBar"; +import UserGenPage from './UserGenPage'; +import MakerPage from './MakerPage'; +import BookPage from './BookPage'; +import OrderPage from './OrderPage'; +import BottomBar from './BottomBar'; export default class HomePage extends Component { - constructor(props) { - super(props); - this.state = { - nickname: null, - token: null, - copiedToken: false, - avatarLoaded: false, - buyChecked: false, - sellChecked: false, - type:2, - currency:0, - bookCurrencyCode:'ANY', - bookOrders:new Array(), - bookLoading: true, - activeOrderId: null, - lastOrderId: null, - earnedRewards: 0, - referralCode:'', - lastDayPremium: 0, - limits: {} - } - } + constructor(props) { + super(props); + this.state = { + nickname: null, + token: null, + copiedToken: false, + avatarLoaded: false, + buyChecked: false, + sellChecked: false, + type: 2, + currency: 0, + bookCurrencyCode: 'ANY', + bookOrders: new Array(), + bookLoading: true, + activeOrderId: null, + lastOrderId: null, + earnedRewards: 0, + referralCode: '', + lastDayPremium: 0, + limits: {}, + }; + } - setAppState=(newState)=>{ - this.setState(newState) - } + setAppState = (newState) => { + this.setState(newState); + }; - redirectTo(location) { + redirectTo(location) { this.props.history.push(location); - } + } - render() { - const fontSize = this.props.theme.typography.fontSize; - const fontSizeFactor = fontSize / 14; // default fontSize is 14 - return ( - -
    - - }/> - }/> - }/> - }/> - }/> - -
    -
    - -
    -
    - ); - } + render() { + const fontSize = this.props.theme.typography.fontSize; + const fontSizeFactor = fontSize / 14; // default fontSize is 14 + return ( + +
    + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + +
    +
    + +
    +
    + ); + } } diff --git a/frontend/src/components/Icons/Amboss.tsx b/frontend/src/components/Icons/Amboss.tsx index 5a11a2c4..2d23cd71 100644 --- a/frontend/src/components/Icons/Amboss.tsx +++ b/frontend/src/components/Icons/Amboss.tsx @@ -1,18 +1,28 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function AmbossIcon(props) { - return ( - - - - - - - - - + return ( + + + + + + + + - - ); - } \ No newline at end of file + + + ); +} diff --git a/frontend/src/components/Icons/BasqueCountryFlag.tsx b/frontend/src/components/Icons/BasqueCountryFlag.tsx index 0f7073b0..79fad1d8 100644 --- a/frontend/src/components/Icons/BasqueCountryFlag.tsx +++ b/frontend/src/components/Icons/BasqueCountryFlag.tsx @@ -1,12 +1,12 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function BasqueCountryFlag(props) { - return ( - - - - - - ); - } \ No newline at end of file + return ( + + + + + + ); +} diff --git a/frontend/src/components/Icons/Bitcoin.tsx b/frontend/src/components/Icons/Bitcoin.tsx index 4e2e5da0..dfbc9b95 100644 --- a/frontend/src/components/Icons/Bitcoin.tsx +++ b/frontend/src/components/Icons/Bitcoin.tsx @@ -1,10 +1,10 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function BitcoinIcon(props) { return ( - - + + ); -} \ No newline at end of file +} diff --git a/frontend/src/components/Icons/BitcoinSign.tsx b/frontend/src/components/Icons/BitcoinSign.tsx index 6c8f764e..94ef24e4 100644 --- a/frontend/src/components/Icons/BitcoinSign.tsx +++ b/frontend/src/components/Icons/BitcoinSign.tsx @@ -1,10 +1,10 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function BitcoinSignIcon(props) { return ( - - + + ); -} \ No newline at end of file +} diff --git a/frontend/src/components/Icons/BuySats.tsx b/frontend/src/components/Icons/BuySats.tsx index 11e2753a..f2c6952d 100644 --- a/frontend/src/components/Icons/BuySats.tsx +++ b/frontend/src/components/Icons/BuySats.tsx @@ -1,49 +1,111 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function BuySatsIcon(props) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - ); - } \ No newline at end of file + c7.991,0,14.492,6.502,14.492,14.494V269.92z' + /> + + + ); +} diff --git a/frontend/src/components/Icons/BuySatsChecked.tsx b/frontend/src/components/Icons/BuySatsChecked.tsx index ea7c5913..d16c0c92 100644 --- a/frontend/src/components/Icons/BuySatsChecked.tsx +++ b/frontend/src/components/Icons/BuySatsChecked.tsx @@ -1,11 +1,13 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function BuySatsCheckedIcon(props) { - return ( - - - - - - - - - - - - ); - } \ No newline at end of file + M101.086,109.122l6.486-7.146l4.892,4.44l0.002,0.002l-6.489,7.149l-4.894-4.442L101.086,109.122z' + /> + + + ); +} diff --git a/frontend/src/components/Icons/CataloniaFlag.tsx b/frontend/src/components/Icons/CataloniaFlag.tsx index cf8dc334..2caa66fb 100644 --- a/frontend/src/components/Icons/CataloniaFlag.tsx +++ b/frontend/src/components/Icons/CataloniaFlag.tsx @@ -1,11 +1,11 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function CataloniaFlag(props) { - return ( - - - - - ); - } \ No newline at end of file + return ( + + + + + ); +} diff --git a/frontend/src/components/Icons/Earth.tsx b/frontend/src/components/Icons/Earth.tsx index d0c3fbf0..42bb72eb 100644 --- a/frontend/src/components/Icons/Earth.tsx +++ b/frontend/src/components/Icons/Earth.tsx @@ -1,16 +1,21 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function EarthIcon(props) { - return ( - - - - - - - - - - - + c8.72-0.94,17.43,1.97,23.76,8.04C181.83,223.785,201.38,236.785,217.97,246.855z' + /> - - - ); - } \ No newline at end of file + C85.17,380.445,0,295.285,0,190.225S85.17,0.005,190.23,0.005z' + /> + + ); +} diff --git a/frontend/src/components/Icons/Export.tsx b/frontend/src/components/Icons/Export.tsx index 038ee27f..52383b85 100644 --- a/frontend/src/components/Icons/Export.tsx +++ b/frontend/src/components/Icons/Export.tsx @@ -1,10 +1,10 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function ExportIcon(props) { return ( - - + + ); -} \ No newline at end of file +} diff --git a/frontend/src/components/Icons/Gold.tsx b/frontend/src/components/Icons/Gold.tsx index 224e5170..e7a31c28 100644 --- a/frontend/src/components/Icons/Gold.tsx +++ b/frontend/src/components/Icons/Gold.tsx @@ -1,21 +1,48 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function GoldIcon(props) { - return ( - - - - - - - - - - - - ); - } \ No newline at end of file + return ( + + + + + + + + + + + + ); +} diff --git a/frontend/src/components/Icons/NewTab.tsx b/frontend/src/components/Icons/NewTab.tsx index 933fc3d2..f96f5906 100644 --- a/frontend/src/components/Icons/NewTab.tsx +++ b/frontend/src/components/Icons/NewTab.tsx @@ -1,10 +1,10 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function NewTabIcon(props) { return ( - - + + ); -} \ No newline at end of file +} diff --git a/frontend/src/components/Icons/RoboSats.tsx b/frontend/src/components/Icons/RoboSats.tsx index 8b2c70aa..e8bac785 100644 --- a/frontend/src/components/Icons/RoboSats.tsx +++ b/frontend/src/components/Icons/RoboSats.tsx @@ -1,11 +1,12 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function RoboSatsIcon(props) { - return ( - - - - - - - - - - - - - - - - - - - - ); - } \ No newline at end of file + c0,10.416,6.225,15.624,18.672,15.624c4.625,0,8.234-0.939,10.827-2.82C818.7,835.149,819.997,832.523,819.997,829.152z' + /> + + + ); +} diff --git a/frontend/src/components/Icons/RoboSatsNoText.tsx b/frontend/src/components/Icons/RoboSatsNoText.tsx index 273754a8..3bd4a62c 100644 --- a/frontend/src/components/Icons/RoboSatsNoText.tsx +++ b/frontend/src/components/Icons/RoboSatsNoText.tsx @@ -1,11 +1,12 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function RoboSatsNoTextIcon(props) { - return ( - - - - - - - - - - ); - } \ No newline at end of file + ' + /> + + + ); +} diff --git a/frontend/src/components/Icons/RoboSatsText.tsx b/frontend/src/components/Icons/RoboSatsText.tsx index fb5bcf3c..41256a99 100644 --- a/frontend/src/components/Icons/RoboSatsText.tsx +++ b/frontend/src/components/Icons/RoboSatsText.tsx @@ -1,11 +1,12 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function RoboSatsTextIcon(props) { - return ( - - - - - - - + C188.111,775.57,188.111,756.221,188.111,736.337z' + /> + - - - - - - - + c0,20.581,12.296,30.87,36.891,30.87c9.136,0,16.268-1.858,21.391-5.573C1720.055,810.097,1722.617,804.907,1722.617,798.248z' + /> - - ); - } \ No newline at end of file + + ); +} diff --git a/frontend/src/components/Icons/SellSats.tsx b/frontend/src/components/Icons/SellSats.tsx index 24ba10b7..8eb79240 100644 --- a/frontend/src/components/Icons/SellSats.tsx +++ b/frontend/src/components/Icons/SellSats.tsx @@ -1,50 +1,112 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function SellSatsIcon(props) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - ); - } \ No newline at end of file + V30.08z' + /> + + + ); +} diff --git a/frontend/src/components/Icons/SellSatsChecked.tsx b/frontend/src/components/Icons/SellSatsChecked.tsx index 2f5b89ad..ca54168c 100644 --- a/frontend/src/components/Icons/SellSatsChecked.tsx +++ b/frontend/src/components/Icons/SellSatsChecked.tsx @@ -1,13 +1,18 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function SellSatsCheckedIcon(props) { return ( - + - - - - - - - + C254.836,164.996,256.054,177.466,252.471,189.264z' + /> ); -} \ No newline at end of file +} diff --git a/frontend/src/components/Icons/SendReceive.tsx b/frontend/src/components/Icons/SendReceive.tsx index 9f88a841..7df18efa 100644 --- a/frontend/src/components/Icons/SendReceive.tsx +++ b/frontend/src/components/Icons/SendReceive.tsx @@ -1,11 +1,13 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function SendReceiveIcon(props) { - return ( - - - - - - ); - } \ No newline at end of file + C140.107,89.966,140.107,79.818,146.365,73.557z' + /> + + + ); +} diff --git a/frontend/src/components/Icons/UserNinja.tsx b/frontend/src/components/Icons/UserNinja.tsx index f471c6f3..699391ab 100644 --- a/frontend/src/components/Icons/UserNinja.tsx +++ b/frontend/src/components/Icons/UserNinja.tsx @@ -1,11 +1,10 @@ -import React, { Component } from "react"; -import { SvgIcon } from "@mui/material" +import React, { Component } from 'react'; +import { SvgIcon } from '@mui/material'; export default function UserNinjaIcon(props) { - return ( - - - - ); - } - + return ( + + + + ); +} diff --git a/frontend/src/components/Icons/index.ts b/frontend/src/components/Icons/index.ts index fddbae9b..984804d7 100644 --- a/frontend/src/components/Icons/index.ts +++ b/frontend/src/components/Icons/index.ts @@ -1,20 +1,20 @@ -export { default as AmbossIcon } from "./Amboss"; -export { default as BitcoinIcon } from "./Bitcoin"; -export { default as BitcoinSignIcon } from "./BitcoinSign"; -export { default as BuySatsIcon } from "./BuySats"; -export { default as BuySatsCheckedIcon } from "./BuySatsChecked"; -export { default as EarthIcon } from "./Earth"; -export { default as GoldIcon } from "./Gold"; -export { default as NewTabIcon } from "./NewTab"; -export { default as RoboSatsIcon } from "./RoboSats"; -export { default as RoboSatsNoTextIcon } from "./RoboSatsNoText"; -export { default as RoboSatsTextIcon } from "./RoboSatsText"; -export { default as SellSatsCheckedIcon } from "./SellSatsChecked"; -export { default as SellSatsIcon } from "./SellSats"; -export { default as SendReceiveIcon } from "./SendReceive"; -export { default as ExportIcon } from "./Export"; -export { default as UserNinjaIcon } from "./UserNinja"; +export { default as AmbossIcon } from './Amboss'; +export { default as BitcoinIcon } from './Bitcoin'; +export { default as BitcoinSignIcon } from './BitcoinSign'; +export { default as BuySatsIcon } from './BuySats'; +export { default as BuySatsCheckedIcon } from './BuySatsChecked'; +export { default as EarthIcon } from './Earth'; +export { default as GoldIcon } from './Gold'; +export { default as NewTabIcon } from './NewTab'; +export { default as RoboSatsIcon } from './RoboSats'; +export { default as RoboSatsNoTextIcon } from './RoboSatsNoText'; +export { default as RoboSatsTextIcon } from './RoboSatsText'; +export { default as SellSatsCheckedIcon } from './SellSatsChecked'; +export { default as SellSatsIcon } from './SellSats'; +export { default as SendReceiveIcon } from './SendReceive'; +export { default as ExportIcon } from './Export'; +export { default as UserNinjaIcon } from './UserNinja'; // Some Flags missing on react-flags -export { default as BasqueCountryFlag } from "./BasqueCountryFlag"; -export { default as CataloniaFlag } from "./CataloniaFlag"; \ No newline at end of file +export { default as BasqueCountryFlag } from './BasqueCountryFlag'; +export { default as CataloniaFlag } from './CataloniaFlag'; diff --git a/frontend/src/components/LinearDeterminate/LinearDeterminate.tsx b/frontend/src/components/LinearDeterminate/LinearDeterminate.tsx index 57db47f8..e2dbe009 100644 --- a/frontend/src/components/LinearDeterminate/LinearDeterminate.tsx +++ b/frontend/src/components/LinearDeterminate/LinearDeterminate.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from "react"; -import { Box, LinearProgress } from "@mui/material" +import React, { useState, useEffect } from 'react'; +import { Box, LinearProgress } from '@mui/material'; import { calcTimeDelta } from 'react-countdown'; type Props = { @@ -7,10 +7,7 @@ type Props = { totalSecsExp: number; }; -const LinearDeterminate = ({ - expiresAt, - totalSecsExp, -}: Props): JSX.Element => { +const LinearDeterminate = ({ expiresAt, totalSecsExp }: Props): JSX.Element => { const [progress, setProgress] = useState(0); useEffect(() => { @@ -28,7 +25,7 @@ const LinearDeterminate = ({ return ( - + ); }; diff --git a/frontend/src/components/LinearDeterminate/index.ts b/frontend/src/components/LinearDeterminate/index.ts index 6c78d4aa..fd63176c 100644 --- a/frontend/src/components/LinearDeterminate/index.ts +++ b/frontend/src/components/LinearDeterminate/index.ts @@ -1 +1 @@ -export { default } from "./LinearDeterminate"; +export { default } from './LinearDeterminate'; diff --git a/frontend/src/components/MakerPage.js b/frontend/src/components/MakerPage.js index 6751dc98..3c37a4d9 100644 --- a/frontend/src/components/MakerPage.js +++ b/frontend/src/components/MakerPage.js @@ -1,11 +1,42 @@ import React, { Component } from 'react'; -import { withTranslation } from "react-i18next"; -import { InputAdornment, LinearProgress, Dialog, IconButton, DialogActions, DialogContent, DialogContentText, DialogTitle, Accordion, AccordionDetails, AccordionSummary, Checkbox, Slider, Box, Tab, Tabs, SliderThumb, Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material" -import RangeSlider from "./RangeSlider"; -import { LocalizationProvider, TimePicker} from '@mui/x-date-pickers'; -import DateFnsUtils from "@date-io/date-fns"; -import { Link as LinkRouter } from 'react-router-dom' -import { StoreTokenDialog, NoRobotDialog } from "./Dialogs"; +import { withTranslation } from 'react-i18next'; +import { + InputAdornment, + LinearProgress, + Dialog, + IconButton, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Accordion, + AccordionDetails, + AccordionSummary, + Checkbox, + Slider, + Box, + Tab, + Tabs, + SliderThumb, + Tooltip, + Paper, + Button, + Grid, + Typography, + TextField, + Select, + FormHelperText, + MenuItem, + FormControl, + Radio, + FormControlLabel, + RadioGroup, +} from '@mui/material'; +import RangeSlider from './RangeSlider'; +import { LocalizationProvider, TimePicker } from '@mui/x-date-pickers'; +import DateFnsUtils from '@date-io/date-fns'; +import { Link as LinkRouter } from 'react-router-dom'; +import { StoreTokenDialog, NoRobotDialog } from './Dialogs'; import FlagWithProps from './FlagWithProps'; import AutocompletePayments from './AutocompletePayments'; @@ -15,16 +46,16 @@ import currencyDict from '../../static/assets/currencies.json'; import LockIcon from '@mui/icons-material/Lock'; import HourglassTopIcon from '@mui/icons-material/HourglassTop'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import { BuySatsCheckedIcon, BuySatsIcon, SellSatsCheckedIcon, SellSatsIcon} from "./Icons"; +import { BuySatsCheckedIcon, BuySatsIcon, SellSatsCheckedIcon, SellSatsIcon } from './Icons'; -import { getCookie } from "../utils/cookies"; -import { pn } from "../utils/prettyNumbers"; -import { copyToClipboard } from "../utils/clipboard"; +import { getCookie } from '../utils/cookies'; +import { pn } from '../utils/prettyNumbers'; +import { copyToClipboard } from '../utils/clipboard'; class MakerPage extends Component { defaultCurrency = 1; defaultCurrencyCode = 'USD'; - defaultPaymentMethod = "not specified"; + defaultPaymentMethod = 'not specified'; defaultPremium = 0; defaultMinTradeSats = 20000; defaultMaxTradeSats = 1200000; @@ -34,65 +65,79 @@ class MakerPage extends Component { constructor(props) { super(props); - this.state={ - minTradeSats: this.defaultMinTradeSats, - maxTradeSats: this.defaultMaxTradeSats, - maxBondlessSats: this.defaultMaxBondlessSats, - tabValue: 0, - openStoreToken: false, - is_explicit: false, - type: null, - currency: this.defaultCurrency, - currencyCode: this.defaultCurrencyCode, - payment_method: this.defaultPaymentMethod, - premium: 0, - satoshis: "", - showAdvanced: false, - allowBondless: false, - publicExpiryTime: new Date(0, 0, 0, 23, 59), - escrowExpiryTime: new Date(0, 0, 0, 3, 0), - enableAmountRange: false, - bondSize: 3, - limits: null, - minAmount: "", - maxAmount: "", - loadingLimits: true, - amount: "", - badPaymentMethod: "", - } - } - - componentDidMount() { - this.getLimits() - // if currency or type have changed in HomePage state, change in MakerPage state too. - this.setState({ - currency: !this.props.currency === 0 ? this.props.currency : this.state.currency, - type: !this.props.type == 2 ? (this.props.type == 1 ? 0 : 1) : this.state.type, - }) - } - - getLimits() { - this.setState({loadingLimits:true}) - fetch('/api/limits/') - .then((response) => response.json()) - .then((data) => this.setState({ - limits:data, - loadingLimits:false, - minAmount: this.state.amount ? parseFloat((this.state.amount/2).toPrecision(2)) : parseFloat(Number(data[this.state.currency]['max_amount']*0.25).toPrecision(2)), - maxAmount: this.state.amount ? this.state.amount : parseFloat(Number(data[this.state.currency]['max_amount']*0.75).toPrecision(2)), - minTradeSats: data["1000"]['min_amount']*100000000, - maxTradeSats: data["1000"]['max_amount']*100000000, - maxBondlessSats: data["1000"]['max_bondless_amount']*100000000, - })); + this.state = { + minTradeSats: this.defaultMinTradeSats, + maxTradeSats: this.defaultMaxTradeSats, + maxBondlessSats: this.defaultMaxBondlessSats, + tabValue: 0, + openStoreToken: false, + is_explicit: false, + type: null, + currency: this.defaultCurrency, + currencyCode: this.defaultCurrencyCode, + payment_method: this.defaultPaymentMethod, + premium: 0, + satoshis: '', + showAdvanced: false, + allowBondless: false, + publicExpiryTime: new Date(0, 0, 0, 23, 59), + escrowExpiryTime: new Date(0, 0, 0, 3, 0), + enableAmountRange: false, + bondSize: 3, + limits: null, + minAmount: '', + maxAmount: '', + loadingLimits: true, + amount: '', + badPaymentMethod: '', + }; } - recalcBounds = () =>{ + componentDidMount() { + this.getLimits(); + // if currency or type have changed in HomePage state, change in MakerPage state too. this.setState({ - minAmount: this.state.amount ? parseFloat((this.state.amount/2).toPrecision(2)) : parseFloat(Number(this.state.limits[this.state.currency]['max_amount']*0.25).toPrecision(2)), - maxAmount: this.state.amount ? this.state.amount : parseFloat(Number(this.state.limits[this.state.currency]['max_amount']*0.75).toPrecision(2)), + currency: !this.props.currency === 0 ? this.props.currency : this.state.currency, + type: !this.props.type == 2 ? (this.props.type == 1 ? 0 : 1) : this.state.type, }); } + getLimits() { + this.setState({ loadingLimits: true }); + fetch('/api/limits/') + .then((response) => response.json()) + .then((data) => + this.setState({ + limits: data, + loadingLimits: false, + minAmount: this.state.amount + ? parseFloat((this.state.amount / 2).toPrecision(2)) + : parseFloat(Number(data[this.state.currency]['max_amount'] * 0.25).toPrecision(2)), + maxAmount: this.state.amount + ? this.state.amount + : parseFloat(Number(data[this.state.currency]['max_amount'] * 0.75).toPrecision(2)), + minTradeSats: data['1000']['min_amount'] * 100000000, + maxTradeSats: data['1000']['max_amount'] * 100000000, + maxBondlessSats: data['1000']['max_bondless_amount'] * 100000000, + }), + ); + } + + recalcBounds = () => { + this.setState({ + minAmount: this.state.amount + ? parseFloat((this.state.amount / 2).toPrecision(2)) + : parseFloat( + Number(this.state.limits[this.state.currency]['max_amount'] * 0.25).toPrecision(2), + ), + maxAmount: this.state.amount + ? this.state.amount + : parseFloat( + Number(this.state.limits[this.state.currency]['max_amount'] * 0.75).toPrecision(2), + ), + }); + }; + a11yProps(index) { return { id: `simple-tab-${index}`, @@ -100,723 +145,913 @@ class MakerPage extends Component { }; } - handleTypeChange=(e)=>{ - this.setState({ - type: e.target.value, - }) - // Share state with HomePage and OrderPage - this.props.setAppState({ - // maker and book page type values 0:1 are reversed - type: (e.target.value == 1 ? 0 : 1), - buyChecked: e.target.value == 0 ? true: false, - sellChecked: e.target.value == 1 ? true: false, - }) - ; - } - handleCurrencyChange=(e)=>{ - var currencyCode = this.getCurrencyCode(e.target.value) + handleTypeChange = (e) => { this.setState({ - currency: e.target.value, - currencyCode: currencyCode, + type: e.target.value, + }); + // Share state with HomePage and OrderPage + this.props.setAppState({ + // maker and book page type values 0:1 are reversed + type: e.target.value == 1 ? 0 : 1, + buyChecked: e.target.value == 0 ? true : false, + sellChecked: e.target.value == 1 ? true : false, + }); + }; + handleCurrencyChange = (e) => { + var currencyCode = this.getCurrencyCode(e.target.value); + this.setState({ + currency: e.target.value, + currencyCode: currencyCode, }); this.props.setAppState({ - type: e.target.value, - currency: e.target.value, - bookCurrencyCode: currencyCode, - }) - if(this.state.enableAmountRange){ - this.setState({ - minAmount: parseFloat(Number(this.state.limits[e.target.value]['max_amount']*0.25).toPrecision(2)), - maxAmount: parseFloat(Number(this.state.limits[e.target.value]['max_amount']*0.75).toPrecision(2)), - }) + type: e.target.value, + currency: e.target.value, + bookCurrencyCode: currencyCode, + }); + if (this.state.enableAmountRange) { + this.setState({ + minAmount: parseFloat( + Number(this.state.limits[e.target.value]['max_amount'] * 0.25).toPrecision(2), + ), + maxAmount: parseFloat( + Number(this.state.limits[e.target.value]['max_amount'] * 0.75).toPrecision(2), + ), + }); } -} - handleAmountChange=(e)=>{ - this.setState({ - amount: e.target.value, - }); + }; + handleAmountChange = (e) => { + this.setState({ + amount: e.target.value, + }); + }; + handleMinAmountChange = (e) => { + this.setState({ + minAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)), + }); + }; + handleMaxAmountChange = (e) => { + this.setState({ + maxAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)), + }); + }; + + handleRangeAmountChange = (e, newValue, activeThumb) => { + var maxAmount = this.getMaxAmount(); + var minAmount = this.getMinAmount(); + var lowerValue = e.target.value[0]; + var upperValue = e.target.value[1]; + var minRange = this.minRangeAmountMultiple; + var maxRange = this.maxRangeAmountMultiple; + + if (lowerValue > maxAmount / minRange) { + lowerValue = maxAmount / minRange; } - handleMinAmountChange=(e)=>{ - this.setState({ - minAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)), - }); - } - handleMaxAmountChange=(e)=>{ - this.setState({ - maxAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)), - }); + if (upperValue < minRange * minAmount) { + upperValue = minRange * minAmount; } - handleRangeAmountChange = (e, newValue, activeThumb) => { - var maxAmount = this.getMaxAmount(); - var minAmount = this.getMinAmount(); - var lowerValue = e.target.value[0]; - var upperValue = e.target.value[1]; - var minRange = this.minRangeAmountMultiple; - var maxRange = this.maxRangeAmountMultiple; - - if (lowerValue > maxAmount/minRange){ - lowerValue = maxAmount/minRange - } - if (upperValue < minRange*minAmount){ - upperValue = minRange*minAmount - } - - if (lowerValue > upperValue/minRange) { - if (activeThumb === 0) { - upperValue = minRange*lowerValue - } else { - lowerValue = upperValue/minRange - } - }else if(lowerValue < upperValue/maxRange){ - if (activeThumb === 0) { - upperValue = maxRange*lowerValue - } else { - lowerValue = upperValue/maxRange - } - } - - this.setState({ - minAmount: parseFloat(Number(lowerValue).toPrecision(lowerValue < 100 ? 2 : 3)), - maxAmount: parseFloat(Number(upperValue).toPrecision(upperValue < 100 ? 2 : 3)), - }); - } - - handlePaymentMethodChange=(value)=>{ - if (value.length > 50){ - this.setState({ - badPaymentMethod: true, - }); - }else{ - this.setState({ - payment_method: value.substring(0,53), - badPaymentMethod: value.length > 50, - }); - } - } - - handlePremiumChange=(e)=>{ - const { t } = this.props; - var max = 999; - var min = -100; - var premium = e.target.value - if(e.target.value > 999){ - var bad_premium = t("Must be less than {{max}}%", {max:max}) - } - if(e.target.value <= -100){ - var bad_premium = t("Must be more than {{min}}%", {min:min}) - } - - if (premium == ""){ - premium = 0 - } else { - premium = Number(Math.round(premium +"e"+ 2) + "e-" + 2) - } - this.setState({ - premium: premium, - badPremium: bad_premium, - }); - } - - handleSatoshisChange=(e)=>{ - const { t } = this.props; - if(e.target.value > this.state.maxTradeSats){ - var bad_sats = t("Must be less than {{maxSats}",{maxSats: pn(this.state.maxTradeSats)}) - } - if(e.target.value < this.state.minTradeSats){ - var bad_sats = t("Must be more than {{minSats}}",{minSats: pn(this.state.minTradeSats)}) - } - - this.setState({ - satoshis: e.target.value, - badSatoshis: bad_sats, - }); - } - handleClickRelative=(e)=>{ - this.setState({ - is_explicit: false, - }); - this.handlePremiumChange(); - } - - handleClickExplicit=(e)=>{ - if(!this.state.enableAmountRange){ - this.setState({ - is_explicit: true, - }); - this.handleSatoshisChange(); - } - } - - handleCreateOfferButtonPressed=()=>{ - this.state.amount == null ? this.setState({amount: 0}) : null; - const requestOptions = { - method: 'POST', - headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')}, - body: JSON.stringify({ - type: this.state.type, - currency: this.state.currency, - amount: this.state.has_range ? null : this.state.amount, - has_range: this.state.enableAmountRange, - min_amount: this.state.minAmount, - max_amount: this.state.maxAmount, - payment_method: this.state.payment_method === ""? this.defaultPaymentMethod: this.state.payment_method, - is_explicit: this.state.is_explicit, - premium: this.state.is_explicit ? null: (this.state.premium==""? 0:this.state.premium), - satoshis: this.state.is_explicit ? this.state.satoshis: null, - public_duration: this.state.publicDuration, - escrow_duration: this.state.escrowDuration, - bond_size: this.state.bondSize, - bondless_taker: this.state.allowBondless, - }), - }; - fetch("/api/make/",requestOptions) - .then((response) => response.json()) - .then((data) => (this.setState({badRequest:data.bad_request}) - & (data.id ? this.props.history.push('/order/' + data.id) :""))); - this.setState({openStoreToken:false}); - } - - getCurrencyCode(val){ - return currencyDict[val.toString()] - } - - handleInputBondSizeChange = (event) => { - this.setState({bondSize: event.target.value === '' ? 1 : Number(event.target.value)}); - }; - - priceNow = () => { - if (this.state.loadingLimits){ - return "..."; - } - else if (this.state.is_explicit & this.state.amount > 0 & this.state.satoshis > 0){ - return parseFloat(Number(this.state.amount / (this.state.satoshis/100000000)).toPrecision(5)); - } - else if (!this.state.is_explicit){ - var price = this.state.limits[this.state.currency]['price']; - return parseFloat(Number(price*(1+this.state.premium/100)).toPrecision(5)); - } - return "..."; - } - - StandardMakerOptions = () => { - const { t } = this.props; - return( - - -
    - - - {t("Buy or Sell Bitcoin?")} - - - - } checkedIcon={}/>} - label={this.state.type == 0 ? {t("Buy")}: {t("Buy")}} - labelPlacement="end" - /> - } checkedIcon={}/>} - label={this.state.type == 1 ? {t("Sell")}: {t("Sell")}} - labelPlacement="end" - /> - - -
    -
    - - -
    - - this.getMaxAmount()) & this.state.amount != "" ? true : false} - helperText={this.state.amount < this.getMinAmount() & this.state.amount != "" ? t("Must be more than {{minAmount}}",{minAmount:this.getMinAmount()}) - : (this.state.amount > this.getMaxAmount() & this.state.amount != "" ? t("Must be less than {{maxAmount}}",{maxAmount:this.getMaxAmount()}) : null)} - label={t("Amount")} - type="number" - required={true} - value={this.state.amount} - inputProps={{ - min:0 , - style: {textAlign:"center"} - }} - onChange={this.handleAmountChange} - /> - -
    -
    - -
    - -
    - - - - - - - - {t("Choose a Pricing Method")} - - - - } - label={t("Relative")} - labelPlacement="end" - onClick={this.handleClickRelative} - /> - - - } - label={t("Explicit")} - labelPlacement="end" - onClick={this.handleClickExplicit} - /> - - - - - {/* conditional shows either Premium % field or Satoshis field based on pricing method */} - -
    - -
    -
    - -
    - -
    -
    - -
    -
    - - - {(this.state.is_explicit ? t("Order rate:"): t("Order current rate:"))+" "+pn(this.priceNow())+" "+this.state.currencyCode+"/BTC"} - - -
    - - - - ) - } - - handleChangePublicDuration = (date) => { - let d = new Date(date), - hours = d.getHours(), - minutes = d.getMinutes(); - - var total_secs = hours*60*60 + minutes * 60; - - this.setState({ - publicExpiryTime: date, - publicDuration: total_secs, - }); - } - - handleChangeEscrowDuration = (date) => { - let d = new Date(date), - hours = d.getHours(), - minutes = d.getMinutes(); - - var total_secs = hours*60*60 + minutes * 60; - - this.setState({ - escrowExpiryTime: date, - escrowDuration: total_secs, - }); - } - - getMaxAmount = () => { - if (this.state.limits == null){ - var max_amount = null - }else{ - var max_amount = this.state.limits[this.state.currency]['max_amount']*(1+this.state.premium/100) - } - // times 0.98 to allow a bit of margin with respect to the backend minimum - return parseFloat(Number(max_amount*0.98).toPrecision(2)) - } - - getMinAmount = () => { - if (this.state.limits == null){ - var min_amount = null - }else{ - var min_amount = this.state.limits[this.state.currency]['min_amount']*(1+this.state.premium/100) - } - // times 1.1 to allow a bit of margin with respect to the backend minimum - return parseFloat(Number(min_amount*1.1).toPrecision(2)) - } - - RangeThumbComponent(props) { - const { children, ...other } = props; - return ( - - {children} - - - - - ); - } - - minAmountError=()=>{ - return this.state.minAmount < this.getMinAmount() || this.state.maxAmount < this.state.minAmount || this.state.minAmount < this.state.maxAmount/(this.maxRangeAmountMultiple+0.15) || this.state.minAmount*(this.minRangeAmountMultiple-0.1) > this.state.maxAmount - } - maxAmountError=()=>{ - return this.state.maxAmount > this.getMaxAmount() || this.state.maxAmount < this.state.minAmount || this.state.minAmount < this.state.maxAmount/(this.maxRangeAmountMultiple+0.15) || this.state.minAmount*(this.minRangeAmountMultiple-0.1) > this.state.maxAmount - } - - rangeText =()=> { - const { t } = this.props; - return ( -
    - {t("From")} - - {t("to")} - - {this.state.currencyCode} -
    - ) - + if (lowerValue > upperValue / minRange) { + if (activeThumb === 0) { + upperValue = minRange * lowerValue; + } else { + lowerValue = upperValue / minRange; + } + } else if (lowerValue < upperValue / maxRange) { + if (activeThumb === 0) { + upperValue = maxRange * lowerValue; + } else { + lowerValue = upperValue / maxRange; } - - AdvancedMakerOptions = () => { - const { t } = this.props; - return( - - - - - - - - - this.setState({enableAmountRange:e.target.checked, is_explicit: false}) & this.recalcBounds()}/> - {this.state.enableAmountRange & this.state.minAmount != null? this.rangeText() : t("Enable Amount Range")} - - -
    - -
    -
    - (parseFloat(Number(x).toPrecision(x < 100 ? 2 : 3))+" "+this.state.currencyCode)} - marks={this.state.limits == null? - null - : - [{value: this.getMinAmount(),label: this.getMinAmount()+" "+ this.state.currencyCode}, - {value: this.getMaxAmount(),label: this.getMaxAmount()+" "+this.state.currencyCode}]} - min={this.getMinAmount()} - max={this.getMaxAmount()} - onChange={this.handleRangeAmountChange} - /> -
    -
    -
    - - - - }> - {t("Expiry Timers")} - - - - - - - - ) - }} - renderInput={(props) => } - label={t("Public Duration (HH:mm)")} - value={this.state.publicExpiryTime} - onChange={this.handleChangePublicDuration} - minTime={new Date(0, 0, 0, 0, 10)} - maxTime={new Date(0, 0, 0, 23, 59)} - /> - - - - - - - - ) - }} - renderInput={(props) => } - label={t("Escrow Deposit Time-Out (HH:mm)")} - value={this.state.escrowExpiryTime} - onChange={this.handleChangeEscrowDuration} - minTime={new Date(0, 0, 0, 1, 0)} - maxTime={new Date(0, 0, 0, 8, 0)} - /> - - - - - - - - - - - - {t("Fidelity Bond Size")} - - - (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) => this.setState({bondSize: e.target.value})} - /> - - - - - - - this.setState({allowBondless: !this.state.allowBondless})} - /> - } - /> - - -
    -
    - ) } - makeOrderBox=()=>{ - const { t } = this.props; - return( - - - - this.setState({tabValue:0})}/> - this.setState({tabValue:1})}/> - - - -
    - {this.StandardMakerOptions()} -
    -
    - {this.AdvancedMakerOptions()} -
    -
    -
    - ) + this.setState({ + minAmount: parseFloat(Number(lowerValue).toPrecision(lowerValue < 100 ? 2 : 3)), + maxAmount: parseFloat(Number(upperValue).toPrecision(upperValue < 100 ? 2 : 3)), + }); + }; + + handlePaymentMethodChange = (value) => { + if (value.length > 50) { + this.setState({ + badPaymentMethod: true, + }); + } else { + this.setState({ + payment_method: value.substring(0, 53), + badPaymentMethod: value.length > 50, + }); } + }; + + handlePremiumChange = (e) => { + const { t } = this.props; + var max = 999; + var min = -100; + var premium = e.target.value; + if (e.target.value > 999) { + var bad_premium = t('Must be less than {{max}}%', { max: max }); + } + if (e.target.value <= -100) { + var bad_premium = t('Must be more than {{min}}%', { min: min }); + } + + if (premium == '') { + premium = 0; + } else { + premium = Number(Math.round(premium + 'e' + 2) + 'e-' + 2); + } + this.setState({ + premium: premium, + badPremium: bad_premium, + }); + }; + + handleSatoshisChange = (e) => { + const { t } = this.props; + if (e.target.value > this.state.maxTradeSats) { + var bad_sats = t('Must be less than {{maxSats}', { maxSats: pn(this.state.maxTradeSats) }); + } + if (e.target.value < this.state.minTradeSats) { + var bad_sats = t('Must be more than {{minSats}}', { minSats: pn(this.state.minTradeSats) }); + } + + this.setState({ + satoshis: e.target.value, + badSatoshis: bad_sats, + }); + }; + handleClickRelative = (e) => { + this.setState({ + is_explicit: false, + }); + this.handlePremiumChange(); + }; + + handleClickExplicit = (e) => { + if (!this.state.enableAmountRange) { + this.setState({ + is_explicit: true, + }); + this.handleSatoshisChange(); + } + }; + + handleCreateOfferButtonPressed = () => { + this.state.amount == null ? this.setState({ amount: 0 }) : null; + const requestOptions = { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, + body: JSON.stringify({ + type: this.state.type, + currency: this.state.currency, + amount: this.state.has_range ? null : this.state.amount, + has_range: this.state.enableAmountRange, + min_amount: this.state.minAmount, + max_amount: this.state.maxAmount, + payment_method: + this.state.payment_method === '' ? this.defaultPaymentMethod : this.state.payment_method, + is_explicit: this.state.is_explicit, + premium: this.state.is_explicit ? null : this.state.premium == '' ? 0 : this.state.premium, + satoshis: this.state.is_explicit ? this.state.satoshis : null, + public_duration: this.state.publicDuration, + escrow_duration: this.state.escrowDuration, + bond_size: this.state.bondSize, + bondless_taker: this.state.allowBondless, + }), + }; + fetch('/api/make/', requestOptions) + .then((response) => response.json()) + .then( + (data) => + this.setState({ badRequest: data.bad_request }) & + (data.id ? this.props.history.push('/order/' + data.id) : ''), + ); + this.setState({ openStoreToken: false }); + }; + + getCurrencyCode(val) { + return currencyDict[val.toString()]; + } + + handleInputBondSizeChange = (event) => { + this.setState({ bondSize: event.target.value === '' ? 1 : Number(event.target.value) }); + }; + + priceNow = () => { + if (this.state.loadingLimits) { + return '...'; + } else if (this.state.is_explicit & (this.state.amount > 0) & (this.state.satoshis > 0)) { + return parseFloat( + Number(this.state.amount / (this.state.satoshis / 100000000)).toPrecision(5), + ); + } else if (!this.state.is_explicit) { + var price = this.state.limits[this.state.currency]['price']; + return parseFloat(Number(price * (1 + this.state.premium / 100)).toPrecision(5)); + } + return '...'; + }; + + StandardMakerOptions = () => { + const { t } = this.props; + return ( + + +
    + + + {t('Buy or Sell Bitcoin?')} + + + + + } + checkedIcon={ + + } + /> + } + label={ + this.state.type == 0 ? ( + + {t('Buy')} + + ) : ( + {t('Buy')} + ) + } + labelPlacement='end' + /> + + } + checkedIcon={ + + } + /> + } + label={ + this.state.type == 1 ? ( + + {t('Sell')} + + ) : ( + {t('Sell')} + ) + } + labelPlacement='end' + /> + + +
    +
    + + +
    + + this.getMaxAmount()) & + (this.state.amount != '') + ? true + : false + } + helperText={ + (this.state.amount < this.getMinAmount()) & (this.state.amount != '') + ? t('Must be more than {{minAmount}}', { minAmount: this.getMinAmount() }) + : (this.state.amount > this.getMaxAmount()) & (this.state.amount != '') + ? t('Must be less than {{maxAmount}}', { maxAmount: this.getMaxAmount() }) + : null + } + label={t('Amount')} + type='number' + required={true} + value={this.state.amount} + inputProps={{ + min: 0, + style: { textAlign: 'center' }, + }} + onChange={this.handleAmountChange} + /> + +
    +
    + +
    +
    + + + + + + + + {t('Choose a Pricing Method')} + + + + } + label={t('Relative')} + labelPlacement='end' + onClick={this.handleClickRelative} + /> + + + } + label={t('Explicit')} + labelPlacement='end' + onClick={this.handleClickExplicit} + /> + + + + + {/* conditional shows either Premium % field or Satoshis field based on pricing method */} + +
    + +
    +
    + +
    + +
    +
    + +
    +
    + + + {(this.state.is_explicit ? t('Order rate:') : t('Order current rate:')) + + ' ' + + pn(this.priceNow()) + + ' ' + + this.state.currencyCode + + '/BTC'} + + +
    + + + + ); + }; + + handleChangePublicDuration = (date) => { + let d = new Date(date), + hours = d.getHours(), + minutes = d.getMinutes(); + + var total_secs = hours * 60 * 60 + minutes * 60; + + this.setState({ + publicExpiryTime: date, + publicDuration: total_secs, + }); + }; + + handleChangeEscrowDuration = (date) => { + let d = new Date(date), + hours = d.getHours(), + minutes = d.getMinutes(); + + var total_secs = hours * 60 * 60 + minutes * 60; + + this.setState({ + escrowExpiryTime: date, + escrowDuration: total_secs, + }); + }; + + getMaxAmount = () => { + if (this.state.limits == null) { + var max_amount = null; + } else { + var max_amount = + this.state.limits[this.state.currency]['max_amount'] * (1 + this.state.premium / 100); + } + // times 0.98 to allow a bit of margin with respect to the backend minimum + return parseFloat(Number(max_amount * 0.98).toPrecision(2)); + }; + + getMinAmount = () => { + if (this.state.limits == null) { + var min_amount = null; + } else { + var min_amount = + this.state.limits[this.state.currency]['min_amount'] * (1 + this.state.premium / 100); + } + // times 1.1 to allow a bit of margin with respect to the backend minimum + return parseFloat(Number(min_amount * 1.1).toPrecision(2)); + }; + + RangeThumbComponent(props) { + const { children, ...other } = props; + return ( + + {children} + + + + + ); + } + + minAmountError = () => { + return ( + this.state.minAmount < this.getMinAmount() || + this.state.maxAmount < this.state.minAmount || + this.state.minAmount < this.state.maxAmount / (this.maxRangeAmountMultiple + 0.15) || + this.state.minAmount * (this.minRangeAmountMultiple - 0.1) > this.state.maxAmount + ); + }; + maxAmountError = () => { + return ( + this.state.maxAmount > this.getMaxAmount() || + this.state.maxAmount < this.state.minAmount || + this.state.minAmount < this.state.maxAmount / (this.maxRangeAmountMultiple + 0.15) || + this.state.minAmount * (this.minRangeAmountMultiple - 0.1) > this.state.maxAmount + ); + }; + + rangeText = () => { + const { t } = this.props; + return ( +
    + {t('From')} + + {t('to')} + + {this.state.currencyCode} +
    + ); + }; + + AdvancedMakerOptions = () => { + const { t } = this.props; + return ( + + + + + + + + this.setState({ enableAmountRange: e.target.checked, is_explicit: false }) & + this.recalcBounds() + } + /> + {this.state.enableAmountRange & (this.state.minAmount != null) + ? this.rangeText() + : t('Enable Amount Range')} + + +
    + +
    +
    + + parseFloat(Number(x).toPrecision(x < 100 ? 2 : 3)) + + ' ' + + this.state.currencyCode + } + marks={ + this.state.limits == null + ? null + : [ + { + value: this.getMinAmount(), + label: this.getMinAmount() + ' ' + this.state.currencyCode, + }, + { + value: this.getMaxAmount(), + label: this.getMaxAmount() + ' ' + this.state.currencyCode, + }, + ] + } + min={this.getMinAmount()} + max={this.getMaxAmount()} + onChange={this.handleRangeAmountChange} + /> +
    +
    +
    + + + + }> + + {t('Expiry Timers')} + + + + + + + + + + ), + }} + renderInput={(props) => } + label={t('Public Duration (HH:mm)')} + value={this.state.publicExpiryTime} + onChange={this.handleChangePublicDuration} + minTime={new Date(0, 0, 0, 0, 10)} + maxTime={new Date(0, 0, 0, 23, 59)} + /> + + + + + + + + + ), + }} + renderInput={(props) => } + label={t('Escrow Deposit Time-Out (HH:mm)')} + value={this.state.escrowExpiryTime} + onChange={this.handleChangeEscrowDuration} + minTime={new Date(0, 0, 0, 1, 0)} + maxTime={new Date(0, 0, 0, 8, 0)} + /> + + + + + + + + + + + + {t('Fidelity Bond Size')}{' '} + + + + 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) => this.setState({ bondSize: e.target.value })} + /> + + + + + + this.setState({ allowBondless: !this.state.allowBondless })} + /> + } + /> + + +
    +
    + ); + }; + + makeOrderBox = () => { + const { t } = this.props; + return ( + + + + this.setState({ tabValue: 0 })} + /> + this.setState({ tabValue: 1 })} + /> + + + +
    + {this.StandardMakerOptions()} +
    +
    + {this.AdvancedMakerOptions()} +
    +
    +
    + ); + }; render() { const { t } = this.props; return ( - - {getCookie("robot_token") ? - this.setState({openStoreToken:false})} - onClickCopy={()=> (copyToClipboard(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))} - copyIconColor={this.props.copiedToken ? "inherit" : "primary"} - onClickBack={() => this.setState({openStoreToken:false})} - onClickDone={this.handleCreateOfferButtonPressed} - /> - : - this.setState({openStoreToken:false})} - /> + + {getCookie('robot_token') ? ( + this.setState({ openStoreToken: false })} + onClickCopy={() => + copyToClipboard(getCookie('robot_token')) & + this.props.setAppState({ copiedToken: true }) } + copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'} + onClickBack={() => this.setState({ openStoreToken: false })} + onClickDone={this.handleCreateOfferButtonPressed} + /> + ) : ( + this.setState({ openStoreToken: false })} + /> + )} - - {this.makeOrderBox()} - - - - {/* conditions to disable the make button */} - {(this.state.type == null || - this.state.amount == null & (this.state.enableAmountRange == false || this.state.loadingLimits) || - this.state.enableAmountRange & (this.minAmountError() || this.maxAmountError()) || - this.state.amount <= 0 & !this.state.enableAmountRange || - (this.state.is_explicit & (this.state.badSatoshis != null || this.state.satoshis == null)) || - (!this.state.is_explicit & this.state.badPremium != null)) - ? - -
    -
    - : - - } - -
    - - {this.state.badRequest ? - - {this.state.badRequest}
    -
    - : ""} - -
    - {this.state.type==null ? - t("Create an order for ") - : - (this.state.type==0 ? - t("Create a BTC buy order for ") - : - t("Create a BTC sell order for ") - ) - } - {this.state.enableAmountRange & this.state.minAmount != null? - this.state.minAmount+"-"+this.state.maxAmount - : - pn(this.state.amount)} - {" " + this.state.currencyCode} - {this.state.is_explicit ? - t(" of {{satoshis}} Satoshis",{satoshis: pn(this.state.satoshis)}) - : - (this.state.premium == 0 ? t(" at market price") : - (this.state.premium > 0 ? - t(" at a {{premium}}% premium", {premium: this.state.premium}) - : - t(" at a {{discount}}% discount", {discount: -this.state.premium})) - ) - } -
    -
    - - - -
    + + {this.makeOrderBox()} + + + {/* conditions to disable the make button */} + {this.state.type == null || + (this.state.amount == null) & + (this.state.enableAmountRange == false || this.state.loadingLimits) || + this.state.enableAmountRange & (this.minAmountError() || this.maxAmountError()) || + (this.state.amount <= 0) & !this.state.enableAmountRange || + this.state.is_explicit & + (this.state.badSatoshis != null || this.state.satoshis == null) || + !this.state.is_explicit & (this.state.badPremium != null) ? ( + +
    + +
    +
    + ) : ( + + )} +
    + + {this.state.badRequest ? ( + + {this.state.badRequest}
    +
    + ) : ( + '' + )} + +
    + {this.state.type == null + ? t('Create an order for ') + : this.state.type == 0 + ? t('Create a BTC buy order for ') + : t('Create a BTC sell order for ')} + {this.state.enableAmountRange & (this.state.minAmount != null) + ? this.state.minAmount + '-' + this.state.maxAmount + : pn(this.state.amount)} + {' ' + this.state.currencyCode} + {this.state.is_explicit + ? t(' of {{satoshis}} Satoshis', { satoshis: pn(this.state.satoshis) }) + : this.state.premium == 0 + ? t(' at market price') + : this.state.premium > 0 + ? t(' at a {{premium}}% premium', { premium: this.state.premium }) + : t(' at a {{discount}}% discount', { discount: -this.state.premium })} +
    +
    + + + +
    +
    ); } } diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index b844473d..61289e79 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -1,16 +1,44 @@ -import React, { Component } from "react"; -import { withTranslation} from "react-i18next"; -import {TextField,Chip, Tooltip, IconButton, Badge, Tab, Tabs, Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material" +import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; +import { + TextField, + Chip, + Tooltip, + IconButton, + Badge, + Tab, + Tabs, + Alert, + Paper, + CircularProgress, + Button, + Grid, + Typography, + List, + ListItem, + ListItemIcon, + ListItemText, + ListItemAvatar, + Avatar, + Divider, + Box, + LinearProgress, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from '@mui/material'; import Countdown, { zeroPad } from 'react-countdown'; -import { StoreTokenDialog, NoRobotDialog } from "./Dialogs"; +import { StoreTokenDialog, NoRobotDialog } from './Dialogs'; import currencyDict from '../../static/assets/currencies.json'; -import PaymentText from './PaymentText' -import TradeBox from "./TradeBox"; -import FlagWithProps from './FlagWithProps' +import PaymentText from './PaymentText'; +import TradeBox from './TradeBox'; +import FlagWithProps from './FlagWithProps'; import LinearDeterminate from './LinearDeterminate'; -import MediaQuery from 'react-responsive' -import { t } from "i18next"; +import MediaQuery from 'react-responsive'; +import { t } from 'i18next'; // icons import AccessTimeIcon from '@mui/icons-material/AccessTime'; @@ -20,63 +48,63 @@ import PaymentsIcon from '@mui/icons-material/Payments'; import ArticleIcon from '@mui/icons-material/Article'; import HourglassTopIcon from '@mui/icons-material/HourglassTop'; import CheckIcon from '@mui/icons-material/Check'; -import { SendReceiveIcon } from "./Icons"; +import { SendReceiveIcon } from './Icons'; -import { getCookie } from "../utils/cookies"; -import { pn } from "../utils/prettyNumbers"; -import { copyToClipboard } from "../utils/clipboard"; -import { getWebln } from "../utils/webln"; +import { getCookie } from '../utils/cookies'; +import { pn } from '../utils/prettyNumbers'; +import { copyToClipboard } from '../utils/clipboard'; +import { getWebln } from '../utils/webln'; class OrderPage extends Component { constructor(props) { super(props); this.state = { - is_explicit: false, - delay: 60000, // Refresh every 60 seconds by default - total_secs_exp: 300, - loading: true, - openCancel: false, - openCollaborativeCancel: false, - openInactiveMaker: false, - openWeblnDialog: false, - waitingWebln: false, - openStoreToken: false, - tabValue: 1, - orderId: this.props.match.params.orderId, + is_explicit: false, + delay: 60000, // Refresh every 60 seconds by default + total_secs_exp: 300, + loading: true, + openCancel: false, + openCollaborativeCancel: false, + openInactiveMaker: false, + openWeblnDialog: false, + waitingWebln: false, + openStoreToken: false, + tabValue: 1, + orderId: this.props.match.params.orderId, }; // Refresh delays according to Order status this.statusToDelay = { - "0": 2000, //'Waiting for maker bond' - "1": 25000, //'Public' - "2": 90000, //'Paused' - "3": 2000, //'Waiting for taker bond' - "4": 999999, //'Cancelled' - "5": 999999, //'Expired' - "6": 6000, //'Waiting for trade collateral and buyer invoice' - "7": 8000, //'Waiting only for seller trade collateral' - "8": 8000, //'Waiting only for buyer invoice' - "9": 10000, //'Sending fiat - In chatroom' - "10": 10000, //'Fiat sent - In chatroom' - "11": 30000, //'In dispute' - "12": 999999, //'Collaboratively cancelled' - "13": 3000, //'Sending satoshis to buyer' - "14": 999999, //'Sucessful trade' - "15": 10000, //'Failed lightning network routing' - "16": 180000, //'Wait for dispute resolution' - "17": 180000, //'Maker lost dispute' - "18": 180000, //'Taker lost dispute' - } + 0: 2000, //'Waiting for maker bond' + 1: 25000, //'Public' + 2: 90000, //'Paused' + 3: 2000, //'Waiting for taker bond' + 4: 999999, //'Cancelled' + 5: 999999, //'Expired' + 6: 6000, //'Waiting for trade collateral and buyer invoice' + 7: 8000, //'Waiting only for seller trade collateral' + 8: 8000, //'Waiting only for buyer invoice' + 9: 10000, //'Sending fiat - In chatroom' + 10: 10000, //'Fiat sent - In chatroom' + 11: 30000, //'In dispute' + 12: 999999, //'Collaboratively cancelled' + 13: 3000, //'Sending satoshis to buyer' + 14: 999999, //'Sucessful trade' + 15: 10000, //'Failed lightning network routing' + 16: 180000, //'Wait for dispute resolution' + 17: 180000, //'Maker lost dispute' + 18: 180000, //'Taker lost dispute' + }; } - completeSetState=(newStateVars)=>{ + completeSetState = (newStateVars) => { // In case the reply only has "bad_request" // Do not substitute these two for "undefined" as // otherStateVars will fail to assign values - if (newStateVars.currency == null){ - newStateVars.currency = this.state.currency - newStateVars.amount = this.state.amount - newStateVars.status = this.state.status + if (newStateVars.currency == null) { + newStateVars.currency = this.state.currency; + newStateVars.amount = this.state.amount; + newStateVars.status = this.state.status; } var otherStateVars = { @@ -85,25 +113,27 @@ class OrderPage extends Component { delay: this.setDelay(newStateVars.status), currencyCode: this.getCurrencyCode(newStateVars.currency), penalty: newStateVars.penalty, // in case penalty time has finished, it goes back to null - invoice_expired: newStateVars.invoice_expired // in case invoice had expired, it goes back to null when it is valid again + invoice_expired: newStateVars.invoice_expired, // in case invoice had expired, it goes back to null when it is valid again }; var completeStateVars = Object.assign({}, newStateVars, otherStateVars); this.setState(completeStateVars); - } + }; - getOrderDetails =(id)=> { - this.setState({orderId:id}) + getOrderDetails = (id) => { + this.setState({ orderId: id }); fetch('/api/order' + '?order_id=' + id) .then((response) => response.json()) .then(this.orderDetailsReceived); - } + }; - orderDetailsReceived = (data) =>{ - if (data.status !== this.state.status) { this.handleWebln(data) } - this.completeSetState(data) - this.setState({pauseLoading:false}) - } + orderDetailsReceived = (data) => { + if (data.status !== this.state.status) { + this.handleWebln(data); + } + this.completeSetState(data); + this.setState({ pauseLoading: false }); + }; // These are used to refresh the data componentDidMount() { @@ -121,78 +151,92 @@ class OrderPage extends Component { } tick = () => { this.getOrderDetails(this.state.orderId); - } + }; handleWebln = async (data) => { const webln = await getWebln(); // If Webln implements locked payments compatibility, this logic might be simplier - if (data.is_maker & data.status == 0) { + if (data.is_maker & (data.status == 0)) { webln.sendPayment(data.bond_invoice); - this.setState({ waitingWebln: true, openWeblnDialog: true}); - } else if (data.is_taker & data.status == 3) { + this.setState({ waitingWebln: true, openWeblnDialog: true }); + } else if (data.is_taker & (data.status == 3)) { webln.sendPayment(data.bond_invoice); - this.setState({ waitingWebln: true, openWeblnDialog: true}); - } else if (data.is_seller & (data.status == 6 || data.status == 7 )) { + this.setState({ waitingWebln: true, openWeblnDialog: true }); + } else if (data.is_seller & (data.status == 6 || data.status == 7)) { webln.sendPayment(data.escrow_invoice); - this.setState({ waitingWebln: true, openWeblnDialog: true}); - } else if (data.is_buyer & (data.status == 6 || data.status == 8 )) { - this.setState({ waitingWebln: true, openWeblnDialog: true}); - webln.makeInvoice(data.trade_satoshis) + this.setState({ waitingWebln: true, openWeblnDialog: true }); + } else if (data.is_buyer & (data.status == 6 || data.status == 8)) { + this.setState({ waitingWebln: true, openWeblnDialog: true }); + webln + .makeInvoice(data.trade_satoshis) .then((invoice) => { if (invoice) { this.sendWeblnInvoice(invoice.paymentRequest); this.setState({ waitingWebln: false, openWeblnDialog: false }); } - }).catch(() => { + }) + .catch(() => { this.setState({ waitingWebln: false, openWeblnDialog: false }); }); } else { this.setState({ waitingWebln: false }); } - } + }; sendWeblnInvoice = (invoice) => { const requestOptions = { method: 'POST', - headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ - 'action':'update_invoice', - 'invoice': invoice, + action: 'update_invoice', + invoice: invoice, }), }; fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) - .then((response) => response.json()) - .then((data) => this.completeSetState(data)); - } + .then((response) => response.json()) + .then((data) => this.completeSetState(data)); + }; // Countdown Renderer callback with condition countdownRenderer = ({ total, hours, minutes, seconds, completed }) => { const { t } = this.props; - if (completed) { - // Render a completed state - return ( {t("The order has expired")}); - - } else { - var col = 'inherit' - var fraction_left = (total/1000) / this.state.total_secs_exp - // Make orange at 25% of time left - if (fraction_left < 0.25){col = 'orange'} - // Make red at 10% of time left - if (fraction_left < 0.1){col = 'red'} - // Render a countdown, bold when less than 25% - return ( - fraction_left < 0.25 ? {hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s - :{hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s - ); - } + if (completed) { + // Render a completed state + return {t('The order has expired')}; + } else { + var col = 'inherit'; + var fraction_left = total / 1000 / this.state.total_secs_exp; + // Make orange at 25% of time left + if (fraction_left < 0.25) { + col = 'orange'; + } + // Make red at 10% of time left + if (fraction_left < 0.1) { + col = 'red'; + } + // Render a countdown, bold when less than 25% + return fraction_left < 0.25 ? ( + + + {hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s{' '} + + + ) : ( + + {hours}h {zeroPad(minutes)}m {zeroPad(seconds)}s{' '} + + ); + } }; - timerRenderer(seconds){ - var hours = parseInt(seconds/3600); - var minutes = parseInt((seconds-hours*3600)/60); - return( - {hours>0 ? hours+"h":""} {minutes>0 ? zeroPad(minutes)+"m":""} - ) + timerRenderer(seconds) { + var hours = parseInt(seconds / 3600); + var minutes = parseInt((seconds - hours * 3600) / 60); + return ( + + {hours > 0 ? hours + 'h' : ''} {minutes > 0 ? zeroPad(minutes) + 'm' : ''}{' '} + + ); } // Countdown Renderer callback with condition @@ -200,555 +244,840 @@ class OrderPage extends Component { const { t } = this.props; if (completed) { // Render a completed state - return ( {t("Penalty lifted, good to go!")}); - + return {t('Penalty lifted, good to go!')}; } else { return ( - {t("You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s",{timeMin: zeroPad(minutes), timeSec: zeroPad(seconds) })} + + {' '} + {t('You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s', { + timeMin: zeroPad(minutes), + timeSec: zeroPad(seconds), + })}{' '} + ); } - }; + }; handleTakeAmountChange = (e) => { - if (e.target.value != "" & e.target.value != null){ - this.setState({takeAmount: parseFloat(e.target.value)}) - }else{ - this.setState({takeAmount: e.target.value}) + if ((e.target.value != '') & (e.target.value != null)) { + this.setState({ takeAmount: parseFloat(e.target.value) }); + } else { + this.setState({ takeAmount: e.target.value }); } - } + }; - amountHelperText=()=>{ + amountHelperText = () => { const { t } = this.props; - if(this.state.takeAmount < this.state.min_amount & this.state.takeAmount != ""){ - return t("Too low") - }else if (this.state.takeAmount > this.state.max_amount & this.state.takeAmount != ""){ - return t("Too high") - }else{ - return null + if ((this.state.takeAmount < this.state.min_amount) & (this.state.takeAmount != '')) { + return t('Too low'); + } else if ((this.state.takeAmount > this.state.max_amount) & (this.state.takeAmount != '')) { + return t('Too high'); + } else { + return null; } - } + }; takeOrderButton = () => { const { t } = this.props; - if(this.state.has_range){ - return( - + if (this.state.has_range) { + return ( + {this.InactiveMakerDialog()} {this.tokenDialog()} -
    - - - this.state.max_amount) & this.state.takeAmount != "" } +
    + + + this.state.max_amount) & + (this.state.takeAmount != '') + } helperText={this.amountHelperText()} - label={t("Amount {{currencyCode}}", {currencyCode: this.state.currencyCode})} - size="small" - type="number" + label={t('Amount {{currencyCode}}', { currencyCode: this.state.currencyCode })} + size='small' + type='number' required={true} value={this.state.takeAmount} inputProps={{ - min:this.state.min_amount , - max:this.state.max_amount , - style: {textAlign:"center"} + min: this.state.min_amount, + max: this.state.max_amount, + style: { textAlign: 'center' }, }} onChange={this.handleTakeAmountChange} - /> - - + /> + +
    -
    this.state.max_amount || this.state.takeAmount == "" || this.state.takeAmount == null) ? '':'none'}}> - +
    this.state.max_amount || + this.state.takeAmount == '' || + this.state.takeAmount == null + ? '' + : 'none', + }} + > + -
    -
    this.state.max_amount || this.state.takeAmount == "" || this.state.takeAmount == null) ? 'none':''}}> - -
    - ) - }else{ - return( + ); + } else { + return ( <> - {this.InactiveMakerDialog()} - {this.tokenDialog()} - + {this.InactiveMakerDialog()} + {this.tokenDialog()} + - ) + ); } - } + }; countdownTakeOrderRenderer = ({ seconds, completed }) => { - if(isNaN(seconds)){return (this.takeOrderButton())} + if (isNaN(seconds)) { + return this.takeOrderButton(); + } if (completed) { // Render a completed state return this.takeOrderButton(); - } else{ - return( -
    - -
    ) + } else { + return ( + +
    + +
    +
    + ); } }; - takeOrder=()=>{ - this.setState({loading:true}) + takeOrder = () => { + this.setState({ loading: true }); const requestOptions = { - method: 'POST', - headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, - body: JSON.stringify({ - 'action':'take', - 'amount':this.state.takeAmount, - }), - }; - fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) - .then((response) => response.json()) - .then((data) => this.handleWebln(data) & this.completeSetState(data)); - } - - // set delay to the one matching the order status. If null order status, delay goes to 9999999. - setDelay = (status)=>{ - return status >= 0 ? this.statusToDelay[status.toString()] : 99999999; - } - - getCurrencyCode(val){ - let code = val ? currencyDict[val.toString()] : "" - return code - } - - handleClickConfirmCancelButton=()=>{ - this.setState({loading:true}) - const requestOptions = { - method: 'POST', - headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, - body: JSON.stringify({ - 'action':'cancel', - }), + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, + body: JSON.stringify({ + action: 'take', + amount: this.state.takeAmount, + }), }; fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) - .then((response) => response.json()) - .then(() => (this.getOrderDetails(this.state.orderId) & this.setState({status:4}))); - this.handleClickCloseConfirmCancelDialog(); + .then((response) => response.json()) + .then((data) => this.handleWebln(data) & this.completeSetState(data)); + }; + + // set delay to the one matching the order status. If null order status, delay goes to 9999999. + setDelay = (status) => { + return status >= 0 ? this.statusToDelay[status.toString()] : 99999999; + }; + + getCurrencyCode(val) { + let code = val ? currencyDict[val.toString()] : ''; + return code; } + handleClickConfirmCancelButton = () => { + this.setState({ loading: true }); + const requestOptions = { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, + body: JSON.stringify({ + action: 'cancel', + }), + }; + fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) + .then((response) => response.json()) + .then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 })); + this.handleClickCloseConfirmCancelDialog(); + }; + handleClickOpenConfirmCancelDialog = () => { - this.setState({openCancel: true}); + this.setState({ openCancel: true }); }; handleClickCloseConfirmCancelDialog = () => { - this.setState({openCancel: false}); + this.setState({ openCancel: false }); }; - CancelDialog =() =>{ + CancelDialog = () => { const { t } = this.props; - return( + return ( - - {t("Cancel the order?")} - + {t('Cancel the order?')} - - {t("If the order is cancelled now you will lose your bond.")} + + {t('If the order is cancelled now you will lose your bond.')} - - + + - ) - } + ); + }; handleClickOpenInactiveMakerDialog = () => { - this.setState({openInactiveMaker: true}); + this.setState({ openInactiveMaker: true }); }; handleClickCloseInactiveMakerDialog = () => { - this.setState({openInactiveMaker: false}); + this.setState({ openInactiveMaker: false }); }; - InactiveMakerDialog =() =>{ + InactiveMakerDialog = () => { const { t } = this.props; - return( + return ( - - {t("The maker is away")} - + {t('The maker is away')} - - {t("By taking this order you risk wasting your time. If the maker does not proceed in time, you will be compensated in satoshis for 50% of the maker bond.")} + + {t( + 'By taking this order you risk wasting your time. If the maker does not proceed in time, you will be compensated in satoshis for 50% of the maker bond.', + )} - - + + - ) - } + ); + }; - tokenDialog = () =>{ - return(getCookie("robot_token") ? + tokenDialog = () => { + return getCookie('robot_token') ? ( this.setState({openStoreToken:false})} - onClickCopy={()=> (copyToClipboard(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))} - copyIconColor={this.props.copiedToken ? "inherit" : "primary"} - onClickBack={() => this.setState({openStoreToken:false})} - onClickDone={() => this.setState({openStoreToken:false}) & - (this.state.maker_status=='Inactive' ? - this.handleClickOpenInactiveMakerDialog() + onClose={() => this.setState({ openStoreToken: false })} + onClickCopy={() => + copyToClipboard(getCookie('robot_token')) & this.props.setAppState({ copiedToken: true }) + } + copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'} + onClickBack={() => this.setState({ openStoreToken: false })} + onClickDone={() => + this.setState({ openStoreToken: false }) & + (this.state.maker_status == 'Inactive' + ? this.handleClickOpenInactiveMakerDialog() : this.takeOrder()) - }/> - : + } + /> + ) : ( this.setState({openStoreToken:false})} - /> - ) - } + onClose={() => this.setState({ openStoreToken: false })} + /> + ); + }; - handleClickConfirmCollaborativeCancelButton=()=>{ - const requestOptions = { - method: 'POST', - headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, - body: JSON.stringify({ - 'action':'cancel', - }), - }; - fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) + handleClickConfirmCollaborativeCancelButton = () => { + const requestOptions = { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, + body: JSON.stringify({ + action: 'cancel', + }), + }; + fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) .then((response) => response.json()) - .then(() => (this.getOrderDetails(this.state.orderId) & this.setState({status:4}))); + .then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 })); this.handleClickCloseCollaborativeCancelDialog(); - } + }; handleClickOpenCollaborativeCancelDialog = () => { - this.setState({openCollaborativeCancel: true}); + this.setState({ openCollaborativeCancel: true }); }; handleClickCloseCollaborativeCancelDialog = () => { - this.setState({openCollaborativeCancel: false}); + this.setState({ openCollaborativeCancel: false }); }; - CollaborativeCancelDialog =() =>{ + CollaborativeCancelDialog = () => { const { t } = this.props; - return( + return ( - - {t("Collaborative cancel the order?")} - + {t('Collaborative cancel the order?')} - - {t("The trade escrow has been posted. The order can be cancelled only if both, maker and taker, agree to cancel.")} + + {t( + 'The trade escrow has been posted. The order can be cancelled only if both, maker and taker, agree to cancel.', + )} - - + + - ) - } + ); + }; BackButton = () => { const { t } = this.props; // If order has expired, show back button. - if (this.state.status == 5){ - return( - - + if (this.state.status == 5) { + return ( + + - )} - return(null) - } + ); + } + return null; + }; CancelButton = () => { const { t } = this.props; // If maker and Waiting for Bond. Or if taker and Waiting for bond. // Simply allow to cancel without showing the cancel dialog. - if ((this.state.is_maker & [0,1,2].includes(this.state.status)) || this.state.is_taker & this.state.status == 3){ - return( - - + if ( + this.state.is_maker & [0, 1, 2].includes(this.state.status) || + this.state.is_taker & (this.state.status == 3) + ) { + return ( + + - )} + ); + } // If the order does not yet have an escrow deposited. Show dialog // to confirm forfeiting the bond - if ([3,6,7].includes(this.state.status)){ - return( -
    - + if ([3, 6, 7].includes(this.state.status)) { + return ( +
    + {this.CancelDialog()} - +
    - )} + ); + } // If the escrow is Locked, show the collaborative cancel button. - if ([8,9].includes(this.state.status)){ - return( - + if ([8, 9].includes(this.state.status)) { + return ( + {this.CollaborativeCancelDialog()} - + - )} + ); + } // If none of the above do not return a cancel button. - return(null) - } + return null; + }; // Colors for the status badges - statusBadgeColor(status){ - if(status=='Active'){return("success")} - if(status=='Seen recently'){return("warning")} - if(status=='Inactive'){return('error')} + statusBadgeColor(status) { + if (status == 'Active') { + return 'success'; + } + if (status == 'Seen recently') { + return 'warning'; + } + if (status == 'Inactive') { + return 'error'; + } } - orderBox=()=>{ + orderBox = () => { const { t } = this.props; - return( - - + return ( + + - - {t("Order Box")} + + {t('Order Box')} - - - - - - - {!this.state.type ? : }
    }> - - - -
    - - - - - {this.state.is_participant ? - <> - {this.state.taker_nick!='None' ? - <> - - - - - - - {this.state.type ? : }
    }> - + + + + + + + {' '} + {!this.state.type ? ( + - - - - - - : - "" + ) : ( + + )} +
    + } + > + + + + + + + secondary={t('Order maker')} + align='right' + /> + + + {this.state.is_participant ? ( + <> + {this.state.taker_nick != 'None' ? ( + <> + + + + + + + + {' '} + {this.state.type ? ( + + ) : ( + + )} +
    + } + > + + + + + + + + ) : ( + '' + )} + + + - + - - - - - : - } - - - -
    - -
    -
    - {this.state.has_range & this.state.amount == null ? - - : - - } - -
    - - - - - - - } secondary={this.state.currency==1000 ? t("Swap destination"):t("Accepted payment methods")}/> - - - - {/* If there is live Price and Premium data, show it. Otherwise show the order maker settings */} - - - - - {this.state.price_now? - - : - (this.state.is_explicit ? - - : - - ) - } - - - - - - - - - - - - - - - - - - + primary={t(this.state.status_message)} + secondary={t('Order status')} + /> + + + + ) : ( + + + + )} + + + +
    + +
    +
    + {this.state.has_range & (this.state.amount == null) ? ( + + ) : ( + + )} +
    + + + + + + + + } + secondary={ + this.state.currency == 1000 + ? t('Swap destination') + : t('Accepted payment methods') + } + /> + + + + {/* If there is live Price and Premium data, show it. Otherwise show the order maker settings */} + + + + + {this.state.price_now ? ( + + ) : this.state.is_explicit ? ( + + ) : ( + + )} + + + + + + + + + + + + + + + + + + + + + -
    - - - {/* if order is in a status that does not expire, do not show countdown */} - {[4,12,13,14,15,16,17,18].includes(this.state.status)? null : - <> - - - - - - - - - - - - } + + {/* if order is in a status that does not expire, do not show countdown */} + {[4, 12, 13, 14, 15, 16, 17, 18].includes(this.state.status) ? null : ( + <> + + + + + + + + + + + + )} {/* If the user has a penalty/limit */} - {this.state.penalty ? - <> - - - - - - - - : null} + {this.state.penalty ? ( + <> + + + + + + + + ) : null} {/* If the counterparty asked for collaborative cancel */} - {this.state.pending_cancel ? - <> - - - - {t("{{nickname}} is asking for a collaborative cancel", {nickname: this.state.is_maker ? this.state.taker_nick : this.state.maker_nick})} - - - - : null} + {this.state.pending_cancel ? ( + <> + + + + {t('{{nickname}} is asking for a collaborative cancel', { + nickname: this.state.is_maker ? this.state.taker_nick : this.state.maker_nick, + })} + + + + ) : null} {/* If the user has asked for a collaborative cancel */} - {this.state.asked_for_cancel ? - <> - - - - {t("You asked for a collaborative cancellation")} - - - - : null} - + {this.state.asked_for_cancel ? ( + <> + + + + {t('You asked for a collaborative cancellation')} + + + + ) : null}
    - + {/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */} - {this.state.is_participant ? + {this.state.is_participant ? ( <> {this.CancelButton()} {this.BackButton()} - : + ) : ( - - + + - - + + - } + )} - ) - } + ); + }; - doubleOrderPageDesktop=()=>{ - return( - - - {this.orderBox()} + doubleOrderPageDesktop = () => { + return ( + + + {this.orderBox()} - - + + - ) - } + ); + }; a11yProps(index) { return { @@ -757,102 +1086,117 @@ class OrderPage extends Component { }; } - doubleOrderPagePhone=()=>{ + doubleOrderPagePhone = () => { const { t } = this.props; - return( - + return ( + - - this.setState({tabValue:0})}/> - this.setState({tabValue:1})}/> + + this.setState({ tabValue: 0 })} + /> + this.setState({ tabValue: 1 })} + /> - -
    - {this.orderBox()} + +
    + {this.orderBox()}
    -
    - +
    +
    - ); - } + ); + }; - orderDetailsPage (){ + orderDetailsPage() { const { t } = this.props; - return( - this.state.bad_request ? -
    - - {/* IMPLEMENT I18N for bad_request */} - {t(this.state.bad_request)}
    -
    - -
    - : - (this.state.is_participant ? - <> - {this.weblnDialog()} - {/* Desktop View */} - - {this.doubleOrderPageDesktop()} - + return this.state.bad_request ? ( +
    + + {/* IMPLEMENT I18N for bad_request */} + {t(this.state.bad_request)} +
    +
    + +
    + ) : this.state.is_participant ? ( + <> + {this.weblnDialog()} + {/* Desktop View */} + {this.doubleOrderPageDesktop()} - {/* SmarPhone View */} - - {this.doubleOrderPagePhone()} - - - : - - {this.orderBox()} - ) - ) + {/* SmarPhone View */} + {this.doubleOrderPagePhone()} + + ) : ( + + {this.orderBox()} + + ); } handleCloseWeblnDialog = () => { - this.setState({openWeblnDialog: false}); - } + this.setState({ openWeblnDialog: false }); + }; - weblnDialog =() =>{ + weblnDialog = () => { const { t } = this.props; - return( + return ( - - {t("WebLN")} - + {t('WebLN')} - - {this.state.waitingWebln ? + + {this.state.waitingWebln ? ( <> - - {this.state.is_buyer ? t("Invoice not received, please check your WebLN wallet.") : t("Payment not received, please check your WebLN wallet.")} - - : <> - - {t("You can close now your WebLN wallet popup.")} - - } + + {this.state.is_buyer + ? t('Invoice not received, please check your WebLN wallet.') + : t('Payment not received, please check your WebLN wallet.')} + + ) : ( + <> + + {t('You can close now your WebLN wallet popup.')} + + )} - + - ) - } + ); + }; - render (){ + render() { return ( // Only so nothing shows while requesting the first batch of data this.state.loading ? : this.orderDetailsPage() diff --git a/frontend/src/components/PaymentText.js b/frontend/src/components/PaymentText.js index da4f3e59..2506be50 100644 --- a/frontend/src/components/PaymentText.js +++ b/frontend/src/components/PaymentText.js @@ -1,60 +1,107 @@ -import React, { Component } from 'react' -import { withTranslation } from "react-i18next"; -import PaymentIcon from './payment-methods/Icons' -import {Tooltip} from "@mui/material" -import { paymentMethods, swapDestinations } from "./payment-methods/Methods"; +import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; +import PaymentIcon from './payment-methods/Icons'; +import { Tooltip } from '@mui/material'; +import { paymentMethods, swapDestinations } from './payment-methods/Methods'; -const ns = [{name: "not specified",icon:'notspecified'}]; +const ns = [{ name: 'not specified', icon: 'notspecified' }]; const methods = ns.concat(swapDestinations).concat(paymentMethods); class PaymentText extends Component { - constructor(props) { - super(props); + constructor(props) { + super(props); + } + + parseText() { + const { t } = this.props; + var rows = []; + var custom_methods = this.props.text; + // Adds icons for each PaymentMethod that matches + methods.forEach((method, i) => { + if (this.props.text.includes(method.name)) { + custom_methods = custom_methods.replace(method.name, ''); + rows.push( + +
    + +
    +
    , + ); + } + }); + + // Adds a Custom icon if there are words that do not match + var chars_left = custom_methods + .replace(' ', '') + .replace(' ', '') + .replace(' ', '') + .replace(' ', '') + .replace(' ', ''); + + if (chars_left.length > 0) { + rows.push( + +
    + +
    +
    , + ); } - parseText(){ - const { t } = this.props; - var rows = []; - var custom_methods = this.props.text; - // Adds icons for each PaymentMethod that matches - methods.forEach((method, i) =>{ - if(this.props.text.includes(method.name)){ - custom_methods = custom_methods.replace(method.name,'') - rows.push( - -
    - -
    -
    - ); - } - }) - - // Adds a Custom icon if there are words that do not match - var chars_left = custom_methods.replace(' ','').replace(' ','').replace(' ','').replace(' ','').replace(' ','') - - if(chars_left.length > 0){rows.push( - -
    - -
    -
    - )} - - if(this.props.verbose){ - return (<>{rows}
    {custom_methods}
    ) - }else{ - return rows - } - } - - render() { + if (this.props.verbose) { return ( -
    - {this.parseText()} -
    - ) + <> + {rows}{' '} +
    + {' '} + {custom_methods} +
    + + ); + } else { + return rows; } + } + + render() { + return ( +
    + {this.parseText()} +
    + ); + } } export default withTranslation()(PaymentText); diff --git a/frontend/src/components/RangeSlider.js b/frontend/src/components/RangeSlider.js index a0bcc715..71be1941 100644 --- a/frontend/src/components/RangeSlider.js +++ b/frontend/src/components/RangeSlider.js @@ -1,4 +1,4 @@ -import { Slider } from "@mui/material" +import { Slider } from '@mui/material'; import { styled } from '@mui/material/styles'; const RangeSlider = styled(Slider)(({ theme }) => ({ @@ -6,15 +6,15 @@ const RangeSlider = styled(Slider)(({ theme }) => ({ height: 3, padding: '13px 0', '& .MuiSlider-thumb': { - height: `${27/16}em`, - width: `${27/16}em`, + height: `${27 / 16}em`, + width: `${27 / 16}em`, backgroundColor: '#fff', border: '1px solid currentColor', '&:hover': { boxShadow: '0 0 0 8px rgba(58, 133, 137, 0.16)', }, '& .range-bar': { - height: `${9/16}em`, + height: `${9 / 16}em`, width: 1, backgroundColor: 'currentColor', marginLeft: 1, @@ -22,13 +22,13 @@ const RangeSlider = styled(Slider)(({ theme }) => ({ }, }, '& .MuiSlider-track': { - height: `${3/16}em`, + height: `${3 / 16}em`, }, '& .MuiSlider-rail': { color: theme.palette.mode === 'dark' ? '#bfbfbf' : '#d8d8d8', opacity: theme.palette.mode === 'dark' ? undefined : 1, - height: `${3/16}em`, + height: `${3 / 16}em`, }, })); -export default RangeSlider; \ No newline at end of file +export default RangeSlider; diff --git a/frontend/src/components/Robots/RobotAvatar/index.tsx b/frontend/src/components/Robots/RobotAvatar/index.tsx index b50af9f0..c1031efd 100644 --- a/frontend/src/components/Robots/RobotAvatar/index.tsx +++ b/frontend/src/components/Robots/RobotAvatar/index.tsx @@ -1,61 +1,74 @@ -import React from "react" -import { Badge, Tooltip } from "@mui/material"; -import SmoothImage from 'react-smooth-image' +import React from 'react'; +import { Badge, Tooltip } from '@mui/material'; +import SmoothImage from 'react-smooth-image'; -import Order from "../../../models/Order.model" -import { useTranslation } from "react-i18next"; -import { SendReceiveIcon } from "../../Icons"; +import Order from '../../../models/Order.model'; +import { useTranslation } from 'react-i18next'; +import { SendReceiveIcon } from '../../Icons'; interface DepthChartProps { - order: Order + order: Order; } const RobotAvatar: React.FC = ({ order }) => { - const { t } = useTranslation() + const { t } = useTranslation(); - const avatarSrc: string = window.location.origin +'/static/assets/avatars/' + order?.maker_nick + '.png' + const avatarSrc: string = + window.location.origin + '/static/assets/avatars/' + order?.maker_nick + '.png'; const statusBadge = ( -
    - {order?.type === 0 ? - : - } +
    + {order?.type === 0 ? ( + + ) : ( + + )}
    - ) + ); const statusBadgeColor = () => { - if(!order){ return } - if(order.maker_status ==='Active'){ return("success") } - if(order.maker_status ==='Seen recently'){ return("warning") } - if(order.maker_status ==='Inactive'){ return('error') } - } + if (!order) { + return; + } + if (order.maker_status === 'Active') { + return 'success'; + } + if (order.maker_status === 'Seen recently') { + return 'warning'; + } + if (order.maker_status === 'Inactive') { + return 'error'; + } + }; return order ? ( - - - + +
    - ) : <> -} + ) : ( + <> + ); +}; -export default RobotAvatar +export default RobotAvatar; diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index baf8e73d..f9ccdd0c 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -1,12 +1,38 @@ -import React, { Component } from "react"; -import { withTranslation, Trans} from "react-i18next"; -import { Alert, AlertTitle, ToggleButtonGroup, ToggleButton, IconButton, Box, Link, Paper, Rating, Button, Tooltip, CircularProgress, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material" -import QRCode from "react-qr-code"; -import Countdown, { zeroPad} from 'react-countdown'; -import Chat from "./EncryptedChat" -import TradeSummary from "./TradeSummary" -import MediaQuery from 'react-responsive' -import { copyToClipboard } from "../utils/clipboard"; +import React, { Component } from 'react'; +import { withTranslation, Trans } from 'react-i18next'; +import { + Alert, + AlertTitle, + ToggleButtonGroup, + ToggleButton, + IconButton, + Box, + Link, + Paper, + Rating, + Button, + Tooltip, + CircularProgress, + Grid, + Typography, + TextField, + List, + ListItem, + ListItemText, + Divider, + ListItemIcon, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from '@mui/material'; +import QRCode from 'react-qr-code'; +import Countdown, { zeroPad } from 'react-countdown'; +import Chat from './EncryptedChat'; +import TradeSummary from './TradeSummary'; +import MediaQuery from 'react-responsive'; +import { copyToClipboard } from '../utils/clipboard'; // Icons import PercentIcon from '@mui/icons-material/Percent'; @@ -15,7 +41,7 @@ import SendIcon from '@mui/icons-material/Send'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; import BalanceIcon from '@mui/icons-material/Balance'; -import ContentCopy from "@mui/icons-material/ContentCopy"; +import ContentCopy from '@mui/icons-material/ContentCopy'; import PauseCircleIcon from '@mui/icons-material/PauseCircle'; import PlayCircleIcon from '@mui/icons-material/PlayCircle'; import BoltIcon from '@mui/icons-material/Bolt'; @@ -24,10 +50,10 @@ import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; import FavoriteIcon from '@mui/icons-material/Favorite'; import RocketLaunchIcon from '@mui/icons-material/RocketLaunch'; import RefreshIcon from '@mui/icons-material/Refresh'; -import { NewTabIcon } from "./Icons"; +import { NewTabIcon } from './Icons'; -import { getCookie } from "../utils/cookies"; -import { pn } from "../utils/prettyNumbers"; +import { getCookie } from '../utils/cookies'; +import { pn } from '../utils/prettyNumbers'; class TradeBox extends Component { invoice_escrow_duration = 3; @@ -44,147 +70,180 @@ class TradeBox extends Component { badInvoice: false, badAddress: false, badStatement: false, - } + }; } - - Sound = (soundFileName) => ( // Four filenames: "locked-invoice", "taker-found", "open-chat", "successful"