From eabe9a8cf0ca9a0451de3e5808481aae442c58af Mon Sep 17 00:00:00 2001 From: Hossein Date: Mon, 1 May 2023 14:19:46 +0330 Subject: [PATCH] #5, Add buyOrder & sellOrder api --- src/index.css | 5 + src/main/Mobile/Mobile.js | 4 +- .../Sections/OrderSection/OrderSection.js | 4 +- .../Market/components/Order/Order.module.css | 14 +- .../Market/components/Order/api/order.js | 24 +- .../Order/components/BuyOrder/BuyOrder.js | 269 +++++++------- .../Order/components/SellOrder/SellOrder.js | 327 +++++++++++++++++- 7 files changed, 505 insertions(+), 142 deletions(-) diff --git a/src/index.css b/src/index.css index a027bb2..d76c1cf 100644 --- a/src/index.css +++ b/src/index.css @@ -664,6 +664,11 @@ a { -ms-flex: 0.6; flex: 0.6; } +.col-52 { + -webkit-box-flex: 0.52; + -ms-flex: 0.52; + flex: 0.52; +} .col-55 { diff --git a/src/main/Mobile/Mobile.js b/src/main/Mobile/Mobile.js index 49f983e..01c37e9 100644 --- a/src/main/Mobile/Mobile.js +++ b/src/main/Mobile/Mobile.js @@ -58,14 +58,14 @@ const Mobile = () => { dispatch(setLastPriceInitiate()); }, 3000) - const Toast = () => { -
+
{showChart ? : }
-
+
diff --git a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/Order.module.css b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/Order.module.css index 8d3c813..2594040 100644 --- a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/Order.module.css +++ b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/Order.module.css @@ -3,11 +3,11 @@ } .content { height: 100%; - padding: 1vh 0.5vw; + /* padding: 1vh 0.5vw;*/ } .thisButton { - width: 100%; + /*width: 100%;*/ } .alertSubmit{ background-color: var(--orange); @@ -45,4 +45,14 @@ } .stopMarket input { cursor: not-allowed; +} + +.smallInput :global(.lead){ + width: 24%; +} +.smallInput :global(input){ + width: 52%; +} +.smallInput :global(.after){ + width: 24%; } \ No newline at end of file diff --git a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/api/order.js b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/api/order.js index 47fbebc..9f0f853 100644 --- a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/api/order.js +++ b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/api/order.js @@ -1,26 +1,18 @@ import axios from "axios"; -export const createOrder = async (activePair , side , order) => { - const timestamp = Date.now() +export const createOrder = async (symbol, side, order, type = "LIMIT", timeInForce = "GTC", timestamp = Date.now().toString()) => { const params = new URLSearchParams(); - params.append('symbol', activePair.symbol); + params.append('symbol',symbol ); params.append('side', side); - params.append('type', "LIMIT"); - params.append('timeInForce', "GTC"); - params.append('timestamp', timestamp.toString()); + params.append('type', type); + params.append('timeInForce', timeInForce); + params.append('timestamp', timestamp); params.append('quantity', order.reqAmount.toString()); params.append('price', order.pricePerUnit.toString()); - return await axios.post(`/api/v3/order`, null , { + return axios.post(`/api/v3/order`, null, { params, - headers : { - 'Content-Type':'application/x-www-form-urlencoded' + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' } - }).then((res) => { - return res; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; }) } diff --git a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/BuyOrder/BuyOrder.js b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/BuyOrder/BuyOrder.js index d4e1f99..d8bd2af 100644 --- a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/BuyOrder/BuyOrder.js +++ b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/BuyOrder/BuyOrder.js @@ -3,7 +3,7 @@ import classes from "../../Order.module.css"; import {Trans, useTranslation} from "react-i18next"; import VerticalNumberInput from "../../../../../../../../../../../../components/VerticalTextInput/VerticalNumberInput"; import {setLastTransaction} from "../../../../../../../../../../../../store/actions/auth"; -import {connect} from "react-redux"; +import {connect, useDispatch, useSelector} from "react-redux"; import {BN, parsePriceString} from "../../../../../../../../../../../../utils/utils"; import {useNavigate} from "react-router-dom"; import Icon from "../../../../../../../../../../../../components/Icon/Icon"; @@ -12,15 +12,30 @@ import {Login as LoginRoute} from "../../../../../../../../../../Routes/routes"; import {toast} from "react-hot-toast"; import {images} from "../../../../../../../../../../../../assets/images"; import {createOrder} from "../../api/order"; +import {useGetUserAccount} from "../../../../../../../../../../../../queries/hooks/useGetUserAccount"; +import NumberInput from "../../../../../../../../../../../../components/NumberInput/NumberInput"; -const BuyOrder = (props) => { +const BuyOrder = () => { const navigate = useNavigate(); + const {t} = useTranslation(); + const dispatch = useDispatch(); + + const {data: userAccount} = useGetUserAccount() const [isLoading, setIsLoading] = useState(false) - const {wallets, activePair, tradeFee, bestBuyPrice, accessToken, isLogin, selectedBuyOrder} = props + + const activePair = useSelector((state) => state.exchange.activePair) + const bestBuyPrice = useSelector((state) => state.exchange.activePairOrders.bestBuyPrice) + const selectedBuyOrder = useSelector((state) => state.exchange.activePairOrders.selectedBuyOrder) + + const tradeFee = useSelector((state) => state.auth.tradeFee) + const isLogin = useSelector((state) => state.auth.isLogin) + + const quote = userAccount?.wallets[activePair.quoteAsset]?.free || 0; + const [alert, setAlert] = useState({ submit: false, reqAmount: null, @@ -36,13 +51,39 @@ const BuyOrder = (props) => { pricePerUnit: new BN(0), totalPrice: new BN(0), }); + + /*useEffect(() => { + if (alert.submit) { + setAlert({ + ...alert, submit: false + }) + } + }, [order, activePair])*/ + useEffect(() => { if (alert.submit) { setAlert({ ...alert, submit: false }) } - }, [order, activePair]) + }, [order]) + + useEffect(() => { + setOrder({ + tradeFee: new BN(0), + stopLimit: false, + stopMarket: false, + stopPrice: new BN(0), + reqAmount: new BN(0), + pricePerUnit: new BN(0), + totalPrice: new BN(0), + }) + setAlert({ + submit: false, + reqAmount: null, + totalPrice: null, + }) + }, [activePair]) const currencyValidator = (key, val, rule) => { if (!val.isZero() && val.isLessThan(rule.min)) { @@ -78,7 +119,7 @@ const BuyOrder = (props) => { ...alert, [key]: () }) } @@ -123,7 +164,7 @@ const BuyOrder = (props) => { }; useEffect(() => { - if(order.totalPrice.isGreaterThan(wallets[activePair.quoteAsset].free)){ + if (order.totalPrice.isGreaterThan(quote)) { return setAlert({ ...alert, totalPrice: t('orders.notEnoughBalance') @@ -163,10 +204,24 @@ const BuyOrder = (props) => { currencyValidator("reqAmount", reqAmount, activePair.baseRange); }, [selectedBuyOrder]); + useEffect(() => { + const reqAmount = new BN(selectedBuyOrder.amount); + const pricePerUnit = new BN(selectedBuyOrder.pricePerUnit); + setOrder({ + ...order, + reqAmount, + pricePerUnit: pricePerUnit, + totalPrice: reqAmount.multipliedBy(pricePerUnit).decimalPlaces(activePair.quoteAssetPrecision), + tradeFee: reqAmount.multipliedBy(tradeFee[activePair.quoteAsset]).decimalPlaces(activePair.baseAssetPrecision), + }); + currencyValidator("reqAmount", reqAmount, activePair.baseRange); + }, [selectedBuyOrder]); + const fillBuyByWallet = () => { + if(order.pricePerUnit.isEqualTo(0) && bestBuyPrice === 0 ) return toast.error(t("orders.hasNoOffer")); if (order.pricePerUnit.isEqualTo(0)) { - const totalPrice = new BN(wallets[activePair.quoteAsset].free); + const totalPrice = new BN(quote); setOrder({ ...order, reqAmount: totalPrice.dividedBy(bestBuyPrice).decimalPlaces(activePair.baseAssetPrecision), @@ -176,7 +231,7 @@ const BuyOrder = (props) => { }); } else { buyPriceHandler( - wallets[activePair.quoteAsset].free.toString(), + quote.toString(), "totalPrice", ); } @@ -189,6 +244,8 @@ const BuyOrder = (props) => { ); }; + console.log("activePair.symbol" , activePair.symbol) + const submit = async () => { if (!isLogin) { navigate(LoginRoute, { replace: true }); @@ -198,121 +255,117 @@ const BuyOrder = (props) => { return false } setIsLoading(true) - const submitOrder = await createOrder(activePair, "BUY", accessToken, order) - if (!submitOrder) { - setIsLoading(false) - } - if (submitOrder.status === 200) { - setOrder({ - tradeFee: new BN(0), - stopLimit: false, - stopMarket: false, - stopPrice: new BN(0), - reqAmount: new BN(0), - - pricePerUnit: new BN(0), - totalPrice: new BN(0), - }) - toast.success(); - setTimeout(() => props.setLastTransaction(submitOrder.data.transactTime), 2000); - } else { + createOrder(activePair.symbol, "BUY", order) + .then((res) => { + setOrder({ + tradeFee: new BN(0), + stopLimit: false, + stopMarket: false, + stopPrice: new BN(0), + reqAmount: new BN(0), + pricePerUnit: new BN(0), + totalPrice: new BN(0), + }) + toast.success(); + dispatch(setLastTransaction(res.data.transactTime)) + }).catch(() => { toast.error(t("orders.error")); setAlert({ ...alert, submit: true }) - } - setIsLoading(false) + }).finally(() => { + setIsLoading(false) + }) } const submitButtonTextHandler = () => { if (isLoading) { return linearLoading } - if (alert.submit) { + /*if (alert.submit) { return {t("login.loginError")} - } + }*/ if (isLogin) { + return t("buy") + } + /*if (isLogin) { return {t("buy")} {order.reqAmount.minus(order.tradeFee).decimalPlaces(activePair.baseAssetPrecision).toNumber()}{" "} {t("currency." + activePair.baseAsset)} - } + }*/ return t("pleaseLogin") } - const volumeTop =
{fillBuyByBestPrice()}}> - - {wallets[activePair.quoteAsset].free.toLocaleString()}{" "}{t("currency." + activePair.quoteAsset)} -
- - const pricePerUnitTop =
{fillBuyByWallet()}}> - - {bestBuyPrice.toLocaleString()}{" "}{t("currency." + activePair.quoteAsset)} -
- - const totalPriceTop =
-

{t("commission")}

-

- {order.tradeFee.toFormat()}{" "} - {t("currency." + activePair.baseAsset)} -

-
- return ( -
- -
-
- buyPriceHandler(e.target.value, "reqAmount")} - //alert={alert.reqAmount} - /> -
-
- buyPriceHandler(e.target.value, "pricePerUnit")} - /> +
+ +
+
{fillBuyByWallet()}}> + {t("orders.availableAmount")}: + {new BN(quote).toFormat()}{" "}{t("currency." + activePair.quoteAsset)}
-
-
-
- buyPriceHandler(e.target.value, "totalPrice")} - //alert={alert.totalPrice} - /> +
fillBuyByBestPrice()}> + {t("orders.bestOffer")}: + {new BN(bestBuyPrice).toFormat()}{" "}{t("currency." + activePair.quoteAsset)}
-
-
-
@@ -321,22 +374,6 @@ const BuyOrder = (props) => { ); }; -const mapStateToProps = (state) => { - return { - activePair: state.exchange.activePair, - bestBuyPrice: state.exchange.activePairOrders.bestBuyPrice, - selectedBuyOrder: state.exchange.activePairOrders.selectedBuyOrder, - wallets: state.auth.wallets, - tradeFee: state.auth.tradeFee, - accessToken: state.auth.accessToken, - isLogin: state.auth.isLogin, - }; -}; -const mapDispatchToProps = (dispatch) => { - return { - setLastTransaction: (time) => dispatch(setLastTransaction(time)), - }; -}; -export default connect(mapStateToProps, mapDispatchToProps)(BuyOrder); +export default BuyOrder; diff --git a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/SellOrder/SellOrder.js b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/SellOrder/SellOrder.js index 7bb5f51..68ad7e5 100644 --- a/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/SellOrder/SellOrder.js +++ b/src/main/Mobile/Pages/UserPanel/Secttions/Content/components/Market/components/Order/components/SellOrder/SellOrder.js @@ -1,17 +1,336 @@ -import React from "react"; +import React, {useEffect, useState} from "react"; import classes from "../../Order.module.css"; -import {useTranslation} from "react-i18next"; +import {Trans, useTranslation} from "react-i18next"; +import {useNavigate} from "react-router-dom"; +import {useDispatch, useSelector} from "react-redux"; +import {useGetUserAccount} from "../../../../../../../../../../../../queries/hooks/useGetUserAccount"; +import {toast} from "react-hot-toast"; +import {setLastTransaction} from "../../../../../../../../../../../../store/actions/auth"; +import {BN, parsePriceString} from "../../../../../../../../../../../../utils/utils"; +import {createOrder} from "js-api-client"; +import {images} from "../../../../../../../../../../../../assets/images"; +import Icon from "../../../../../../../../../../../../components/Icon/Icon"; +import NumberInput from "../../../../../../../../../../../../components/NumberInput/NumberInput"; +import Button from "../../../../../../../../../../../../components/Button/Button"; + + const SellOrder = () => { + const navigate = useNavigate(); const {t} = useTranslation(); + const dispatch = useDispatch(); + const [isLoading, setIsLoading] = useState(false) - return ( -
+ const tradeFee = useSelector((state) => state.auth.tradeFee) + const isLogin = useSelector((state) => state.auth.isLogin) + const activePair = useSelector((state) => state.exchange.activePair) + const bestSellPrice = useSelector((state) => state.exchange.activePairOrders.bestSellPrice) + const selectedSellOrder = useSelector((state) => state.exchange.activePairOrders.selectedSellOrder) + + const {data: userAccount} = useGetUserAccount() + const base = userAccount?.wallets[activePair.baseAsset]?.free || 0; + const quote = userAccount?.wallets[activePair.quoteAsset]?.free || 0; + + const [alert, setAlert] = useState({ + reqAmount: null, + submit: false, + }); + + const [order, setOrder] = useState({ + tradeFee: new BN(0), + stopLimit: false, + stopMarket: false, + stopPrice: new BN(0), + reqAmount: new BN(0), + pricePerUnit: new BN(0), + totalPrice: new BN(0), + }); + + useEffect(() => { + if (alert.submit) { + setAlert({ + ...alert, submit: false + }) + } + }, [order]) + + useEffect(() => { + setOrder({ + tradeFee: new BN(0), + stopLimit: false, + stopMarket: false, + stopPrice: new BN(0), + reqAmount: new BN(0), + pricePerUnit: new BN(0), + totalPrice: new BN(0), + }) + setAlert({ + submit: false, + reqAmount: null, + totalPrice: null, + }) + }, [activePair]) + + + const currencyValidator = (key, val, rule) => { + if (!val.isZero() && val.isLessThan(rule.min)) { + return setAlert({ + ...alert, + [key]: ( + + ), + }); + } + if (val.isGreaterThan(rule.max)) { + return setAlert({ + ...alert, + [key]: + () + }) + } + if (!val.mod(rule.step).isZero()) { + return setAlert({ + ...alert, + [key]: () + }) + } + return setAlert({...alert, [key]: null}); + }; + + const sellPriceHandler = (value, key) => { + value = parsePriceString(value); + switch (key) { + case "reqAmount": + const reqAmount = new BN(value); + currencyValidator("reqAmount", reqAmount, activePair.baseRange); + setOrder({ + ...order, + reqAmount, + totalPrice: reqAmount.multipliedBy(order.pricePerUnit).decimalPlaces(activePair.quoteAssetPrecision), + tradeFee: reqAmount.multipliedBy(order.pricePerUnit).multipliedBy(tradeFee[activePair.quoteAsset]).decimalPlaces(activePair.baseAssetPrecision), + }); + break; + case "pricePerUnit": + const pricePerUnit = new BN(value); + setOrder({ + ...order, + pricePerUnit: pricePerUnit, + totalPrice: pricePerUnit.multipliedBy(order.reqAmount).decimalPlaces(activePair.quoteAssetPrecision), + tradeFee: pricePerUnit.multipliedBy(order.reqAmount).multipliedBy(tradeFee[activePair.quoteAsset]).decimalPlaces(activePair.baseAssetPrecision), + }); + break; + case "totalPrice": + const totalPrice = new BN(value); + const req = totalPrice.dividedBy(order.pricePerUnit).decimalPlaces(activePair.baseAssetPrecision); + setOrder({ + ...order, + reqAmount: req.isFinite() ? req : new BN(0), + totalPrice, + tradeFee: req.isFinite() ? totalPrice.multipliedBy(tradeFee[activePair.quoteAsset]).decimalPlaces(activePair.baseAssetPrecision) : new BN(0), + }); + currencyValidator("reqAmount", req, activePair.baseRange); + break; + default: + } + }; + useEffect(() => { + setOrder((prevState) => ({ + ...order, + tradeFee: prevState.totalPrice.multipliedBy(tradeFee[activePair.quoteAsset]).decimalPlaces(activePair.baseAssetPrecision), + })); + }, [tradeFee]); + + useEffect(() => { + sellPriceHandler( + bestSellPrice.toString(), + "pricePerUnit", + ); + }, [order.stopMarket]); + + useEffect(() => { + const reqAmount = new BN(selectedSellOrder.amount); + const pricePerUnit = new BN(selectedSellOrder.pricePerUnit); + setOrder({ + ...order, + reqAmount, + pricePerUnit: pricePerUnit, + totalPrice: reqAmount.multipliedBy(pricePerUnit).decimalPlaces(activePair.quoteAssetPrecision), + tradeFee: reqAmount.multipliedBy(tradeFee[activePair.quoteAsset]).decimalPlaces(activePair.baseAssetPrecision), + }); + currencyValidator("reqAmount", reqAmount, activePair.baseRange); + }, [selectedSellOrder]); + + const fillSellByWallet = () => { + if(order.pricePerUnit.isEqualTo(0) && bestSellPrice === 0 ) return toast.error(t("orders.hasNoOffer")); + if (order.pricePerUnit.isEqualTo(0)) { + const totalPrice = new BN(quote); + setOrder({ + ...order, + reqAmount: totalPrice.dividedBy(bestSellPrice).decimalPlaces(activePair.baseAssetPrecision), + pricePerUnit: new BN(bestSellPrice), + totalPrice, + tradeFee: totalPrice.multipliedBy(tradeFee[activePair.quoteAsset]).decimalPlaces(activePair.baseAssetPrecision), + }); + } else { + sellPriceHandler( + quote.toString(), + "totalPrice", + ); + } + }; + + const fillSellByBestPrice = () => { + sellPriceHandler( + bestSellPrice.toString(), + "pricePerUnit", + ); + }; + + useEffect(() => { + if (order.reqAmount.isGreaterThan(base)) { + return setAlert({ + ...alert, + reqAmount: t('orders.notEnoughBalance') + }) + } + return setAlert({ + ...alert, + reqAmount: null + }) + }, [order.reqAmount]); + + const submit = () => { + if (!isLogin) { + return false + } + if (isLoading) { + return false + } + setIsLoading(true) + createOrder(activePair.symbol, "SELL", order) + .then((res) => { + setOrder({ + tradeFee: new BN(0), + stopLimit: false, + stopMarket: false, + stopPrice: new BN(0), + reqAmount: new BN(0), + pricePerUnit: new BN(0), + totalPrice: new BN(0), + }) + toast.success(); + dispatch(setLastTransaction(res.data.transactTime)) + }).catch(() => { + toast.error(t("orders.error")); + setAlert({ + ...alert, submit: true + }) + }).finally(() => { + setIsLoading(false) + }) + } + + const submitButtonTextHandler = () => { + if (isLoading) { + return linearLoading + } + /*if (alert.submit) { + return {t("login.loginError")} + }*/ + if (isLogin) { + return t("sell") + } + return t("pleaseLogin") + } + + + return ( +
+
+
{fillSellByWallet()}}> + {t("orders.availableAmount")}: + {new BN(base).toFormat()}{" "}{t("currency." + activePair.baseAsset)} +
+
fillSellByBestPrice()}> + {t("orders.bestOffer")}: + {new BN(bestSellPrice).toFormat()}{" "}{t("currency." + activePair.quoteAsset)} +
+
+ sellPriceHandler(e.target.value, "reqAmount")} + alert={alert.reqAmount} + customClass={`${classes.smallInput} fs-0-8`} + /> + sellPriceHandler(e.target.value, "pricePerUnit")} + customClass={`${classes.smallInput} fs-0-8 my-05`} + /> + sellPriceHandler(e.target.value, "totalPrice")} + customClass={`${classes.smallInput} fs-0-8`} + /> +
+
+

+ {t("orders.tradeFee")}:{" "} + {order.tradeFee.toFormat()}{" "} + {t("currency." + activePair.quoteAsset)} +

+

+ {t("orders.getAmount")}:{" "} + {order.totalPrice.minus(order.tradeFee).decimalPlaces(activePair.baseAssetPrecision).toNumber()}{" "} + {t("currency." + activePair.quoteAsset)} +

+
+
); };