diff --git a/frontend/src/components/MakerForm/AmountRange.tsx b/frontend/src/components/MakerForm/AmountRange.tsx index 7f5b715a..ca798757 100644 --- a/frontend/src/components/MakerForm/AmountRange.tsx +++ b/frontend/src/components/MakerForm/AmountRange.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext, useMemo, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { SliderThumb, @@ -15,6 +15,7 @@ import { FlagWithProps } from '../Icons'; import RangeSlider from './RangeSlider'; import currencyDict from '../../../static/assets/currencies.json'; import { pn } from '../../utils'; +import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext'; const RangeThumbComponent: React.FC = (props) => { const { children, ...other } = props; @@ -29,39 +30,93 @@ const RangeThumbComponent: React.FC = (props) => { }; interface AmountRangeProps { - minAmount: string; - maxAmount: string; - type: number; + amountSafeThresholds: number[]; currency: number; - handleRangeAmountChange: (event: Event, value: number | number[], activeThumb: number) => void; - handleMaxAmountChange: ( - e: React.ChangeEventHandler, - ) => void; - handleMinAmountChange: ( - e: React.ChangeEventHandler, - ) => void; + setHasRangeError: (hasRangeError: boolean) => void; handleCurrencyChange: (newCurrency: number) => void; - maxAmountError: boolean; - minAmountError: boolean; currencyCode: string; amountLimits: number[]; } const AmountRange: React.FC = ({ - minAmount, - handleRangeAmountChange, + amountSafeThresholds, currency, currencyCode, handleCurrencyChange, + setHasRangeError, amountLimits, - maxAmount, - minAmountError, - maxAmountError, - handleMinAmountChange, - handleMaxAmountChange, }) => { + const { setMaker, maker } = useContext(GarageContext); const theme = useTheme(); const { t } = useTranslation(); + const maxRangeAmountMultiple = 14.8; + const minRangeAmountMultiple = 1.6; + + const minAmountError = useMemo(() => { + return ( + maker.maxAmount !== null && + maker.minAmount !== null && + (maker.minAmount < amountLimits[0] * 0.99 || + maker.maxAmount < maker.minAmount || + maker.minAmount < maker.maxAmount / (maxRangeAmountMultiple + 0.15) || + maker.minAmount * (minRangeAmountMultiple - 0.1) > maker.maxAmount) + ); + }, [maker.minAmount, maker.maxAmount, amountLimits]); + + const maxAmountError = useMemo(() => { + return ( + maker.maxAmount !== null && + maker.minAmount !== null && + (maker.maxAmount > amountLimits[1] * 1.01 || + maker.maxAmount < maker.minAmount || + maker.minAmount < maker.maxAmount / (maxRangeAmountMultiple + 0.15) || + maker.minAmount * (minRangeAmountMultiple - 0.1) > maker.maxAmount) + ); + }, [maker.minAmount, maker.maxAmount, amountLimits]); + + const handleRangeAmountChange = ( + e: Event, + newValue: number | number[], + activeThumb: number, + ): void => { + if (typeof newValue === 'number' || newValue.length < 2) return; + + let minAmount = newValue[0]; + let maxAmount = newValue[1]; + + minAmount = Math.min( + (amountLimits[1] * amountSafeThresholds[1]) / minRangeAmountMultiple, + minAmount, + ); + maxAmount = Math.max( + minRangeAmountMultiple * amountLimits[0] * amountSafeThresholds[0], + maxAmount, + ); + + if (minAmount > maxAmount / minRangeAmountMultiple) { + if (activeThumb === 0) { + maxAmount = minRangeAmountMultiple * minAmount; + } else { + minAmount = maxAmount / minRangeAmountMultiple; + } + } else if (minAmount < maxAmount / maxRangeAmountMultiple) { + if (activeThumb === 0) { + maxAmount = maxRangeAmountMultiple * minAmount; + } else { + minAmount = maxAmount / maxRangeAmountMultiple; + } + } + + setMaker({ + ...maker, + minAmount: parseFloat(minAmount.toPrecision(minAmount < 100 ? 2 : 3)), + maxAmount: parseFloat(maxAmount.toPrecision(maxAmount < 100 ? 2 : 3)), + }); + }; + + useEffect(() => { + setHasRangeError(!minAmountError || !maxAmountError); + }, [minAmountError, maxAmountError]); return ( @@ -93,11 +148,19 @@ const AmountRange: React.FC = ({ variant='standard' type='number' size='small' - value={minAmount} - onChange={handleMinAmountChange} + value={maker.minAmount?.toString()} + onChange={(e) => { + setMaker((maker) => { + const value = Number(e.target.value); + return { + ...maker, + minAmount: parseFloat(value.toPrecision(value < 100 ? 2 : 3)), + }; + }); + }} error={minAmountError} sx={{ - width: `${minAmount.toString().length * 0.56}em`, + width: `${(maker.minAmount?.toString().length ?? 0) * 0.56}em`, minWidth: '0.56em', maxWidth: '2.8em', }} @@ -116,11 +179,19 @@ const AmountRange: React.FC = ({ variant='standard' size='small' type='number' - value={maxAmount} - onChange={handleMaxAmountChange} + value={maker.maxAmount?.toString()} + onChange={(e) => { + setMaker((maker) => { + const value = Number(e.target.value); + return { + ...maker, + maxAmount: parseFloat(value.toPrecision(value < 100 ? 2 : 3)), + }; + }); + }} error={maxAmountError} sx={{ - width: `${maxAmount.toString().length * 0.56}em`, + width: `${(maker.maxAmount?.toString().length ?? 0) * 0.56}em`, minWidth: '0.56em', maxWidth: '3.36em', }} @@ -159,7 +230,7 @@ const AmountRange: React.FC = ({ > void; onReset?: () => void; @@ -55,7 +52,6 @@ interface MakerFormProps { } const MakerForm = ({ - pricingMethods = false, disableRequest = false, collapseAll = false, onSubmit = () => {}, @@ -72,7 +68,6 @@ const MakerForm = ({ const [badRequest, setBadRequest] = useState(null); const [amountLimits, setAmountLimits] = useState([1, 1000]); - const [satoshisLimits, setSatoshisLimits] = useState([20000, 4000000]); const [currentPrice, setCurrentPrice] = useState('...'); const [currencyCode, setCurrencyCode] = useState('USD'); @@ -80,10 +75,9 @@ const MakerForm = ({ const [openWorldmap, setOpenWorldmap] = useState(false); const [submittingRequest, setSubmittingRequest] = useState(false); const [amountRangeEnabled, setAmountRangeEnabled] = useState(true); + const [hasRangeError, setHasRangeError] = useState(false); const [limits, setLimits] = useState({}); - const maxRangeAmountMultiple = 14.8; - const minRangeAmountMultiple = 1.6; const amountSafeThresholds = [1.03, 0.98]; useEffect(() => { @@ -105,11 +99,10 @@ const MakerForm = ({ const updateCoordinatorInfo = (): void => { if (maker.coordinator != null) { - const newLimits = federation.getCoordinator(maker.coordinator).limits; - if (Object.keys(newLimits).length !== 0) { + const newLimits = federation.getCoordinator(maker.coordinator)?.limits; + if (newLimits && Object.keys(newLimits).length !== 0) { updateAmountLimits(newLimits, fav.currency, maker.premium); updateCurrentPrice(newLimits, fav.currency, maker.premium); - updateSatoshisLimits(newLimits); setLimits(newLimits); } } @@ -135,28 +128,14 @@ const MakerForm = ({ setAmountLimits([minAmountLimit, maxAmountLimit]); }; - const updateSatoshisLimits = function (limitList: LimitList): void { - const minAmount: number = limitList[1000].min_amount * 100000000; - let maxAmount: number = limitList[1000].max_amount * 100000000; - maxAmount = Math.min( - federation.getCoordinator(maker.coordinator).size_limit / 100000000, - maxAmount, - ); - setSatoshisLimits([minAmount, maxAmount]); - }; - const updateCurrentPrice = function ( limitsList: LimitList, currency: number, premium: number, ): void { const index = currency === 0 ? 1 : currency; - let price = '...'; - if (maker.isExplicit && maker.amount > 0 && maker.satoshis > 0) { - price = maker.amount / (maker.satoshis / 100000000); - } else if (!maker.isExplicit) { - price = limitsList[index].price * (1 + premium / 100); - } + const price = limitsList[index].price * (1 + premium / 100); + setCurrentPrice(parseFloat(Number(price).toPrecision(5))); }; @@ -222,24 +201,6 @@ const MakerForm = ({ }); }; - const handleMinAmountChange = function ( - e: React.ChangeEventHandler, - ): void { - setMaker({ - ...maker, - minAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)), - }); - }; - - const handleMaxAmountChange = function ( - e: React.ChangeEventHandler, - ): void { - setMaker({ - ...maker, - maxAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)), - }); - }; - const handlePremiumChange: React.ChangeEventHandler = function ({ target: { value } }): void { const max = fav.mode === 'fiat' ? 999 : 99; @@ -263,42 +224,6 @@ const MakerForm = ({ }); }; - const handleSatoshisChange = function (e: object): void { - const newSatoshis = e.target.value; - let badSatoshisText: string = ''; - let satoshis: string = newSatoshis; - if (newSatoshis > satoshisLimits[1]) { - badSatoshisText = t('Must be less than {{maxSats}', { maxSats: pn(satoshisLimits[1]) }); - satoshis = satoshisLimits[1]; - } - if (newSatoshis < satoshisLimits[0]) { - badSatoshisText = t('Must be more than {{minSats}}', { minSats: pn(satoshisLimits[0]) }); - satoshis = satoshisLimits[0]; - } - - setMaker({ - ...maker, - satoshis, - badSatoshisText, - }); - }; - - const handleClickRelative = function (): void { - setMaker({ - ...maker, - isExplicit: false, - }); - }; - - const handleClickExplicit = function (): void { - if (!maker.advancedOptions) { - setMaker({ - ...maker, - isExplicit: true, - }); - } - }; - const handleCreateOrder = function (): void { const slot = garage.getSlot(); @@ -318,9 +243,8 @@ const MakerForm = ({ max_amount: makerHasAmountRange ? maker.maxAmount : null, payment_method: maker.paymentMethodsText === '' ? 'not specified' : maker.paymentMethodsText, - is_explicit: maker.isExplicit, - premium: maker.isExplicit ? null : maker.premium === '' ? 0 : maker.premium, - satoshis: maker.isExplicit ? maker.satoshis : null, + premium: !maker.premium ? 0 : maker.premium, + satoshis: null, public_duration: maker.publicDuration, escrow_duration: maker.escrowDuration, bond_size: maker.bondSize, @@ -377,40 +301,21 @@ const MakerForm = ({ const handleClickAdvanced = function (): void { if (maker.advancedOptions) { - handleClickRelative(); setMaker({ ...maker, advancedOptions: false }); } else { resetRange(true); } }; - const minAmountError = useMemo(() => { - return ( - maker.minAmount < amountLimits[0] * 0.99 || - maker.maxAmount < maker.minAmount || - maker.minAmount < maker.maxAmount / (maxRangeAmountMultiple + 0.15) || - maker.minAmount * (minRangeAmountMultiple - 0.1) > maker.maxAmount - ); - }, [maker.minAmount, maker.maxAmount, amountLimits]); - - const maxAmountError = useMemo(() => { - return ( - maker.maxAmount > amountLimits[1] * 1.01 || - maker.maxAmount < maker.minAmount || - maker.minAmount < maker.maxAmount / (maxRangeAmountMultiple + 0.15) || - maker.minAmount * (minRangeAmountMultiple - 0.1) > maker.maxAmount - ); - }, [maker.minAmount, maker.maxAmount, amountLimits]); - const resetRange = function (advancedOptions: boolean): void { const index = fav.currency === 0 ? 1 : fav.currency; const minAmount = - maker.amount !== '' - ? parseFloat((maker.amount / 2).toPrecision(2)) + maker.amount !== null + ? (maker.amount / 2).toPrecision(2) : parseFloat(Number(limits[index].max_amount * 0.25).toPrecision(2)); const maxAmount = - maker.amount !== '' - ? parseFloat(maker.amount) + maker.amount !== null + ? maker.amount : parseFloat(Number(limits[index].max_amount * 0.75).toPrecision(2)); setMaker({ @@ -421,44 +326,6 @@ const MakerForm = ({ }); }; - const handleRangeAmountChange = ( - e: Event, - newValue: number | number[], - activeThumb: number, - ): void => { - let minAmount = e.target.value[0]; - let maxAmount = e.target.value[1]; - - minAmount = Math.min( - (amountLimits[1] * amountSafeThresholds[1]) / minRangeAmountMultiple, - minAmount, - ); - maxAmount = Math.max( - minRangeAmountMultiple * amountLimits[0] * amountSafeThresholds[0], - maxAmount, - ); - - if (minAmount > maxAmount / minRangeAmountMultiple) { - if (activeThumb === 0) { - maxAmount = minRangeAmountMultiple * minAmount; - } else { - minAmount = maxAmount / minRangeAmountMultiple; - } - } else if (minAmount < maxAmount / maxRangeAmountMultiple) { - if (activeThumb === 0) { - maxAmount = maxRangeAmountMultiple * minAmount; - } else { - minAmount = maxAmount / maxRangeAmountMultiple; - } - } - - setMaker({ - ...maker, - minAmount: parseFloat(Number(minAmount).toPrecision(minAmount < 100 ? 2 : 3)), - maxAmount: parseFloat(Number(maxAmount).toPrecision(maxAmount < 100 ? 2 : 3)), - }); - }; - const handleClickAmountRangeEnabled = function ( _e: React.ChangeEvent, checked: boolean, @@ -505,14 +372,13 @@ const MakerForm = ({ return ( fav.type == null || (!makerHasAmountRange && - maker.amount !== '' && + maker.amount && (maker.amount < amountLimits[0] || maker.amount > amountLimits[1])) || maker.badPaymentMethod || - (maker.amount == null && (!makerHasAmountRange || Object.keys(limits).lenght < 1)) || - (makerHasAmountRange && (minAmountError || maxAmountError)) || - (!makerHasAmountRange && maker.amount <= 0) || - (maker.isExplicit && (maker.badSatoshisText !== '' || maker.satoshis === '')) || - (!maker.isExplicit && maker.badPremiumText !== '') || + (maker.amount == null && (!makerHasAmountRange || (Object.keys(limits)?.length ?? 0) < 1)) || + (makerHasAmountRange && hasRangeError) || + (!makerHasAmountRange && maker.amount && maker.amount <= 0) || + maker.badPremiumText !== '' || federation.getCoordinator(maker.coordinator)?.limits === undefined || typeof maker.premium !== 'number' || maker.paymentMethods.length === 0 @@ -573,15 +439,13 @@ const MakerForm = ({ maker.maxAmount * 100000000, )} {' ' + (fav.mode === 'fiat' ? currencyCode : 'Sats')} - {maker.isExplicit - ? t(' of {{satoshis}} Satoshis', { satoshis: pn(maker.satoshis) }) - : maker.premium === 0 - ? fav.mode === 'fiat' - ? t(' at market price') - : '' - : maker.premium > 0 - ? t(' at a {{premium}}% premium', { premium: maker.premium }) - : t(' at a {{discount}}% discount', { discount: -maker.premium })} + {maker.premium === 0 + ? fav.mode === 'fiat' + ? t(' at market price') + : '' + : maker.premium > 0 + ? t(' at a {{premium}}% premium', { premium: maker.premium }) + : t(' at a {{discount}}% discount', { discount: -maker.premium })} ); }; @@ -696,47 +560,70 @@ const MakerForm = ({
- - + + - {fav.mode === 'fiat' ? t('Sell') : t('Swap Out')} - + +
@@ -766,113 +653,96 @@ const MakerForm = ({ - - - - + + + amountLimits[1]) } - > - amountLimits[1]) - } - helperText={ - maker.amount < amountLimits[0] && maker.amount !== '' - ? t('Must be more than {{minAmount}}', { - minAmount: pn(parseFloat(amountLimits[0].toPrecision(2))), + helperText={ + maker.amount && maker.amount < amountLimits[0] + ? t('Must be more than {{minAmount}}', { + minAmount: pn(parseFloat(amountLimits[0].toPrecision(2))), + }) + : maker.amount && maker.amount > amountLimits[1] + ? t('Must be less than {{maxAmount}}', { + maxAmount: pn(parseFloat(amountLimits[1].toPrecision(2))), }) - : maker.amount > amountLimits[1] && maker.amount !== '' - ? t('Must be less than {{maxAmount}}', { - maxAmount: pn(parseFloat(amountLimits[1].toPrecision(2))), - }) - : null - } - label={amountLabel.label} - required={true} - value={maker.amount} - type='number' - inputProps={{ - min: 0, - style: { - textAlign: 'center', - backgroundColor: theme.palette.background.paper, - borderRadius: '4px', - }, - }} - onChange={(e) => { - setMaker({ ...maker, amount: e.target.value }); - }} - /> - - {fav.mode === 'swap' && maker.amount !== '' ? ( - - {amountLabel.helper} - - ) : null} - - - {fav.mode === 'fiat' ? ( - - - + : null + } + label={amountLabel.label} + required={true} + value={maker.amount} + type='number' + onChange={(e) => { + setMaker({ ...maker, amount: Number(e.target.value) }); + }} + /> + + {fav.mode === 'swap' && maker.amount ? ( + + {amountLabel.helper} + ) : null} + + {fav.mode === 'fiat' ? ( + + + + ) : null}
- + { @@ -927,8 +797,98 @@ const MakerForm = ({ )} - {!maker.advancedOptions && pricingMethods ? ( - + + + + + + + + + + ), + }, + }, + }} + label={t('Public Duration (HH:mm)')} + value={maker.publicExpiryTime} + onChange={handleChangePublicDuration} + minTime={new Date(0, 0, 0, 0, 10)} + maxTime={new Date(0, 0, 0, 23, 59)} + /> + + + + + + + + + ), + }, + }, + }} + label={t('Escrow/Invoice Timer (HH:mm)')} + value={maker.escrowExpiryTime} + onChange={handleChangeEscrowDuration} + minTime={new Date(0, 0, 0, 1, 0)} + maxTime={new Date(0, 0, 0, 8, 0)} + /> + + + + - - - {t('Choose a Pricing Method')} - - - - } - label={t('Relative')} - labelPlacement='end' - onClick={handleClickRelative} - /> - - - } - label={t('Exact')} - labelPlacement='end' - onClick={handleClickExplicit} - /> - - - - - - ) : null} - -
- -
-
- -
-
- - - - - - - - - ), - }, - }, - }} - label={t('Public Duration (HH:mm)')} - value={maker.publicExpiryTime} - onChange={handleChangePublicDuration} - minTime={new Date(0, 0, 0, 0, 10)} - maxTime={new Date(0, 0, 0, 23, 59)} - /> - - - - - - - - - ), - }, - }, - }} - label={t('Escrow/Invoice Timer (HH:mm)')} - value={maker.escrowExpiryTime} - onChange={handleChangeEscrowDuration} - minTime={new Date(0, 0, 0, 1, 0)} - maxTime={new Date(0, 0, 0, 8, 0)} - /> - - - - - + - - + - - - {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) => { - setMaker({ ...maker, bondSize: e.target.value }); - }} - /> - - - + {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) => { + setMaker({ ...maker, bondSize: e.target.value }); + }} + /> + - -
-
+ +
+ @@ -1180,7 +969,7 @@ const MakerForm = ({ /> - + @@ -1239,15 +1028,10 @@ const MakerForm = ({ enterTouchDelay={0} enterDelay={1000} enterNextDelay={2000} - title={ - maker.isExplicit - ? t('Your order fixed exchange rate') - : t("Your order's current exchange rate. Rate will move with the market.") - } + title={t("Your order's current exchange rate. Rate will move with the market.")} > - {(maker.isExplicit ? t('Order rate:') : t('Order current rate:')) + - ` ${pn(currentPrice)} ${currencyCode}/BTC`} + {`${t('Order current rate:')} ${currentPrice ?? '-'} ${currencyCode}/BTC`} diff --git a/frontend/src/components/MakerForm/SelectCoordinator.tsx b/frontend/src/components/MakerForm/SelectCoordinator.tsx index 447125fd..9c1063a2 100644 --- a/frontend/src/components/MakerForm/SelectCoordinator.tsx +++ b/frontend/src/components/MakerForm/SelectCoordinator.tsx @@ -76,13 +76,13 @@ const SelectCoordinator: React.FC = ({ { onClickCurrentCoordinator(coordinatorAlias); @@ -108,13 +108,16 @@ const SelectCoordinator: React.FC = ({ - +