diff --git a/components/Asset/Chart/Chart.jsx b/components/Asset/Chart/Chart.jsx index 5d30e445b..e9c595cb1 100644 --- a/components/Asset/Chart/Chart.jsx +++ b/components/Asset/Chart/Chart.jsx @@ -16,7 +16,7 @@ import * as ReactDOM from 'react-dom' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState, useMemo } from 'react' import ChartOverlay from './ChartOverlay' import ChartSettings from './ChartSettings' @@ -26,6 +26,8 @@ import styled from '@emotion/styled' import useAreaChart from './hooks/useAreaChart' import useCandleChart from './hooks/useCandleChart' import { withAssetChartQuery } from '@/hooks' +import floatToFixed from '@algodex/algodex-sdk/lib/utils/format/floatToFixed' +import { useInversionStatus } from '@/hooks/utils/useInversionStatus' const Container = styled.div` position: relative; @@ -76,42 +78,7 @@ const SettingsContainer = styled.div` height: 2.75rem; } ` -function autoScaleProvider(original, chart, priceData) { - let visibleRange = chart.timeScale().getVisibleRange() - if (!visibleRange) { - return - } - const rangeStart = visibleRange.from - const rangeEnd = visibleRange.to - let max = 0 - let min = -1 - for (let i = 0; i < priceData.length; i++) { - const priceItem = priceData[i] - if (priceItem.time < rangeStart) { - continue - } - max = Math.max(priceItem.close, max) - max = Math.max(priceItem.open, max) - max = Math.max(priceItem.high, max) - if (min === -1) { - min = priceItem.open - } - min = Math.min(priceItem.close, min) - min = Math.min(priceItem.low, min) - min = Math.min(priceItem.open, min) - if (priceItem.time > rangeEnd) { - break - } - } - - const res = original() - if (res !== null && min !== -1) { - res.priceRange.maxValue = max - res.priceRange.minValue = min - } - return res -} export function Chart({ asset, interval: _interval, @@ -126,6 +93,30 @@ export function Chart({ const [overlay, setOverlay] = useState(_overlay) const [chartMode, setChartMode] = useState(_mode) const [currentLogical, setCurrentLogical] = useState(ohlc.length - 1) + const isInverted = useInversionStatus(asset.id) + // console.log(isInverted, 'is inverted') + const formatedPriceData = useMemo( + () => { + if (isInverted) { + const formatedPriceClone = [...ohlc] + const formattedPrice = formatedPriceClone.reduce( + (accumulator, currentValue) => { + accumulator.push({ + ...currentValue, + close: currentValue.close !== 0 ? floatToFixed(1 / currentValue.close) : 'Invalid', + high: currentValue.high !== 0 ? floatToFixed(1 / currentValue.high) : 'Invalid', + low: currentValue.low !== 0 ? floatToFixed(1 / currentValue.low) : 'Invalid', + open: currentValue.open !== 0 ? floatToFixed(1 / currentValue.open) : 'Invalid' + }) + return accumulator + }, []); + return formattedPrice + } else { + return ohlc + } + }, + [isInverted, ohlc], + ) useEffect(() => { setOverlay(_overlay) @@ -143,9 +134,45 @@ export function Chart({ const candleChartRef = useRef() const areaChartRef = useRef() - const { candleChart } = useCandleChart(candleChartRef, volume, ohlc, autoScaleProvider) - const { areaChart } = useAreaChart(areaChartRef, ohlc, autoScaleProvider) + const autoScaleProvider = useCallback((original, chart, priceData) => { + let visibleRange = chart.timeScale().getVisibleRange() + if (!visibleRange) { + return + } + const rangeStart = visibleRange.from + const rangeEnd = visibleRange.to + let max = 0 + let min = -1 + for (let i = 0; i < priceData.length; i++) { + const priceItem = priceData[i] + if (priceItem.time < rangeStart) { + continue + } + max = Math.max(priceItem.close, max) + max = Math.max(priceItem.open, max) + max = Math.max(priceItem.high, max) + if (min === -1) { + min = priceItem.open + } + min = Math.min(priceItem.close, min) + min = Math.min(priceItem.low, min) + min = Math.min(priceItem.open, min) + if (priceItem.time > rangeEnd) { + break + } + } + + const res = original() + if (res !== null && min !== -1) { + res.priceRange.maxValue = max + res.priceRange.minValue = min + } + + return res + }, []) + const { candleChart } = useCandleChart(candleChartRef, volume, formatedPriceData, autoScaleProvider) + const { areaChart } = useAreaChart(areaChartRef, formatedPriceData, autoScaleProvider) const onSettingsChange = useCallback( (e) => { if (e?.target?.name === 'mode') { @@ -170,6 +197,16 @@ export function Chart({ if (ohlc == null || volume == null) { return } + // if (asset.isInverted) { + // if (ohlc == null || algoVolume == null) { + // return + // } + // } else { + // if (ohlc == null || volume == null) { + // return + // } + // } + const priceEntry = ohlc[logical] const volumeEntry = volume[logical] @@ -183,7 +220,13 @@ export function Chart({ ) const mouseOut = useCallback(() => { - setOverlay(_overlay) + // setOverlay(_overlay) + if (isInverted) { + const __overlay = { ...overlay, ..._overlay } + setOverlay(__overlay) + } else { + setOverlay(_overlay) + } }, [setOverlay, _overlay]) const mouseMove = useCallback( @@ -203,6 +246,18 @@ export function Chart({ return } + // if (isInverted) { + // if (logical >= ohlc.length || logical >= algoVolume.length) { + // // setOverlay(_overlay) + // return + // } + // } else { + // if (logical >= ohlc.length || logical >= volume.length) { + // // setOverlay(_overlay) + // return + // } + // } + if (logical !== currentLogical) { setCurrentLogical(logical) updateHoverPrices(logical) @@ -218,6 +273,7 @@ export function Chart({ setCurrentLogical, updateHoverPrices, volume, + asset.isInverted, ohlc ] ) @@ -248,6 +304,7 @@ export function Chart({ ask={overlay.orderbook.ask} spread={overlay.orderbook.spread} volume={overlay.volume} + // volume={asset.isInverted ? overlay.algoVolume : overlay.volume} /> )} {typeof overlay.ohlc === 'undefined' && ( @@ -258,6 +315,7 @@ export function Chart({ ask={_overlay.orderbook.ask} spread={_overlay.orderbook.spread} volume={_overlay.volume} + // volume={asset.isInverted ? overlay.algoVolume : overlay.volume} /> )} @@ -270,7 +328,8 @@ export function Chart({ Chart.propTypes = { asset: PropTypes.shape({ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - decimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired + decimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + isInverted: PropTypes.bool }).isRequired, interval: PropTypes.string.isRequired, mode: PropTypes.string.isRequired, diff --git a/components/Asset/Chart/ChartOverlay.jsx b/components/Asset/Chart/ChartOverlay.jsx index 8711c52b5..f66933910 100644 --- a/components/Asset/Chart/ChartOverlay.jsx +++ b/components/Asset/Chart/ChartOverlay.jsx @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -import { useCallback, useMemo } from 'react' - +import { useCallback, useEffect, useMemo, useState } from 'react' +// import Button from 'components/Button' import Big from 'big.js' import Icon from '@mdi/react' import { Info } from 'react-feather' @@ -25,6 +25,13 @@ import { mdiCheckDecagram } from '@mdi/js' import styled from '@emotion/styled' import theme from 'theme' import { useUserStore } from 'store' +import { Typography, Stack, Button } from '@mui/material' +import Image from 'next/image' +import { useInversionStatus } from '@/hooks/utils/useInversionStatus' +import {StableAssets} from '@/components/StableAssets' + +import { useRouter } from 'next/router' +import Tooltip from '@/components/Tooltip' export const Container = styled.div` position: absolute; @@ -45,6 +52,7 @@ export const Header = styled.header` margin-left: 1.75rem; margin-top: 1.25rem; margin-bottom: 0.125rem; + width: 100%; ` export const TradingPair = styled.h3` @@ -57,7 +65,7 @@ export const TradingPair = styled.h3` margin-block-end: 0; color: ${({ theme }) => theme.palette.gray[500]}; white-space: nowrap; - + width: inherit; span { color: ${({ theme }) => theme.palette.gray[100]}; } @@ -120,7 +128,7 @@ export const OhlcItem = styled.div` dd { color: ${({ theme, value }) => - parseFloat(value) < 0 ? theme.palette.red[500] : theme.palette.green[500]}; + parseFloat(value) < 0 ? theme.palette.red[500] : theme.palette.green[500]}; } @media (min-width: 1024px) { @@ -206,12 +214,13 @@ export const Volume = styled.div` function ChartOverlay(props) { const { asset, ohlc, bid, ask, spread, volume } = props + const [inversionStatus, setInversionStatus] = useState(false) const setShowAssetInfo = useUserStore((state) => state.setShowAssetInfo) const currentPrice = asset.price_info?.price ? new Big(asset.price_info?.price) : new Big(0) const changeAmt = asset.price_info?.price24Change ? currentPrice - .sub(currentPrice.div(new Big(1 + asset.price_info?.price24Change / 100))) - .toString() + .sub(currentPrice.div(new Big(1 + asset.price_info?.price24Change / 100))) + .toString() : '0' const changePct = asset.price_info?.price24Change ? new Big(asset.price_info?.price24Change) @@ -226,6 +235,74 @@ function ChartOverlay(props) { setShowAssetInfo(true) }, [setShowAssetInfo]) + const setInversionStatusFn = useCallback(() => { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus) { + if (inversionStatus === 'true') { + localStorage.setItem('inversionStatus', 'false') + setInversionStatus(false) + } else { + localStorage.setItem('inversionStatus', 'true') + setInversionStatus(true) + } + } else { + localStorage.setItem('inversionStatus', 'true') + setInversionStatus(true) + } + }, []) + + const getInversionStatus = useCallback(() => { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus && inversionStatus === 'true') { + return true + } + return false + }, []) + + const router = useRouter() + useEffect(() => { + // Get the ID of asset + const isStableAsset = StableAssets.includes(parseInt(asset.id)) + if (isStableAsset) { + localStorage.setItem('inversionStatus', 'true') + } else { + localStorage.setItem('inversionStatus', 'false') + } + }, [router]) + + const ButtonComp = ({ setTriggerRef }) => { + return + } + + ButtonComp.propTypes = { + setTriggerRef: PropTypes.func + } + return (
@@ -235,17 +312,50 @@ function ChartOverlay(props) { path={mdiCheckDecagram} title="Verified Asset" size={0.7} + className="-mt-4" color={theme.palette.gray['500']} /> )} -
-  {`${asset.name} `} / ALGO -
-
- - - -
+ + + {/*
+  {`${asset.name} `} / ALGO +
*/} + {!useInversionStatus(asset.id) && ( +
+  {`${asset.name} `} / ALGO +
+ )} + {(useInversionStatus(asset.id)) && ( +
+ ALGO / {`${asset.name} `} +
+ )} +
+ + + +
+
+
+ {asset.decimals !== 6 ? } + > +
+ + Inverted mode is currently not supported for assets not configured with 6 decimals + +
+
: + } +
+
+ + diff --git a/components/Asset/Chart/ChartSettings.jsx b/components/Asset/Chart/ChartSettings.jsx index e01c091f8..b7b7242ca 100644 --- a/components/Asset/Chart/ChartSettings.jsx +++ b/components/Asset/Chart/ChartSettings.jsx @@ -14,8 +14,7 @@ * along with this program. If not, see . */ -import Button from 'components/Button' -import { Fragment, useCallback } from 'react' +import { useCallback } from 'react' import PropTypes from 'prop-types' import { lighten } from 'polished' import styled from '@emotion/styled' @@ -87,12 +86,11 @@ function ChartSettings(props) { return ( - + Candle Area - + {renderTimeIntervals()} diff --git a/components/Asset/Chart/hooks/useAreaChart.js b/components/Asset/Chart/hooks/useAreaChart.js index 3143f7648..929b5582a 100644 --- a/components/Asset/Chart/hooks/useAreaChart.js +++ b/components/Asset/Chart/hooks/useAreaChart.js @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { addListener, removeListener } from 'resize-detector' import theme from '@/theme/index' import moment from 'moment' @@ -118,7 +118,8 @@ export default function useAreaChart(containerRef, priceData, autoScaleProvider) useEffect(() => { if (areaChart) { - const areaSeriesData = priceData?.map(({ time, close }) => ({ + const areaPriceData = priceData + const areaSeriesData = areaPriceData?.map(({ time, close }) => ({ time, value: close })) diff --git a/components/Asset/Chart/hooks/useCandleChart.js b/components/Asset/Chart/hooks/useCandleChart.js index 4f592277f..236a180dc 100644 --- a/components/Asset/Chart/hooks/useCandleChart.js +++ b/components/Asset/Chart/hooks/useCandleChart.js @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback, useMemo, useReducer } from 'react' import { addListener, removeListener } from 'resize-detector' import theme from '@/theme' import moment from 'moment' diff --git a/components/Asset/OrderBook/OrderBook.jsx b/components/Asset/OrderBook/OrderBook.jsx index d74aa5068..98c7f460e 100644 --- a/components/Asset/OrderBook/OrderBook.jsx +++ b/components/Asset/OrderBook/OrderBook.jsx @@ -16,8 +16,7 @@ // import '@/wdyr'; import { ArrowDown, ArrowUp } from 'react-feather' -import { Fragment, useCallback, useMemo, useState } from 'react' -// import { Typography, Typography, Typography, Typography } from '@/components/Typography' +import { useCallback, useMemo, useState } from 'react' import { useAlgodex, withAssetOrderbookQuery, withAssetPriceQuery } from '@/hooks' import Big from 'big.js' @@ -32,8 +31,6 @@ import TablePriceHeader from '@/components/Table/PriceHeader' import Typography from '@mui/material/Typography' import { assetVeryShortNameFn } from '@/components/helpers' import { floatToFixedDynamic } from '@/services/display' -// import convertFromAsaUnits from '@algodex/algodex-sdk/lib/utils/units/fromAsaUnits' -// import floatToFixed from '@algodex/algodex-sdk/lib/utils/format/floatToFixed' import { isUndefined } from 'lodash/lang' import { rgba } from 'polished' import styled from '@emotion/styled' @@ -47,6 +44,7 @@ import { getIsRestricted, getIsRestrictedCountry } from '@/utils/restrictedAssets' +import { useInversionStatus } from '@/hooks/utils/useInversionStatus' const FirstOrderContainer = styled.div` flex: 1 1 0; @@ -162,9 +160,9 @@ const BookRow = styled.div` ${gridStyles} &:hover { background-color: ${({ theme, type }) => { - const color = type === 'buy' ? 'green' : 'red' - return rgba(theme.palette[color]['500'], 0.15) - }}; + const color = type === 'buy' ? 'green' : 'red' + return rgba(theme.palette[color]['500'], 0.15) + }}; p { &:not(:first-of-type) { color: ${({ theme }) => theme.palette.gray['000']}; @@ -251,6 +249,7 @@ export function OrderBookPrice({ asset }) { const isDecrease = asset?.price_info?.price24Change < 0 const color = isDecrease ? 'red' : 'green' + // console.log(asset, 'asset here') // function PriceInfo() { // return ( // @@ -321,7 +320,7 @@ const DECIMALS_MAP = { * @returns {JSX.Element} * @constructor */ - export function OrderBook({ asset, orders, components, isMobile }) { +export function OrderBook({ asset, orders, components, isMobile }) { const { query } = useRouter() const { PriceDisplay } = components const { t } = useTranslation('common') @@ -330,6 +329,7 @@ const DECIMALS_MAP = { const isSignedIn = isConnected const cachedSelectedPrecision = useUserState((state) => state.cachedSelectedPrecision) const setCachedSelectedPrecision = useUserState((state) => state.setCachedSelectedPrecision) + const isInverted = useInversionStatus(asset.id) const onAggrSelectorChange = useCallback((e) => { setCachedSelectedPrecision({ ...cachedSelectedPrecision, @@ -337,7 +337,7 @@ const DECIMALS_MAP = { }) setSelectedPrecision(DECIMALS_MAP[e.target.value]) }, [asset.id, cachedSelectedPrecision, setCachedSelectedPrecision]) - + const [selectedPrecision, setSelectedPrecision] = useState( DECIMALS_MAP[cachedSelectedPrecision[asset.id]] || 6 ) @@ -373,35 +373,36 @@ const DECIMALS_MAP = { // Deducted a Microalgo because of rounding in use-store while setting total // FIXME: look into - (asset.decimals ? 0.000001 : 1) const retval = parseFloat(new Big(maxSpendableAlgo).div(_price)) - (asset.decimals ? 0.000001 : 1) - // console.log('yreturning ' + retval) return retval } else { - // console.log('zreturning ' + compoundedAmount) return compoundedAmount } - },[asset.decimals, maxSpendableAlgo]) + }, [asset.decimals, maxSpendableAlgo]) const reduceOrders = useCallback((result, order) => { - const _price = floatToFixedDynamic(order.price, selectedPrecision, selectedPrecision) + const _price = isInverted + ? floatToFixedDynamic(1 / order.price, selectedPrecision, selectedPrecision) + : floatToFixedDynamic(order.price, selectedPrecision, selectedPrecision) const _amount = order.amount + const index = result.findIndex( (obj) => floatToFixedDynamic(obj.price, selectedPrecision, selectedPrecision) === _price ) if (index !== -1) { - result[index].amount += _amount - result[index].total += _amount * _price + result[index].amount += isInverted ? order.price * _amount : _amount + result[index].total += isInverted ? _amount : _amount * _price return result } result.push({ price: _price, - amount: _amount, - total: _amount * _price + amount: isInverted ? order.price * _amount : _amount, + total: isInverted ? _amount : _amount * _price }) return result - }, [selectedPrecision]) + }, [isInverted, selectedPrecision]) const aggregatedBuyOrder = useMemo(() => { if (typeof orders?.buy === 'undefined' && !Array.isArray(orders.buy)) return [] @@ -414,7 +415,7 @@ const DECIMALS_MAP = { }, [orders.sell, reduceOrders]) const isGeoBlocked = useMemo(() => getIsRestrictedCountry(query) && getIsRestricted(asset.id) - , [asset.id, query]) + , [asset.id, query]) const renderOrders = useCallback((data, type) => { const color = type === 'buy' ? 'green' : 'red' @@ -475,16 +476,26 @@ const DECIMALS_MAP = { ) }) - },[calculatedAmountFn, decimals, dispatcher, isGeoBlocked]) + }, [calculatedAmountFn, decimals, dispatcher, isGeoBlocked, isInverted]) - const renderedSellOrders = useMemo( () => { + const renderedSellOrders = useMemo(() => { return renderOrders(aggregatedSellOrder, 'sell') - },[aggregatedSellOrder, renderOrders]); + }, [aggregatedSellOrder, renderOrders]); - const renderedBuyOrders = useMemo( () => { + const renderedBuyOrders = useMemo(() => { return renderOrders(aggregatedBuyOrder, 'buy') - },[aggregatedBuyOrder, renderOrders]); - + }, [aggregatedBuyOrder, renderOrders]); + + const sortedBuyOrder = useMemo(() => { + const sortedOrders = aggregatedSellOrder.sort((a, b) => b.price - a.price) + return renderOrders(sortedOrders, 'buy') + }, [aggregatedBuyOrder]) + + const sortedSellOrder = useMemo(() => { + const sortedSellOrder = aggregatedBuyOrder.sort((a, b) => b.price - a.price) + return renderOrders(sortedSellOrder, 'sell') + }, [aggregatedSellOrder]) + return useMemo(() => { if (typeof orders.sell !== 'undefined' && typeof orders.buy !== 'undefined') { @@ -492,54 +503,59 @@ const DECIMALS_MAP = { return } } - + return ( -
- - - - - {t('order-book')} - - - - - - - - - - -
- - - {t('amount')} ({assetVeryShortName}) - - -
-
- - - {renderedSellOrders} - - - - - - - - - {renderedBuyOrders} - - -
-
- )}, [PriceDisplay, asset, assetVeryShortName, isSignedIn, - onAggrSelectorChange, orders.buy, orders.sell, - renderedBuyOrders, renderedSellOrders, selectedPrecision, t]) +
+ + + + + {t('order-book')} + + + + + + + + + + +
+ + + {t('amount')} ({isInverted ? 'ALGO' : assetVeryShortName}) + + + {t('total')} ({!isInverted ? 'ALGO' : assetVeryShortName}) + +
+
+ + + + {isInverted ? sortedSellOrder : renderedSellOrders} + + + + + + + + + + {isInverted ? sortedBuyOrder : renderedBuyOrders} + + +
+
+ ) + }, [PriceDisplay, asset, assetVeryShortName, isSignedIn, + onAggrSelectorChange, orders.buy, orders.sell, + renderedBuyOrders, renderedSellOrders, selectedPrecision, t, isInverted]) } OrderBook.propTypes = { diff --git a/components/Asset/OrderBook/OrderBookPriceInfo.js b/components/Asset/OrderBook/OrderBookPriceInfo.js index b3b974f2b..279874206 100644 --- a/components/Asset/OrderBook/OrderBookPriceInfo.js +++ b/components/Asset/OrderBook/OrderBookPriceInfo.js @@ -17,16 +17,17 @@ // import { BodyCopy, HeaderSmInter, LabelLg } from '@/components/Typography' import { Stack, Typography } from '@mui/material' -import { useCallback, useMemo } from 'react' - +import { useMemo, useCallback } from 'react' import Icon from '@mdi/react' import PropTypes from 'prop-types' import Spinner from '@/components/Spinner' -import convertFromAsaUnits from '@algodex/algodex-sdk/lib/utils/units/fromAsaUnits' +// import convertFromAsaUnits from '@algodex/algodex-sdk/lib/utils/units/fromAsaUnits' import floatToFixed from '@algodex/algodex-sdk/lib/utils/format/floatToFixed' import { formatUSDPrice } from '@/components/helpers' import { mdiApproximatelyEqual } from '@mdi/js' import { withAlgorandPriceQuery } from '@/hooks' +import InvertedUSDInputPrice from '@/components/Wallet/PriceConversion/InvertedUSDInputPrice' +import { StableAssets } from '@/components/StableAssets' const getPriceDecimals = (price) => { if (price >= 10000) { @@ -40,34 +41,53 @@ const getPriceDecimals = (price) => { } const Loading = () => -const PriceInfoView = ({asaValue, algoPrice, asset}) => { +const PriceInfoView = ({ asaValue, algoPrice, asset }) => { + const getInversionStatus = useCallback(() => { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus && inversionStatus === 'true') { + return true + } + return false + }, []) + + const formattedAsaValue = getInversionStatus() ? (1 / asaValue).toFixed(asset.decimals) : asaValue + // console.log(asaValue, 'asa value here') + return useMemo(() => { return ( <> - {asaValue} + {formattedAsaValue} {asset && asset.price_info && ( - {(asset?.price_info?.price24Change && - `${floatToFixed(asset?.price_info?.price24Change, 2)}%`) || - '0.00%'} + {(asset?.price_info?.price24Change && `${floatToFixed(asset?.price_info?.price24Change, 2)}%`) || '0.00%'} )} -
+ {getInversionStatus() && StableAssets.includes(asset.id) && +
+ + + + +
+ } + {!getInversionStatus() &&
- ${formatUSDPrice(algoPrice * asaValue)} + ${formatUSDPrice(algoPrice * formattedAsaValue)}
+ } - )}, [asaValue, algoPrice, asset]) + ) + }, [formattedAsaValue, asaValue, algoPrice, asset]) } PriceInfoView.propTypes = { @@ -78,7 +98,7 @@ PriceInfoView.propTypes = { export function OrderBookPriceInfo({ algoPrice, asset }) { const decimals = getPriceDecimals(asset?.price_info?.price || 0) const asaValue = floatToFixed(asset?.price_info?.price || 0, decimals, 6) - return typeof asset?.price_info === 'undefined' ? : + return typeof asset?.price_info === 'undefined' ? : } OrderBookPriceInfo.propTypes = { diff --git a/components/Asset/TradeHistory/TradeHistory.jsx b/components/Asset/TradeHistory/TradeHistory.jsx index 7edbfa27f..d8b5a0793 100644 --- a/components/Asset/TradeHistory/TradeHistory.jsx +++ b/components/Asset/TradeHistory/TradeHistory.jsx @@ -31,6 +31,7 @@ import { rgba } from 'polished' import styled from '@emotion/styled' import useTranslation from 'next-translate/useTranslation' import { withAssetTradeHistoryQuery } from '@/hooks' +import {useInversionStatus} from '@/hooks/utils/useInversionStatus' dayjs.extend(localizedFormat) @@ -126,16 +127,21 @@ const PriceHeaderText = styled(Typography)` } ` -const PriceHeader = () => { +const PriceHeader = ({ title }) => { const { t } = useTranslation('common') return ( {t('price')} - + {!title ? :  {title}} ) } +PriceHeader.propTypes = { + title: PropTypes.string +} + + /** * Asset Trade History * @@ -148,7 +154,7 @@ const PriceHeader = () => { export function TradeHistory({ asset, orders: tradesData }) { const { t } = useTranslation('common') const hasTradeHistory = tradesData.length > 0 - + const isInverted = useInversionStatus(asset.id) const assetVeryShortName = useMemo(() => assetVeryShortNameFn(asset), [asset]) const renderHistory = useCallback(() => { @@ -170,7 +176,6 @@ export function TradeHistory({ asset, orders: tradesData }) { } const priceDecimals = getPriceDecimals(avgPrice); - return tradesData .sort((a, b) => { if (a.timestamp === b.timestamp) { @@ -180,11 +185,15 @@ export function TradeHistory({ asset, orders: tradesData }) { }) .map((row) => { const amount = new Big(row.amount) + if (row.price === 0) { + return + } + const price = isInverted ? 1 / row.price : row.price return ( - {floatToFixed(row.price, priceDecimals, 6)} + {floatToFixed(price, priceDecimals, 6)}
- - + {/* */} + + {/* {t('amount')} ({assetVeryShortName}) + */} + + {t('amount')} ({isInverted ? 'ALGO' : assetVeryShortName}) {t('time')} diff --git a/components/Layout/OriginalLayout.jsx b/components/Layout/OriginalLayout.jsx index 0f01f84bc..05eaa190a 100644 --- a/components/Layout/OriginalLayout.jsx +++ b/components/Layout/OriginalLayout.jsx @@ -210,7 +210,7 @@ function MainLayout({ asset, children }) { - + diff --git a/components/Nav/SearchSidebar/SearchTable.jsx b/components/Nav/SearchSidebar/SearchTable.jsx index 1a0c75c06..b61ae72a3 100644 --- a/components/Nav/SearchSidebar/SearchTable.jsx +++ b/components/Nav/SearchSidebar/SearchTable.jsx @@ -24,7 +24,7 @@ import { getAssetTotalStatus, getIsRestricted, getIsRestrictedCountry } from '.. import { mdiAlertCircleOutline, mdiCheckDecagram, mdiStar } from '@mdi/js' import { useCallback, useMemo } from 'react' import { useEffect, useRef, useState } from 'react' - +import { StableAssets } from '@/components/StableAssets' import AlgoIcon from '@/components/Icon' import { DelistedAssets } from '@/components/DelistedAssets' import Icon from '@mdi/react' @@ -34,7 +34,7 @@ import Table from '@/components/Table' import Tooltip from 'components/Tooltip' import { flatten } from 'lodash' import floatToFixed from '@algodex/algodex-sdk/lib/utils/format/floatToFixed' -import {floatToFixedDisplay} from '@/services/display'; +import { floatToFixedDisplay } from '@/services/display'; import { formatUSDPrice } from '@/components/helpers' import { sortBy } from 'lodash' import styled from '@emotion/styled' @@ -73,15 +73,16 @@ export const mapToSearchResults = ({ unitName, isGeoBlocked, formattedASALiquidity, - formattedAlgoLiquidity + formattedAlgoLiquidity, + isInverted }) => { const price = formattedPrice ? floatToFixedDisplay(formattedPrice) : hasOrders ? '--' : null const change = !isNaN(parseFloat(priceChg24Pct)) ? floatToFixed(priceChg24Pct, 2) : hasOrders - ? '--' - : null + ? '--' + : null return { id: assetId, @@ -94,7 +95,8 @@ export const mapToSearchResults = ({ liquidityAsa: formattedASALiquidity, price, change, - decimals + decimals, + isInverted } } @@ -192,6 +194,7 @@ export const NavSearchTable = ({ const searchTableRef = useRef() const router = useRouter() + const {query} = router const { t } = useTranslation('assets') const filterByFavoritesFn = useCallback( @@ -202,6 +205,19 @@ export const NavSearchTable = ({ [setIsFilteringByFavorites, isFilteringByFavorites] ) + const formattedStableAsa = {} + const formattedAssets = StableAssets.forEach( + (asa, index) => (formattedStableAsa[StableAssets[index]] = asa) + ) + + const getInversionStatus = useCallback((id) => { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus && inversionStatus === 'true') { + return true + } + return false + }, [router]) + useEffect(() => { const handleResize = () => { /** @@ -262,7 +278,7 @@ export const NavSearchTable = ({ DelistedAssets.forEach((element) => { bannedAssets[element] = element }) - + // Remove banned assets const _acceptedAssets = assets.filter((asset) => !(asset.assetId in bannedAssets)) // Geoformatted assets @@ -295,20 +311,23 @@ export const NavSearchTable = ({ } }, [assets, handleRestrictedAsset, - isListingVerifiedAssets, isFilteringByFavorites, favoritesState]) + isListingVerifiedAssets, isFilteringByFavorites, favoritesState]) const AssetPriceCell = useCallback( ({ value, row }) => { + // console.log(value, 'value', row.original.id) + const isInverted = getInversionStatus(row.original.id) + const activeAssetId = query.id + const formattedValue = (isInverted && parseInt(activeAssetId) === row.original?.id) ? (1/value).toFixed(row.original?.decimals) : value return ( - {value} + {formattedValue}

- {value !== '--' ? {formatUSDPrice(algoPrice * value)} USD : ''} + {formattedValue !== '--' ? {formatUSDPrice(algoPrice * formattedValue)} USD : ''}

) @@ -322,6 +341,8 @@ export const NavSearchTable = ({ const AssetNameCell = useCallback( ({ value, row }) => { + const isInverted = getInversionStatus(row.original?.id) + const activeAssetId = query.id return (
- + {/* {value} {`/`} ALGO - {/* {row.original.verified && } */} - + */} + {(isInverted && parseInt(activeAssetId) === row.original?.id) ? ( + + ALGO + {`/`} + + {value} + + + ) : ( + + {value} + {`/`} + + ALGO + + + )}
{/*
*/}
@@ -398,7 +435,7 @@ export const NavSearchTable = ({
) }, - [handleFavoritesFn, toggleFavoritesFn] + [handleFavoritesFn, toggleFavoritesFn, getInversionStatus, router] ) AssetNameCell.propTypes = { @@ -488,13 +525,12 @@ export const NavSearchTable = ({ useEffect(() => { // Prefetch the top assets - searchResultData.slice(0,30).map(result => { + searchResultData.slice(0, 30).map(result => { const assetId = result.id - // console.log('zprefetching: ' + assetId) - router.prefetch('/trade/'+assetId) + router.prefetch('/trade/' + assetId) }) }, [router, searchResultData]) - + return ( . + */ + + + +// Current StableCoin List. +// Should check whether to decide using of assetid or asset name +export const StableAssets = [ + // 51435943, // USDC, + // 37074699, // USDC, + // 38718614, // USDC, + // 42279195, // USDT + // 94115664, //USDT + // 31566704, // USDC Mainnet + + // MAINNET ASSETS + 312769, + 2757561, + 27165954, + 31566704, + 137020565, + 137594422, + 142838028, + 226265212, + 226701642, + 251014570, + 266846137, + 283820866, + 287867876, + 300208676, + 310014962, + 329110405, + 386192725, + 386195940, + 388592191, + 390982964, + 393537671, + 400593267, + 403499324, + 409604194, + 441139422, + 444035862, + 444108880, + 452047208, + 461849439, + 463554836, + 465865291, + 470842789, + 490508385, + 509808838, + 511028589, + 511484048, + 523683256, + 542132831, + 544217506, + 558801604, + 559219992, + 567485181, + 569120128, + 571576867, + 591601798, + 607591690, + 610886011, + 674925395, + 694432641, + 712012773, + 724480511, + 744665252, + 747635241, + 752850639, + 753137719, + 756578163, + 781829486, + 782285248, + 792313023, + 793124631, + 818432243, + 833815143, + 844772414, + 886452112, + 900652777, + + // TESTNET ASSETS + 10458941, // USDC Testing + 10458941, + 21582668, + 23828111, + 32416641, + 58862619, + 70283957, + 79929599, + 85951079, + 87779982 +] diff --git a/components/Table/Cell.jsx b/components/Table/Cell.jsx index d0d6918be..e759d184f 100644 --- a/components/Table/Cell.jsx +++ b/components/Table/Cell.jsx @@ -25,6 +25,8 @@ import { useCallback, useMemo } from 'react' import { useEventDispatch } from '@/hooks/useEvents' import useTranslation from 'next-translate/useTranslation' import { getActiveNetwork } from 'services/environment' +import {useInversionStatus} from '@/hooks/utils/useInversionStatus' +import { useRouter } from 'next/router' const OrderTypeSpan = styled.span` color: ${({ theme, value }) => @@ -46,11 +48,22 @@ const TradeDetailLink = styled.a` */ export const AssetNameCell = ({ value, row }) => { const dispatcher = useEventDispatch() + const isInverted = useInversionStatus(row?.original.id) + const {query} = useRouter() + const assetId = useMemo(() => row?.original?.asset?.id || row?.original?.id, [row?.original?.asset?.id, row?.original?.id]) const onClick = useCallback(() => { dispatcher('clicked', 'asset') }, [dispatcher]) + const formattedPair = (value) => { + const splittedPair = value.split('/') + if (isInverted && parseInt(query.id) === assetId && typeof splittedPair[1] !== 'undefined') { + return `${splittedPair[1]}/${splittedPair[0]}` + } else { + return value + } + } return ( {/* */} @@ -76,15 +89,27 @@ AssetNameCell.propTypes = { row: PropTypes.any, value: PropTypes.any } * @returns {JSX.Element} * @constructor */ -export const OrderTypeCell = ({ value }) => { +export const OrderTypeCell = ({ value, row }) => { const { t } = useTranslation('orders') + const isInverted = useInversionStatus(row?.original.id) + const assetId = useMemo(() => row?.original?.asset?.id || row?.original?.id, + [row?.original?.asset?.id, row?.original?.id]) + const {query} = useRouter() + const formattedPair = (value) => { + if (isInverted && parseInt(query.id) === assetId) { + return value === 'BUY' ? t('sell') : t('buy') + } else { + return t(value.toLowerCase()) + } + } return ( - - {t(value.toLowerCase())} + + {/* {t(value.toLowerCase())} */} + {formattedPair(value)} ) } -OrderTypeCell.propTypes = { value: PropTypes.any } +OrderTypeCell.propTypes = { value: PropTypes.any, row: PropTypes.object } /** * Show Trade Detail diff --git a/components/Table/Cell.spec.jsx b/components/Table/Cell.spec.jsx index 1a3aebc73..a016efd6e 100644 --- a/components/Table/Cell.spec.jsx +++ b/components/Table/Cell.spec.jsx @@ -20,6 +20,7 @@ import { render } from 'test/test-utils' import generateAsset from '../../spec/OrderHistory' expect.extend(matchers) +jest.mock('next/dist/client/router', () => require('next-router-mock')) describe('Cell Component', () => { it('Should render Table Cell', () => { diff --git a/components/Table/PriceHeader.jsx b/components/Table/PriceHeader.jsx index cce9a35bd..acffb2bcc 100644 --- a/components/Table/PriceHeader.jsx +++ b/components/Table/PriceHeader.jsx @@ -35,15 +35,19 @@ export const TablePriceHeader = ({ title, textAlign }) => { const { t } = useTranslation('common') return ( - {t(title)} - + {title !== '' && title} + {!title && } ) } TablePriceHeader.propTypes = { title: PropTypes.string.isRequired, - textAlign: PropTypes.string.isRequired + textAlign: PropTypes.string +} + +TablePriceHeader.defaultProps = { + textAlign: 'left' } export default TablePriceHeader diff --git a/components/Table/Table.jsx b/components/Table/Table.jsx index ffcdf61b5..1a1cee177 100644 --- a/components/Table/Table.jsx +++ b/components/Table/Table.jsx @@ -28,6 +28,9 @@ import _ from 'lodash' import { css } from '@emotion/react' import { rgba } from 'polished' import styled from '@emotion/styled' +import { useInversionStatus } from '@/hooks/utils/useInversionStatus' +import { useRouter } from 'next/router' +import { floatToFixedDisplay } from '@/services/display'; // import { usePopperTooltip } from 'react-popper-tooltip' @@ -152,8 +155,8 @@ const Container = styled.div` top: 37px; width: 100%; height: ${({ optionalGridInfo }) => { - return optionalGridInfo && (optionalGridInfo.height - UPPERBODYHEIGHT) > 0 ? `${optionalGridInfo.height - UPPERBODYHEIGHT}px` : `inherit` - }}; + return optionalGridInfo && (optionalGridInfo.height - UPPERBODYHEIGHT) > 0 ? `${optionalGridInfo.height - UPPERBODYHEIGHT}px` : `inherit` + }}; overflow-y: scroll; @media (max-width: 996px) { height: ${({ tableSizeOnMobile }) => tableSizeOnMobile && `${tableSizeOnMobile.height - 128}px`}; @@ -167,6 +170,91 @@ const Container = styled.div` } ` // height: ${({ optionalGridInfo }) => optionalGridInfo && `${optionalGridInfo.height - 126}px`}; +// export function DefaultCell({ value, row }) { +// const isInverted = useInversionStatus(row.original.id) +// const assetId = useMemo(() => row?.original?.asset?.id || row?.original?.id, +// [row?.original?.asset?.id, row?.original?.id]) +// const { query } = useRouter() +// const formattedValue = useMemo(() => { +// if (isInverted && parseInt(query.id) === assetId && !isNaN(parseFloat(value))) { +// const val = 1 / value +// if (value == 0) { +// return value +// } else { +// return isInverted && parseInt(query.id) === assetId ? parseFloat(val).toFixed(6) : value +// } +// } else { +// return value +// } +// }, [isInverted, value]) +// return ( +// +// {formattedValue} +// +// ) +// } +// DefaultCell.propTypes = { value: PropTypes.any, row: PropTypes.object } + +export function InvertableCell({ value, row }) { + const isInverted = useInversionStatus(row.original.id) + const assetId = useMemo(() => row?.original?.asset?.id || row?.original?.id, + [row?.original?.asset?.id, row?.original?.id]) + const { query } = useRouter() + const {price, amount} = row.original + const formattedValue = useMemo(() => { + if (isInverted && parseInt(query.id) === assetId) { + return parseFloat(amount/(price * amount))?.toFixed(6) + } else { + return value + } + }, [isInverted, value]) + return ( + + {formattedValue} + + ) +} +InvertableCell.propTypes = { value: PropTypes.any, row: PropTypes.object } + +export function AmountInvertibleCell({ value, row }) { + const isInverted = useInversionStatus(row.original.id) + const assetId = useMemo(() => row?.original?.asset?.id || row?.original?.id, + [row?.original?.asset?.id, row?.original?.id]) + const { query } = useRouter() + const {price, amount} = row.original + const formattedValue = useMemo(() => { + if (isInverted && parseInt(query.id) === assetId) { + return price * amount + } else { + return amount + } + }, [isInverted, value]) + return ( + + {formattedValue} + + ) +} +AmountInvertibleCell.propTypes = { value: PropTypes.any, row: PropTypes.object } + export function DefaultCell({ value }) { return ( { const splited = value.toString().split('.') const _decimals = decimal > 6 ? 6 : decimal @@ -107,6 +107,14 @@ export function PlaceOrderForm({ showTitle = true, asset, onSubmit, components: return parseFloat(value) }, []) + const getInversionStatus = useCallback(() => { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus && inversionStatus === 'true') { + return true + } + return false + }, []) + const assetBalance = useMemo(() => { let res = 0 if (typeof wallet !== 'undefined' && Array.isArray(wallet.assets)) { @@ -118,15 +126,18 @@ export function PlaceOrderForm({ showTitle = true, asset, onSubmit, components: return res }, [wallet, asset]) + const maxSpendableAlgo = useMaxSpendableAlgo() + const algoBalance = useMemo(() => { let res = 0 if (typeof wallet !== 'undefined' && typeof wallet.amount === 'number') { - res = fromBaseUnits(wallet.amount) + res = fromBaseUnits(maxSpendableAlgo) + // res = fromBaseUnits(wallet.amount) } return res - }, [wallet]) + }, [maxSpendableAlgo, wallet]) - const getAdjOrderAmount = useCallback(({ amount, type, price }) => { + const getAdjOrderAmount = useCallback(({amount, type, price}) => { let adjAmount = amount || 0 let total = adjAmount * price if (type === 'buy' && total > algoBalance) { @@ -154,14 +165,13 @@ export function PlaceOrderForm({ showTitle = true, asset, onSubmit, components: // currentState.price = formatFloat(currentState.price, 6) || '' const amount = getAdjOrderAmount(currentState) - + currentState.amount = amount // Amount should be based on asset decimals // currentState.amount = formatFloat(amount, asset.decimals) || '' const price = currentState.price || 0 const total = parseFloat(amount) * parseFloat(price) - // Set Order Total precision currentState.total = formatFloat(total, 6) @@ -232,14 +242,22 @@ export function PlaceOrderForm({ showTitle = true, asset, onSubmit, components: setOrder(order) } }, []) - - const buttonProps = useMemo( - () => ({ - buy: { variant: 'primary', text: `${t('buy')} ${asset.name || asset.id}` }, - sell: { variant: 'danger', text: `${t('sell')} ${asset.name || asset.id}` } - }), - [asset] - ) + + const buttonProps = useCallback( + (key) => { + const isInverted = getInversionStatus() + const btnProps = { + buy: { + variant: 'primary', + text: `${t('buy')} ${(isInverted ? 'ALGO' : asset.name) || asset.id}` + }, + sell: { + variant: 'danger', + text: `${t('sell')} ${(isInverted ? 'ALGO' : asset.name) || asset.id}` + } + } + return btnProps[key]?.text + }, [asset]) useEvent('clicked', (data) => { if (data.type === 'order') { @@ -280,19 +298,34 @@ export function PlaceOrderForm({ showTitle = true, asset, onSubmit, components: return 0 }, [order.price, order.amount, algoBalance, assetBalance]) - - const maxSpendableAlgo = useMaxSpendableAlgo() - - const hasBalance = useMemo(() => { + + const hasBalance = useCallback(() => { + const isInverted = getInversionStatus() if (order.type === 'sell') { + if (isInverted) { + return maxSpendableAlgo + } return assetBalance > 0 } if (order.type === 'buy') { + if (isInverted) { + return assetBalance > 0 + } return maxSpendableAlgo } return false }, [order.type, assetBalance, maxSpendableAlgo]) + const hasTradeableAsset = useCallback(() => { + const isInverted = getInversionStatus() + if (isInverted) { + return order.total > assetBalance + } else { + return order.total > maxSpendableAlgo + } + }, [order, assetBalance, maxSpendableAlgo]) + + const isBelowMinOrderAmount = useMemo(() => { if (order.type === 'buy') { return new Big(order.total).lt(0.5) @@ -367,10 +400,13 @@ export function PlaceOrderForm({ showTitle = true, asset, onSubmit, components: const handleSubmit = useCallback( (e) => { e.preventDefault() - const formattedOrder = { ...order } - formattedOrder.price = formatFloat(formattedOrder.price, 6) - formattedOrder.amount = formatFloat(formattedOrder.amount, asset.decimals) - + const isInverted = getInversionStatus() + const formattedOrder = { ...order, type: isInverted ? order.type === 'buy' ? 'sell' : 'buy' : order.type } + const invertedOrderAmount = (formattedOrder.price/(formattedOrder.price))*order.price * order.amount + formattedOrder.price = isInverted ? parseFloat(formatFloat(1/formattedOrder.price, 6)) : formatFloat(formattedOrder.price, 6) + formattedOrder.amount = isInverted ? parseFloat(formatFloat(invertedOrderAmount, asset.decimals)) : formatFloat(formattedOrder.amount, asset.decimals) + formattedOrder.total = parseFloat(order.total) + let lastToastId = undefined let orderPromise const notifier = (msg) => { @@ -517,12 +553,12 @@ export function PlaceOrderForm({ showTitle = true, asset, onSubmit, components: {/**/} - {!hasBalance && ( + {!hasBalance() && ( {t('insufficient-balance')} )} - {hasBalance && ( + {hasBalance() && ( - {buttonProps[order.type || 'buy']?.text} + {/* {buttonProps[order.type || 'buy']?.text} */} + {buttonProps(order.type || 'buy')} )} @@ -582,7 +619,8 @@ PlaceOrderForm.propTypes = { id: PropTypes.number.isRequired, decimals: PropTypes.number.isRequired, name: PropTypes.string, - isGeoBlocked: PropTypes.bool + isGeoBlocked: PropTypes.bool, + isInverted: PropTypes.bool }).isRequired, /** * Wallet to execute Orders from diff --git a/components/Wallet/PlaceOrder/Form/TradeInputs.jsx b/components/Wallet/PlaceOrder/Form/TradeInputs.jsx index b21a36981..fa9f1bfb7 100644 --- a/components/Wallet/PlaceOrder/Form/TradeInputs.jsx +++ b/components/Wallet/PlaceOrder/Form/TradeInputs.jsx @@ -21,29 +21,32 @@ import Big from 'big.js' import { FormHelperText } from '@mui/material' import { default as MUIInputAdornment } from '@mui/material/InputAdornment' import { default as MaterialBox } from '@mui/material/Box' -import {NumericFormat} from 'react-number-format'; +import { NumericFormat } from 'react-number-format'; import OutlinedInput from '@/components/Input/OutlinedInput' import PropTypes from 'prop-types' import Slider from '@/components/Input/Slider' import Typography from '@mui/material/Typography' import USDPrice from '@/components/Wallet/PriceConversion/USDPrice' +import InvertedUSDInputPrice from '@/components/Wallet/PriceConversion/InvertedUSDInputPrice' import theme from '../../../../theme' import useTranslation from 'next-translate/useTranslation' - +import { useInversionStatus } from '@/hooks/utils/useInversionStatus' /** * * Render USD Price for an input component * @param {*} { value, id } * @return {*} */ -export const USDInputPrice = ({ value, id }) => { +export const USDInputPrice = ({ value, id, assetId = null }) => { return (
USD {id === 'price' ? 'Price' : 'Total'}{' '} - + { useInversionStatus(assetId) ? : + + } USD
@@ -52,7 +55,8 @@ export const USDInputPrice = ({ value, id }) => { USDInputPrice.propTypes = { value: PropTypes.number, - id: PropTypes.string + id: PropTypes.string, + assetId: PropTypes.number } export const NumberFormatCustom = forwardRef(function NumberFormatCustom(props, ref) { const { decimals, onChange, ...other } = props; @@ -95,23 +99,6 @@ export const TradeInputs = ({ microAlgo }) => { const { t } = useTranslation('place-order') - // if (!enableOrder[order.type]) { - // // @todo: make this better, this is a placeholder - // return ( - // - // {t('insufficient-balance')} - // - // ) - // } - - // const isErrorMsgVisible = () => { - // if (order.price === '0.00' || order.price === '') { - // return false - // } - // if (order.price < microAlgo) { - // return true - // } - // } const inputPlaceHolder = useMemo(() => asset.decimals !== 0 ? `0.${'0'.repeat(Math.max(0, asset.decimals - 4))}` : '0', [asset]) const marks = [ @@ -145,8 +132,102 @@ export const TradeInputs = ({ return true } }, [order, microAlgo]) - return ( - + + const invertedAssetInput = () => { + return <> + + {t('price')} + + } + endAdornment={ + + {asset.name} + + } + error={isErrorMsgVisible} + /> + + { + isErrorMsgVisible && order.execution !== 'market' ? ( + + Price cannot be less than {microAlgo} + + ) : ( + + ) + } + + + {t('amount')} + + } + endAdornment={ + + ALGO + + } + /> + + } + + const regularAssetInput = () => { + return <> } /> + + } + return ( + + {useInversionStatus(asset.id) ? invertedAssetInput() : regularAssetInput()} - ALGO + + {useInversionStatus(asset.id) ? asset.name : 'ALGO'} + } /> diff --git a/components/Wallet/PriceConversion/InvertedUSDInputPrice.jsx b/components/Wallet/PriceConversion/InvertedUSDInputPrice.jsx new file mode 100644 index 000000000..da42954cb --- /dev/null +++ b/components/Wallet/PriceConversion/InvertedUSDInputPrice.jsx @@ -0,0 +1,56 @@ +/* + * Algodex Frontend (algodex-react) + * Copyright (C) 2021 - 2022 Algodex VASP (BVI) Corp. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import PropTypes from 'prop-types' +import { formatUSDPrice } from '@/components/helpers' +import { withOtherAssetPriceQuery } from '@/hooks' +import { useRouter } from 'next/router' +import Icon from '@mdi/react' +import { mdiApproximatelyEqual } from '@mdi/js' + +export function InvertedUSDInputPrice(props) { + const { query } = useRouter() + const invertedAssetPrice = props[`${query.id}`]?.price + const { asaWorth, priceToConvert, currency } = props + if (invertedAssetPrice) { + return ( + <> + + {currency} + ${formatUSDPrice(asaWorth * priceToConvert * invertedAssetPrice)} + + + + ) + } else { + return <> + } + +} + +InvertedUSDInputPrice.propTypes = { + priceToConvert: PropTypes.number, + asaWorth: PropTypes.number, + currency: PropTypes.string +} + +InvertedUSDInputPrice.defaultProps = { + priceToConvert: 0, + asaWorth: 1, + currency: '' +} + +export default withOtherAssetPriceQuery(InvertedUSDInputPrice) diff --git a/components/Wallet/PriceConversion/USDPrice.jsx b/components/Wallet/PriceConversion/USDPrice.jsx index 5b42caff1..8627c4616 100644 --- a/components/Wallet/PriceConversion/USDPrice.jsx +++ b/components/Wallet/PriceConversion/USDPrice.jsx @@ -13,11 +13,32 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import axios from 'axios' +export const fetchCurrentPrices = async () => { + // const url = `https://api.hsforms.com/submissions/v3/integration/submit/${process.env.NEXT_PUBLIC_PORTAL_ID}/${formId}` + const url = `https://testnet.analytics.tinyman.org/api/v1/current-asset-prices/` + // const config = { + // headers: { + // 'Content-Type': 'application/json' + // } + // } + const response = await axios + .get(url) + .then((res) => { + return res.data + }) + .catch((error) => { + return error + }) + + return response +} import PropTypes from 'prop-types' import { formatUSDPrice } from '@/components/helpers' import { withAlgorandPriceQuery } from '@/hooks' export function USDPrice({ algoPrice, asaWorth, priceToConvert, currency }) { + // const res = fetchCurrentPrices() return ( {currency} diff --git a/components/Wallet/Table/AssetsTable.jsx b/components/Wallet/Table/AssetsTable.jsx index c76ce2d69..43dc53c28 100644 --- a/components/Wallet/Table/AssetsTable.jsx +++ b/components/Wallet/Table/AssetsTable.jsx @@ -74,10 +74,15 @@ export function AssetsTable({ assets }) { // console.log(`AssetsTable(`, arguments[0], `)`) const { t } = useTranslation('orders') + // const isInverted = useInversionStatus(asset.id) const walletAssetsTableState = useUserStore((state) => state.walletAssetsTableState) const setWalletAssetsTableState = useUserStore((state) => state.setWalletAssetsTableState) - + const formatAssetsList = assets?.map((asset) => { + return { + ...asset, + } + }) const columns = useMemo( () => [ { @@ -121,7 +126,7 @@ export function AssetsTable({ assets }) { initialState={walletAssetsTableState} onStateChange={(state) => setWalletAssetsTableState(state)} columns={columns} - data={assets || []} + data={formatAssetsList || []} /> diff --git a/components/Wallet/Table/AssetsTable.spec.js b/components/Wallet/Table/AssetsTable.spec.js index 70a05e2f8..3c3702374 100644 --- a/components/Wallet/Table/AssetsTable.spec.js +++ b/components/Wallet/Table/AssetsTable.spec.js @@ -27,6 +27,7 @@ const wallet = { afterEach(() => { cleanup() }) +jest.mock('next/dist/client/router', () => require('next-router-mock')) describe('Assets', () => { it('should not show any rows if no data is provided', async () => { diff --git a/components/Wallet/Table/OpenOrdersTable.jsx b/components/Wallet/Table/OpenOrdersTable.jsx index d83569b61..95d0b128a 100644 --- a/components/Wallet/Table/OpenOrdersTable.jsx +++ b/components/Wallet/Table/OpenOrdersTable.jsx @@ -17,6 +17,8 @@ import Table, { AssetNameCell, DefaultCell, + InvertableCell, + AmountInvertibleCell, ExpandTradeDetail, OrderTypeCell } from '@/components/Table' @@ -52,7 +54,6 @@ const TableWrapper = styled.div` display: none; } ` - const OrderCancelButton = styled.button` cursor: pointer; background: none; @@ -103,7 +104,13 @@ export function OpenOrdersTable({ orders: _orders }) { const walletOpenOrdersTableState = useUserStore((state) => state.walletOpenOrdersTableState) const setWalletOpenOrdersTableState = useUserStore((state) => state.setWalletOpenOrdersTableState) - + const getInversionStatus = useCallback(() => { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus && inversionStatus === 'true') { + return true + } + return false + }, []) const OrderCancelCell = useCallback( ({ data, cell }) => { const handleCancelOrder = async () => { @@ -193,7 +200,7 @@ export function OpenOrdersTable({ orders: _orders }) { }, [t, openOrdersData, wallet] ) - + const inversionStatus = getInversionStatus() const columns = useMemo( () => [ { @@ -210,9 +217,9 @@ export function OpenOrdersTable({ orders: _orders }) { Cell: AssetNameCell }, { - Header: t('price') + ' (ALGO)', + Header: `${t('price')} ${!inversionStatus ? '(ALGO)' : ''}`, accessor: 'price', - Cell: DefaultCell + Cell: InvertableCell }, { Header: t('type'), @@ -222,7 +229,7 @@ export function OpenOrdersTable({ orders: _orders }) { { Header: t('amount'), accessor: 'amount', - Cell: DefaultCell + Cell: AmountInvertibleCell }, { Header: t('status'), @@ -236,7 +243,7 @@ export function OpenOrdersTable({ orders: _orders }) { disableSortBy: true } ], - [t, OrderCancelCell] + [t, OrderCancelCell, inversionStatus] ) return ( diff --git a/components/Wallet/Table/OpenOrdersTable.spec.js b/components/Wallet/Table/OpenOrdersTable.spec.js index 1cf7634bf..458d405d5 100644 --- a/components/Wallet/Table/OpenOrdersTable.spec.js +++ b/components/Wallet/Table/OpenOrdersTable.spec.js @@ -22,6 +22,7 @@ const OPEN_ORDER_ROW = 'cancel-order-button' const wallet = { address: 'TJFFNUYWHPPIYDE4DGGYPGHWKGAPJEWP3DGE5THZS3B2M2XIAPQ2WY3X4I' } +jest.mock('next/dist/client/router', () => require('next-router-mock')) describe('OpenOrders', () => { it('should not show any rows if no data is provided', () => { diff --git a/components/Wallet/Table/TradeHistoryTable.jsx b/components/Wallet/Table/TradeHistoryTable.jsx index e3c30187c..3ed0c9f43 100644 --- a/components/Wallet/Table/TradeHistoryTable.jsx +++ b/components/Wallet/Table/TradeHistoryTable.jsx @@ -17,13 +17,14 @@ import Table, { AssetNameCell, DefaultCell, + InvertableCell, + AmountInvertibleCell, ExpandTradeDetail, OrderTypeCell } from '@/components/Table' - import PropTypes from 'prop-types' import styled from '@emotion/styled' -import { useMemo } from 'react' +import { useMemo, useCallback } from 'react' import useTranslation from 'next-translate/useTranslation' import useUserStore from '@/store/use-user-state' import { withWalletTradeHistoryQuery } from '@/hooks' @@ -55,22 +56,31 @@ const TableWrapper = styled.div` * @constructor */ export function TradeHistoryTable({ orders }) { - //console.log(`TradeHistoryTable(`, arguments[0], `)`) const { t } = useTranslation('orders') const walletOrderHistoryTableState = useUserStore((state) => state.walletOrderHistoryTableState) const setWalletOrderHistoryTableState = useUserStore( (state) => state.setWalletOrderHistoryTableState ) + const getInversionStatus = useCallback(() => { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus && inversionStatus === 'true') { + return true + } + return false + }, []) const _formattedOrders = useMemo(() => { return orders.map((order) => { const _order = { ...order, - price: floatToFixedDisplay(order.price) + price: floatToFixedDisplay(order.price), + isInverted: getInversionStatus() } return _order }) }, [orders]) - + + + const inversionStatus = getInversionStatus() const columns = useMemo( () => [ { @@ -93,17 +103,17 @@ export function TradeHistoryTable({ orders }) { }, { - Header: t('price') + ' (ALGO)', + Header: `${t('price')} ${!inversionStatus ? '(ALGO)' : ''}`, accessor: 'price', - Cell: DefaultCell + Cell: InvertableCell }, { Header: t('amount'), accessor: 'amount', - Cell: DefaultCell + Cell: AmountInvertibleCell } ], - [t] + [t, inversionStatus] ) return ( diff --git a/components/Wallet/Table/TradeHistoryTable.spec.js b/components/Wallet/Table/TradeHistoryTable.spec.js index 1012f764e..e71dd417d 100644 --- a/components/Wallet/Table/TradeHistoryTable.spec.js +++ b/components/Wallet/Table/TradeHistoryTable.spec.js @@ -19,6 +19,7 @@ import { TradeHistoryTable } from './TradeHistoryTable' import { render, waitFor } from 'test/test-utils' const ORDER_HISTORY_ROW = 'default-cell' +jest.mock('next/dist/client/router', () => require('next-router-mock')) const wallet = { address: 'TJFFNUYWHPPIYDE4DGGYPGHWKGAPJEWP3DGE5THZS3B2M2XIAPQ2WY3X4I' diff --git a/hooks/http/useAlgoExplorer.js b/hooks/http/useAlgoExplorer.js index 3cd428d51..cded66ccf 100644 --- a/hooks/http/useAlgoExplorer.js +++ b/hooks/http/useAlgoExplorer.js @@ -19,6 +19,8 @@ import Spinner from '../components/Spinner'; import useAlgodex from '../useAlgodex.js'; import {useQuery} from 'react-query'; import withQuery from '../utils/withQuery'; +import axios from 'axios' + const DEBUG = process.env.NEXT_PUBLIC_DEBUG || process.env.DEBUG || false; const refetchInterval = 3000; @@ -27,6 +29,21 @@ const components = { ServiceError, }; +const fetchCurrentPrices = async () => { + const testnet = `https://testnet.analytics.tinyman.org/api/v1/current-asset-prices/` + const mainnet = `https://mainnet.analytics.tinyman.org/api/v1/current-asset-prices/` + const url = process.env.NEXT_PUBLIC_ALGORAND_NETWORK === 'testnet' ? testnet : mainnet + const response = await axios + .get(url) + .then((res) => { + return res.data + }) + .catch((error) => { + return error + }) + return response +} + /** * @@ -87,6 +104,20 @@ export function useAlgorandPriceQuery({ ['fetchAlgorandPrice', {query}], () => http.explorer.fetchAlgorandPrice(query), options, + ); +} + +export function useOtherAssetPriceQuery({ + query = '', + isInverted, + options = { + refetchInterval: query === '' ? refetchInterval : 20000, + }, +} = {}) { + return useQuery( + ['fetchCurrentPrices'], + () => fetchCurrentPrices(isInverted), + options, ); } @@ -104,3 +135,17 @@ export function withAlgorandPriceQuery(Component, options) { ...options, }); } + +/** + * With Algorand Price Query + * @param {JSX.Element| Function} Component Component to wrap + * @param {object} [options] Options to pass to withQuery + * @return {JSX.Element} + */ +export function withOtherAssetPriceQuery(Component, options) { + return withQuery(Component, { + hook: useOtherAssetPriceQuery, + components, + ...options, + }); +} diff --git a/hooks/utils/useInversionStatus.js b/hooks/utils/useInversionStatus.js new file mode 100644 index 000000000..f6532c2e7 --- /dev/null +++ b/hooks/utils/useInversionStatus.js @@ -0,0 +1,32 @@ +/* + * Algodex Frontend (algodex-react) + * Copyright (C) 2021 - 2022 Algodex VASP (BVI) Corp. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { StableAssets } from '@/components/StableAssets' + +/** + * + * @param {number} id + * @return {boolean} + */ +export function useInversionStatus(id) { + const inversionStatus = localStorage.getItem('inversionStatus') + if (inversionStatus && inversionStatus === 'true') { + return true + } + return false +} + +export default useInversionStatus diff --git a/hooks/utils/withQuery.js b/hooks/utils/withQuery.js index 96ba37cd2..a582dd09f 100644 --- a/hooks/utils/withQuery.js +++ b/hooks/utils/withQuery.js @@ -46,6 +46,7 @@ export default function withQuery(Component, {hook = useQuery, components}) { */ function withQueryWrapper(props) { const {isSuccess, isLoading, isError, data, error} = hook(props); + // console.log(data, 'data inside here') if (isSuccess) return ; if (isLoading) return ; if (isError) return ; diff --git a/pages/trade/[id].js b/pages/trade/[id].js index a28ab1bdf..2835bbd79 100644 --- a/pages/trade/[id].js +++ b/pages/trade/[id].js @@ -39,6 +39,8 @@ import useDebounce from '@/hooks/useDebounce' import { useRouter } from 'next/router' import useUserStore from '@/store/use-user-state' import useWallets from '@/hooks/useWallets' +import { StableAssets } from '@/components/StableAssets' +// import {useInversionStatus} from '@/hooks/utils/useInversionStatus' /** * Fetch Traded Asset Paths @@ -126,7 +128,9 @@ export async function getStaticProps({ params: { id } }) { if (typeof staticAssetPrice.isTraded !== 'undefined') { staticExplorerAsset.price_info = staticAssetPrice } - + // const isInverted = localStorage.getItem('inversionStatus') + // console.log(isInverted, 'hello here') + staticExplorerAsset.isInverted = StableAssets.includes(parseInt(id)) if (typeof staticExplorerAsset.name === 'undefined') { staticExplorerAsset.name = '' } diff --git a/public/Invert.svg b/public/Invert.svg new file mode 100644 index 000000000..403d5ce7f --- /dev/null +++ b/public/Invert.svg @@ -0,0 +1,3 @@ + + +