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 { LocalizationProvider, TimePicker} from '@mui/lab'; import DateFnsUtils from "@date-io/date-fns"; import { Link as LinkRouter } from 'react-router-dom' import { styled } from '@mui/material/styles'; import getFlags from './getFlags'; import AutocompletePayments from './AutocompletePayments'; import currencyDict from '../../static/assets/currencies.json'; //icons import LockIcon from '@mui/icons-material/Lock'; import HourglassTopIcon from '@mui/icons-material/HourglassTop'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import BuySatsIcon from "./icons/BuySatsIcon"; import BuySatsCheckedIcon from "./icons/BuySatsCheckedIcon"; import SellSatsIcon from "./icons/SellSatsIcon"; import SellSatsCheckedIcon from "./icons/SellSatsCheckedIcon"; import ContentCopy from "@mui/icons-material/ContentCopy"; import { getCookie } from "../utils/cookies"; import { pn } from "../utils/prettyNumbers"; class MakerPage extends Component { defaultCurrency = 1; defaultCurrencyCode = 'USD'; defaultPaymentMethod = "not specified"; defaultPremium = 0; minTradeSats = 20000; maxTradeSats = 1200000; maxBondlessSats = 50000; maxRangeAmountMultiple = 4.8; minRangeAmountMultiple = 1.6; constructor(props) { super(props); this.state={ is_explicit: false, type: 0, currency: this.defaultCurrency, currencyCode: this.defaultCurrencyCode, payment_method: this.defaultPaymentMethod, premium: 0, satoshis: null, showAdvanced: false, allowBondless: false, publicExpiryTime: new Date(0, 0, 0, 23, 59), escrowExpiryTime: new Date(0, 0, 0, 3, 0), enableAmountRange: false, minAmount: null, bondSize: 1, limits: null, minAmount: null, maxAmount: null, loadingLimits: true, } this.getLimits() } 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)), })); } a11yProps(index) { return { id: `simple-tab-${index}`, 'aria-controls': `simple-tabpanel-${index}`, }; } handleTypeChange=(e)=>{ this.setState({ type: e.target.value, }); } handleCurrencyChange=(e)=>{ this.setState({ currency: e.target.value, currencyCode: this.getCurrencyCode(e.target.value), }); 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, }); } 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 } 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; 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}) } this.setState({ premium: e.target.value, badPremium: bad_premium, }); } handleSatoshisChange=(e)=>{ const { t } = this.props; if(e.target.value > this.maxTradeSats){ var bad_sats = t("Must be less than {{maxSats}",{maxSats: pn(this.maxTradeSats)}) } if(e.target.value < this.minTradeSats){ var bad_sats = t("Must be more than {{minSats}}",{minSats: pn(this.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, is_explicit: this.state.is_explicit, premium: this.state.is_explicit ? null: 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" />
{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)) } RangeSlider = styled(Slider)(({ theme }) => ({ color: 'primary', height: 3, padding: '13px 0', '& .MuiSlider-thumb': { height: 27, width: 27, backgroundColor: '#fff', border: '1px solid currentColor', '&:hover': { boxShadow: '0 0 0 8px rgba(58, 133, 137, 0.16)', }, '& .range-bar': { height: 9, width: 1, backgroundColor: 'currentColor', marginLeft: 1, marginRight: 1, }, }, '& .MuiSlider-track': { height: 3, }, '& .MuiSlider-rail': { color: theme.palette.mode === 'dark' ? '#bfbfbf' : '#d8d8d8', opacity: theme.palette.mode === 'dark' ? undefined : 1, height: 3, }, })); RangeThumbComponent(props) { const { children, ...other } = props; return ( {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.state.enableAmountRange & this.state.minAmount != null? : 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: 1,label: '1%'},{value: 5,label: '5%'},{value: 10,label: '10%'},{value: 15,label: '15%'}]} min={1} max={15} onChange={(e) => this.setState({bondSize: e.target.value})} />
this.setState({allowBondless: !this.state.allowBondless})} /> } />
) } StoreTokenDialog = () =>{ const { t } = this.props; // If there is a robot cookie, prompt user to store it // Else, prompt user to generate a robot if (getCookie("robot_token")){ return( this.setState({openStoreToken:false})} > {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.")}
(navigator.clipboard.writeText(getCookie("robot_token")) & this.props.setAppState({copiedToken:true}))}> , }} />
) }else{ return( this.setState({openStoreToken:false})} > {t("You do not have a robot avatar")} {t("You need to generate a robot avatar in order to become an order maker")} ) } } makeOrderBox=()=>{ const [value, setValue] = React.useState(this.state.showAdvanced); const { t } = this.props; const handleChange = (event, newValue) => { this.setState({showAdvanced:newValue}) setValue(newValue); }; return(
) } render() { const { t } = this.props; return ( {/* ORDER MAKER */} {/* conditions to disable the make button */} {(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==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})) ) }
); } } export default withTranslation()(MakerPage);