From 965bbb076545bf3c898edfe2c810f12715231c46 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com> Date: Fri, 10 Feb 2023 13:28:26 +0000 Subject: [PATCH] Enhance UI for Swaps (#346) * Add better fiat/swap UI switches * Add swap controls to booktable * Enhance order details and take button for swaps * Add swap specific order summary strings * Add specific bond commitment descriptions for swap orders * Fix cosmetics --- frontend/src/basic/BookPage/index.tsx | 15 +- frontend/src/basic/Main.tsx | 4 +- frontend/src/basic/SettingsPage/index.tsx | 14 +- .../src/components/BookTable/BookControl.tsx | 160 ++++++++++++------ frontend/src/components/BookTable/index.tsx | 42 ++--- .../src/components/MakerForm/AmountRange.tsx | 2 +- .../src/components/MakerForm/MakerForm.tsx | 146 +++++++++------- .../components/OrderDetails/TakeButton.tsx | 22 ++- .../src/components/OrderDetails/index.tsx | 47 +++-- .../src/components/SettingsForm/index.tsx | 32 +++- .../TradeBox/Prompts/LockInvoice.tsx | 16 +- .../components/TradeBox/Prompts/Payout.tsx | 8 +- frontend/src/models/Favorites.model.ts | 1 + frontend/src/utils/filterOrders.ts | 3 +- frontend/src/utils/prettyNumbers.ts | 9 +- 15 files changed, 324 insertions(+), 197 deletions(-) diff --git a/frontend/src/basic/BookPage/index.tsx b/frontend/src/basic/BookPage/index.tsx index 9dda3291..99136fa0 100644 --- a/frontend/src/basic/BookPage/index.tsx +++ b/frontend/src/basic/BookPage/index.tsx @@ -69,15 +69,6 @@ const BookPage = ({ } }, []); - const handleCurrencyChange = function (e) { - const currency = e.target.value; - setFav({ ...fav, currency }); - }; - - const handleTypeChange = function (mouseEvent, val) { - setFav({ ...fav, type: val }); - }; - const onOrderClicked = function (id: number) { if (hasRobot) { history.push('/order/' + id); @@ -159,13 +150,12 @@ const BookPage = ({ clickRefresh={() => fetchBook()} book={book} fav={fav} + setFav={setFav} maxWidth={maxBookTableWidth} // EM units maxHeight={windowSize.height * 0.825 - 5} // EM units fullWidth={windowSize.width} // EM units fullHeight={windowSize.height} // EM units defaultFullscreen={false} - onCurrencyChange={handleCurrencyChange} - onTypeChange={handleTypeChange} onOrderClicked={onOrderClicked} baseUrl={baseUrl} /> @@ -199,13 +189,12 @@ const BookPage = ({ book={book} clickRefresh={() => fetchBook()} fav={fav} + setFav={setFav} maxWidth={windowSize.width * 0.97} // EM units maxHeight={windowSize.height * 0.825 - 5} // EM units fullWidth={windowSize.width} // EM units fullHeight={windowSize.height} // EM units defaultFullscreen={false} - onCurrencyChange={handleCurrencyChange} - onTypeChange={handleTypeChange} onOrderClicked={onOrderClicked} baseUrl={baseUrl} /> diff --git a/frontend/src/basic/Main.tsx b/frontend/src/basic/Main.tsx index 877df09a..0e91ef59 100644 --- a/frontend/src/basic/Main.tsx +++ b/frontend/src/basic/Main.tsx @@ -89,7 +89,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => { const [info, setInfo] = useState(defaultInfo); const [coordinators, setCoordinators] = useState(defaultCoordinators); const [baseUrl, setBaseUrl] = useState(''); - const [fav, setFav] = useState({ type: null, currency: 0 }); + const [fav, setFav] = useState({ type: null, mode: 'fiat', currency: 0 }); const [delay, setDelay] = useState(60000); const [timer, setTimer] = useState(setInterval(() => null, delay)); @@ -456,6 +456,8 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => { >
void; settings: Settings; setSettings: (state: Settings) => void; windowSize: { width: number; height: number }; } -const SettingsPage = ({ settings, setSettings, windowSize }: SettingsPageProps): JSX.Element => { +const SettingsPage = ({ + fav, + setFav, + settings, + setSettings, + windowSize, +}: SettingsPageProps): JSX.Element => { const theme = useTheme(); const { t } = useTranslation(); const maxHeight = windowSize.height * 0.85 - 3; @@ -23,6 +31,8 @@ const SettingsPage = ({ settings, setSettings, windowSize }: SettingsPageProps): void; paymentMethod: string[]; - onCurrencyChange: () => void; - onTypeChange: () => void; setPaymentMethods: () => void; } const BookControl = ({ width, - type, - currency, - onCurrencyChange, - onTypeChange, + fav, + setFav, paymentMethod, setPaymentMethods, }: BookControlProps): JSX.Element => { const { t } = useTranslation(); const theme = useTheme(); - const smallestToolbarWidth = (t('Buy').length + t('Sell').length) * 0.7 + 12; + const typeZeroText = fav.mode === 'fiat' ? 'Buy' : 'Swap In'; + const typeOneText = fav.mode === 'fiat' ? 'Sell' : 'Swap Out'; + + const smallestToolbarWidth = (typeZeroText.length + typeOneText.length) * 0.7 + 12; const mediumToolbarWidth = smallestToolbarWidth + 12; const verboseToolbarWidth = mediumToolbarWidth + (t('and use').length + t('pay with').length) * 0.6; + const handleCurrencyChange = function (e) { + const currency = e.target.value; + setFav({ ...fav, currency, mode: currency === 1000 ? 'swap' : 'fiat' }); + }; + + const handleTypeChange = function (mouseEvent, val) { + setFav({ ...fav, type: val }); + }; + + const handleModeChange = function (mouseEvent, val) { + const mode = fav.mode === 'fiat' ? 'swap' : 'fiat'; + const currency = fav.mode === 'fiat' ? 1000 : 0; + setFav({ ...fav, mode, currency }); + }; + return ( ) : null} + + + + + + + + + + - {t('Buy')} + {typeZeroText} - {t('Sell')} + {typeOneText} - {width > verboseToolbarWidth ? ( + {width > verboseToolbarWidth && fav.mode === 'fiat' ? ( {t('and use')} @@ -97,53 +145,55 @@ const BookControl = ({ ) : null} - - +
- + - {' ' + value} + {' ' + t('ANY')}
- ))} - -
+ {Object.entries(currencyDict).map(([key, value]) => ( + +
+ + + {' ' + value} + +
+
+ ))} + +
+ ) : null} {width > verboseToolbarWidth ? ( - {currency == 1000 ? t('swap to') : t('pay with')} + {fav.currency == 1000 ? t(fav.type === 0 ? 'to' : 'from') : t('pay with')} ) : null} @@ -164,10 +214,10 @@ const BookControl = ({ listBoxProps={{ sx: { width: '13em' } }} onAutocompleteChange={setPaymentMethods} value={paymentMethod} - optionsType={currency == 1000 ? 'swap' : 'fiat'} + optionsType={fav.currency == 1000 ? 'swap' : 'fiat'} error={false} helperText={''} - label={currency == 1000 ? t('DESTINATION') : t('METHOD')} + label={fav.currency == 1000 ? t('DESTINATION') : t('METHOD')} tooltipTitle='' listHeaderText='' addNewButtonText='' @@ -216,7 +266,7 @@ const BookControl = ({
- {currency === 1000 + {fav.currency === 1000 ? swapMethods.map((method, index) => ( void; - onTypeChange?: (mouseEvent: any, val: number) => void; + setFav?: (state: Favorites) => void; onOrderClicked?: (id: number) => void; baseUrl: string; } @@ -52,7 +51,8 @@ interface BookTableProps { const BookTable = ({ clickRefresh, book, - fav = { currency: 1, type: 0 }, + fav = { currency: 1, type: 0, mode: 'fiat' }, + setFav, maxWidth = 100, maxHeight = 70, fullWidth = 100, @@ -63,8 +63,6 @@ const BookTable = ({ showControls = true, showFooter = true, showNoResults = true, - onCurrencyChange, - onTypeChange, onOrderClicked = () => null, baseUrl, }: BookTableProps): JSX.Element => { @@ -218,7 +216,10 @@ const BookTable = ({ field: 'type', headerName: t('Is'), width: width * fontSize, - renderCell: (params: any) => (params.row.type ? t('Seller') : t('Buyer')), + renderCell: (params: any) => + params.row.type + ? t(fav.mode === 'fiat' ? 'Seller' : 'Swapping Out') + : t(fav.mode === 'fiat' ? 'Buyer' : 'Swapping In'), }; }; @@ -230,14 +231,15 @@ const BookTable = ({ type: 'number', width: width * fontSize, renderCell: (params: any) => { + const amount = fav.mode === 'swap' ? params.row.amount * 100000 : params.row.amount; + const minAmount = + fav.mode === 'swap' ? params.row.min_amount * 100000 : params.row.min_amount; + const maxAmount = + fav.mode === 'swap' ? params.row.max_amount * 100000 : params.row.max_amount; return (
- {amountToString( - params.row.amount, - params.row.has_range, - params.row.min_amount, - params.row.max_amount, - )} + {amountToString(amount, params.row.has_range, minAmount, maxAmount) + + (fav.mode === 'swap' ? 'K Sats' : '')}
); }, @@ -246,7 +248,7 @@ const BookTable = ({ const currencyObj = function (width: number, hide: boolean) { return { - hide, + hide: fav.mode === 'swap' ? true : hide, field: 'currency', headerName: t('Currency'), width: width * fontSize, @@ -274,7 +276,7 @@ const BookTable = ({ return { hide, field: 'payment_method', - headerName: t('Payment Method'), + headerName: fav.mode === 'fiat' ? t('Payment Method') : t('Destination'), width: width * fontSize, renderCell: (params: any) => { return ( @@ -497,7 +499,7 @@ const BookTable = ({ priority: 1, order: 4, normal: { - width: 6.5, + width: fav.mode === 'swap' ? 9.5 : 6.5, object: amountObj, }, }, @@ -505,7 +507,7 @@ const BookTable = ({ priority: 2, order: 5, normal: { - width: 5.9, + width: fav.mode === 'swap' ? 0 : 5.9, object: currencyObj, }, }, @@ -577,7 +579,7 @@ const BookTable = ({ priority: 10, order: 2, normal: { - width: 4.3, + width: fav.mode === 'swap' ? 7 : 4.3, object: typeObj, }, }, @@ -742,10 +744,8 @@ const BookTable = ({ componentsProps={{ toolbar: { width, - type: fav.type, - currency: fav.currency, - onCurrencyChange, - onTypeChange, + fav, + setFav, paymentMethod: paymentMethods, setPaymentMethods, }, diff --git a/frontend/src/components/MakerForm/AmountRange.tsx b/frontend/src/components/MakerForm/AmountRange.tsx index 8580440d..e8e369cb 100644 --- a/frontend/src/components/MakerForm/AmountRange.tsx +++ b/frontend/src/components/MakerForm/AmountRange.tsx @@ -148,7 +148,7 @@ function AmountRange({ {fav.type == null - ? t('Order for ') + ? t(fav.mode === 'fiat' ? 'Order for ' : 'Swap of ') : fav.type == 1 - ? t('Buy order for ') - : t('Sell order for ')} - {maker.advancedOptions && maker.minAmount != '' - ? pn(maker.minAmount) + '-' + pn(maker.maxAmount) - : pn(maker.amount)} - {' ' + currencyCode} + ? t(fav.mode === 'fiat' ? 'Buy BTC for ' : 'Swap into LN ') + : t(fav.mode === 'fiat' ? 'Sell BTC for ' : 'Swap out of LN ')} + {fav.mode === 'fiat' + ? amountToString(maker.amount, maker.advancedOptions, maker.minAmount, maker.maxAmount) + : amountToString( + maker.amount * 100000000, + maker.advancedOptions, + maker.minAmount * 100000000, + maker.maxAmount * 100000000, + )} + {' ' + (fav.mode === 'fiat' ? currencyCode : 'Sats')} {maker.isExplicit ? t(' of {{satoshis}} Satoshis', { satoshis: pn(maker.satoshis) }) : maker.premium == 0 - ? t(' at market price') + ? t(fav.mode === 'fiat' ? ' at market price' : '') : maker.premium > 0 ? t(' at a {{premium}}% premium', { premium: maker.premium }) : t(' at a {{discount}}% discount', { discount: -maker.premium })} @@ -492,56 +499,73 @@ const MakerForm = ({ - - - {t('Buy or Sell Bitcoin?')} - -
- - - - -
-
+ + + + + {t('Swap?')} + handleCurrencyChange(fav.mode == 'swap' ? 1 : 1000)} + /> + + + + + + + + {fav.mode === 'fiat' ? t('Buy or Sell Bitcoin?') : t('In or Out of Lightning?')} + +
+ + + + +
+
+
+
@@ -637,10 +661,10 @@ const MakerForm = ({ (false); const [open, setOpen] = useState(closeAll); - const currencyCode: string = currencies[`${order.currency}`]; + const currencyCode: string = order.currency == 1000 ? 'Sats' : currencies[`${order.currency}`]; const InactiveMakerDialog = function () { return ( @@ -104,9 +104,10 @@ const TakeButton = ({ }; const amountHelperText = function () { - if (Number(takeAmount) < Number(order.min_amount) && takeAmount != '') { + const amount = order.currency == 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount); + if (amount < Number(order.min_amount) && takeAmount != '') { return t('Too low'); - } else if (Number(takeAmount) > Number(order.max_amount) && takeAmount != '') { + } else if (amount > Number(order.max_amount) && takeAmount != '') { return t('Too high'); } else { return null; @@ -122,9 +123,10 @@ const TakeButton = ({ }; const invalidTakeAmount = function () { + const amount = order.currency == 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount); return ( - Number(takeAmount) < Number(order.min_amount) || - Number(takeAmount) > Number(order.max_amount) || + amount < Number(order.min_amount) || + amount > Number(order.max_amount) || takeAmount == '' || takeAmount == null ); @@ -146,7 +148,7 @@ const TakeButton = ({ }} > - + Number(order.max_amount)) && - takeAmount != '' - } + error={takeAmount === '' ? false : invalidTakeAmount()} helperText={amountHelperText()} label={t('Amount {{currencyCode}}', { currencyCode })} size='small' @@ -249,7 +247,7 @@ const TakeButton = ({ apiClient .post(baseUrl, '/api/order/?order_id=' + order.id, { action: 'take', - amount: takeAmount, + amount: order.currency == 1000 ? takeAmount / 100000000 : takeAmount, }) .then((data) => { setLoadingTake(false); diff --git a/frontend/src/components/OrderDetails/index.tsx b/frontend/src/components/OrderDetails/index.tsx index d481ce13..6e96db0d 100644 --- a/frontend/src/components/OrderDetails/index.tsx +++ b/frontend/src/components/OrderDetails/index.tsx @@ -32,7 +32,7 @@ import { FlagWithProps } from '../Icons'; import LinearDeterminate from './LinearDeterminate'; import { Order } from '../../models'; -import { statusBadgeColor, pn } from '../../utils'; +import { statusBadgeColor, pn, amountToString } from '../../utils'; import { Page } from '../../basic/NavBar'; import TakeButton from './TakeButton'; @@ -58,21 +58,23 @@ const OrderDetails = ({ const AmountString = function () { // precision to 8 decimal if currency is BTC otherwise 4 decimals - const precision = order.currency == 1000 ? 8 : 4; - - let primary = ''; - let secondary = ''; - if (order.has_range && order.amount == null) { - const minAmount = pn(parseFloat(Number(order.min_amount).toPrecision(precision))); - const maxAmount = pn(parseFloat(Number(order.max_amount).toPrecision(precision))); - primary = `${minAmount}-${maxAmount} ${currencyCode}`; - secondary = t('Amount range'); + if (order.currency == 1000) { + return ( + amountToString( + order.amount * 100000000, + order.amount ? false : order.has_range, + order.min_amount * 100000000, + order.max_amount * 100000000, + ) + ' Sats' + ); } else { - const amount = pn(parseFloat(Number(order.amount).toPrecision(precision))); - primary = `${amount} ${currencyCode}`; - secondary = t('Amount'); + return amountToString( + order.amount, + order.amount ? false : order.has_range, + order.min_amount, + order.max_amount, + ); } - return { primary, secondary }; }; // Countdown Renderer callback with condition @@ -151,7 +153,11 @@ const OrderDetails = ({ /> @@ -160,7 +166,11 @@ const OrderDetails = ({ @@ -205,7 +215,10 @@ const OrderDetails = ({ - + diff --git a/frontend/src/components/SettingsForm/index.tsx b/frontend/src/components/SettingsForm/index.tsx index a08c1ca3..a31cda31 100644 --- a/frontend/src/components/SettingsForm/index.tsx +++ b/frontend/src/components/SettingsForm/index.tsx @@ -14,7 +14,7 @@ import { ToggleButtonGroup, ToggleButton, } from '@mui/material'; -import { Settings } from '../../models'; +import { Favorites, Settings } from '../../models'; import SelectLanguage from './SelectLanguage'; import { Translate, @@ -23,11 +23,16 @@ import { DarkMode, SettingsOverscan, Link, + AccountBalance, + AttachMoney, } from '@mui/icons-material'; import { systemClient } from '../../services/System'; +import SwapCalls from '@mui/icons-material/SwapCalls'; interface SettingsFormProps { dense?: boolean; + fav: Favorites; + setFav: (state: Favorites) => void; settings: Settings; setSettings: (state: Settings) => void; showNetwork?: boolean; @@ -35,6 +40,8 @@ interface SettingsFormProps { const SettingsForm = ({ dense = false, + fav, + setFav, settings, setSettings, showNetwork = false, @@ -139,6 +146,29 @@ const SettingsForm = ({ track={false} /> + + + + + + { + setFav({ ...fav, mode, currency: mode === 'fiat' ? 0 : 1000 }); + }} + > + + + {t('Fiat')} + + + + {t('Swaps')} + + + + {showNetwork ? ( diff --git a/frontend/src/components/TradeBox/Prompts/LockInvoice.tsx b/frontend/src/components/TradeBox/Prompts/LockInvoice.tsx index 5d626a4c..a2886aeb 100644 --- a/frontend/src/components/TradeBox/Prompts/LockInvoice.tsx +++ b/frontend/src/components/TradeBox/Prompts/LockInvoice.tsx @@ -56,18 +56,22 @@ export const LockInvoicePrompt = ({ order, concept }: LockInvoicePromptProps): J alignItems='center' spacing={0.5} > + + {concept === 'bond' ? : } + + {concept == 'bond' ? ( - - {t(`You are ${order.is_buyer ? 'BUYING' : 'SELLING'} BTC`)} + + + {order.currency == 1000 + ? t(`${order.is_buyer ? 'SWAPPING INTO' : 'SWAPPING OUT of'} Lightning`) + : t(`You are ${order.is_buyer ? 'BUYING' : 'SELLING'} BTC`)} + ) : ( <> )} - - {concept === 'bond' ? : } - - diff --git a/frontend/src/models/Favorites.model.ts b/frontend/src/models/Favorites.model.ts index 88204101..c9ee4d1b 100644 --- a/frontend/src/models/Favorites.model.ts +++ b/frontend/src/models/Favorites.model.ts @@ -1,5 +1,6 @@ export interface Favorites { type: number | null; + mode: 'swap' | 'fiat'; currency: number; } diff --git a/frontend/src/utils/filterOrders.ts b/frontend/src/utils/filterOrders.ts index 811d81b8..911debb6 100644 --- a/frontend/src/utils/filterOrders.ts +++ b/frontend/src/utils/filterOrders.ts @@ -50,11 +50,12 @@ const filterOrders = function ({ }: FilterOrders) { const filteredOrders = orders.filter((order) => { const typeChecks = order.type == baseFilter.type || baseFilter.type == null; + const modeChecks = baseFilter.mode === 'fiat' ? !(order.currency === 1000) : true; const currencyChecks = order.currency == baseFilter.currency || baseFilter.currency == 0; const paymentMethodChecks = paymentMethods.length > 0 ? filterByPayment(order, paymentMethods) : true; const amountChecks = amountFilter != null ? filterByAmount(order, amountFilter) : true; - return typeChecks && currencyChecks && paymentMethodChecks && amountChecks; + return typeChecks && modeChecks && currencyChecks && paymentMethodChecks && amountChecks; }); return filteredOrders; }; diff --git a/frontend/src/utils/prettyNumbers.ts b/frontend/src/utils/prettyNumbers.ts index 5c624046..c47dbd1e 100644 --- a/frontend/src/utils/prettyNumbers.ts +++ b/frontend/src/utils/prettyNumbers.ts @@ -15,15 +15,16 @@ export const amountToString: ( has_range: boolean, min_amount: number, max_amount: number, -) => string = (amount, has_range, min_amount, max_amount) => { + precision?: number, +) => string = (amount, has_range, min_amount, max_amount, precision = 4) => { if (has_range) { return ( - pn(parseFloat(Number(min_amount).toPrecision(4))) + + pn(parseFloat(Number(min_amount).toPrecision(precision))) + '-' + - pn(parseFloat(Number(max_amount).toPrecision(4))) + pn(parseFloat(Number(max_amount).toPrecision(precision))) ); } - return pn(parseFloat(Number(amount).toPrecision(4))) || ''; + return pn(parseFloat(Number(amount).toPrecision(precision))) || ''; }; export default pn;