@ -2,9 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="
|
||||
connect-src 'self' wss://appnode.qortal.org;
|
||||
">
|
||||
|
||||
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
@ -14,7 +12,7 @@
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>Qort.Trade</title>
|
||||
<title>Q-Trade</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
150
package-lock.json
generated
@ -19,8 +19,12 @@
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-countdown-circle-timer": "^3.2.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-qr-code": "^2.0.15",
|
||||
"react-router-dom": "^6.23.0",
|
||||
"react-toastify": "^10.0.5",
|
||||
"sass": "^1.76.0",
|
||||
@ -3236,6 +3240,11 @@
|
||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/stylis": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
|
||||
"integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
@ -3826,6 +3835,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001660",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
|
||||
@ -3952,6 +3969,14 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/copy-to-clipboard": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||
"integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
|
||||
"dependencies": {
|
||||
"toggle-selection": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js-compat": {
|
||||
"version": "3.38.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
|
||||
@ -4003,6 +4028,24 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@ -5984,7 +6027,6 @@
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -6218,7 +6260,6 @@
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -6242,6 +6283,11 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@ -6292,6 +6338,11 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qr.js": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
|
||||
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@ -6332,6 +6383,26 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-copy-to-clipboard": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
||||
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
|
||||
"dependencies": {
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.3.0 || 16 || 17 || 18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-countdown-circle-timer": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-countdown-circle-timer/-/react-countdown-circle-timer-3.2.1.tgz",
|
||||
"integrity": "sha512-yBAy/9ILXOiFbLBM+3jS72TW5LeRcH8wkRC9NNqMpUkCXkGjSnaeRbJMsR9lsYF0oVXjSDbJaRbCuVMT+9HnKA==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
@ -6354,6 +6425,34 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
},
|
||||
"node_modules/react-loader-spinner": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz",
|
||||
"integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==",
|
||||
"dependencies": {
|
||||
"react-is": "^18.2.0",
|
||||
"styled-components": "^6.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-qr-code": {
|
||||
"version": "2.0.15",
|
||||
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.15.tgz",
|
||||
"integrity": "sha512-MkZcjEXqVKqXEIMVE0mbcGgDpkfSdd8zhuzXEl9QzYeNcw8Hq2oVIzDLWuZN2PQBwM5PWjc2S31K8Q1UbcFMfw==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.8.1",
|
||||
"qr.js": "0.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||
@ -6788,6 +6887,11 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@ -7041,6 +7145,38 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components": {
|
||||
"version": "6.1.13",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz",
|
||||
"integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==",
|
||||
"dependencies": {
|
||||
"@emotion/is-prop-valid": "1.2.2",
|
||||
"@emotion/unitless": "0.8.1",
|
||||
"@types/stylis": "4.2.5",
|
||||
"css-to-react-native": "3.2.0",
|
||||
"csstype": "3.1.3",
|
||||
"postcss": "8.4.38",
|
||||
"shallowequal": "1.1.0",
|
||||
"stylis": "4.3.2",
|
||||
"tslib": "2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/styled-components"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0",
|
||||
"react-dom": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/stylis": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
|
||||
"integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
@ -7189,6 +7325,11 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toggle-selection": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
|
||||
@ -7210,6 +7351,11 @@
|
||||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
@ -21,8 +21,12 @@
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-countdown-circle-timer": "^3.2.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-qr-code": "^2.0.15",
|
||||
"react-router-dom": "^6.23.0",
|
||||
"react-toastify": "^10.0.5",
|
||||
"sass": "^1.76.0",
|
||||
|
@ -2,13 +2,15 @@ import { Box } from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
|
||||
export const AppContainer = styled(Box)(({ theme }) => ({
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
padding: "1em 0",
|
||||
paddingBottom: '50px'
|
||||
padding: "20px 30px 0 30px",
|
||||
backgroundColor: "#323336",
|
||||
[`@media (max-width: 500px)`]: {
|
||||
padding: "10px 5px 0 5px",
|
||||
}
|
||||
}));
|
||||
|
||||
export const MainContainer = styled(Box)`
|
||||
|
19
src/App.css
@ -125,3 +125,22 @@
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-track:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #444444;
|
||||
border-radius: 8px;
|
||||
background-clip: content-box;
|
||||
border: 4px solid transparent;
|
||||
}
|
65
src/App.tsx
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import ReactGA from "react-ga4";
|
||||
import "./App.css";
|
||||
import socketService from "./services/socketService";
|
||||
@ -71,7 +71,13 @@ export async function sendRequestToExtension(
|
||||
function App() {
|
||||
const [userInfo, setUserInfo] = useState<any>(null);
|
||||
const [qortBalance, setQortBalance] = useState<any>(null);
|
||||
const [ltcBalance, setLtcBalance] = useState<any>(null);
|
||||
const [balances, setBalances] = useState<any>({});
|
||||
const [selectedCoin, setSelectedCoin] = useState("LITECOIN");
|
||||
|
||||
const foreignCoinBalance = useMemo(()=> {
|
||||
if(balances[selectedCoin] === 0) return 0
|
||||
return balances[selectedCoin] || null
|
||||
}, [balances, selectedCoin])
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
const [OAuthLoading, setOAuthLoading] = useState<boolean>(false);
|
||||
const db = useIndexedDBContext();
|
||||
@ -172,14 +178,19 @@ function App() {
|
||||
setQortBalance(balanceResponse.data?.value)
|
||||
}
|
||||
|
||||
const getLTCBalance = async () => {
|
||||
const getLTCBalance = async (coin) => {
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: "GET_WALLET_BALANCE",
|
||||
coin: "LTC"
|
||||
coin: getCoinLabel(coin)
|
||||
});
|
||||
if(!response?.error){
|
||||
setLtcBalance(+response)
|
||||
setBalances((prev)=> {
|
||||
return {
|
||||
...prev,
|
||||
[coin]: +response
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
@ -187,18 +198,18 @@ function App() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(!userInfo?.address) return
|
||||
if(!userInfo?.address || !selectedCoin) return
|
||||
const intervalGetTradeInfo = setInterval(() => {
|
||||
fetchOngoingTransactions()
|
||||
getLTCBalance()
|
||||
getLTCBalance(selectedCoin)
|
||||
getQortBalance()
|
||||
}, 150000)
|
||||
getLTCBalance()
|
||||
getLTCBalance(selectedCoin)
|
||||
getQortBalance()
|
||||
return () => {
|
||||
clearInterval(intervalGetTradeInfo)
|
||||
}
|
||||
}, [userInfo?.address, isAuthenticated])
|
||||
}, [userInfo?.address, isAuthenticated, selectedCoin])
|
||||
|
||||
|
||||
const handleMessage = async (event: any) => {
|
||||
@ -208,7 +219,6 @@ function App() {
|
||||
setAvatar("");
|
||||
setIsAuthenticated(false);
|
||||
setQortBalance(null)
|
||||
setLtcBalance(null)
|
||||
localStorage.setItem("token", "");
|
||||
} else if(event.data.type === "RESPONSE_FOR_TRADES"){
|
||||
|
||||
@ -246,6 +256,37 @@ function App() {
|
||||
};
|
||||
}, [userInfo?.address]);
|
||||
|
||||
const getCoinLabel = useCallback((coin?: string)=> {
|
||||
switch(coin || selectedCoin){
|
||||
case "LITECOIN":{
|
||||
|
||||
return 'LTC'
|
||||
}
|
||||
case "DOGECOIN":{
|
||||
|
||||
return 'DOGE'
|
||||
}
|
||||
case "BITCOIN":{
|
||||
|
||||
return 'BTC'
|
||||
}
|
||||
case "DIGIBYTE":{
|
||||
|
||||
return 'DGB'
|
||||
}
|
||||
case "RAVENCOIN":{
|
||||
|
||||
return 'RVN'
|
||||
}
|
||||
case "PIRATECHAIN":{
|
||||
|
||||
return 'ARRR'
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}, [selectedCoin])
|
||||
|
||||
const gameContextValue: IContextProps = {
|
||||
userInfo,
|
||||
setUserInfo,
|
||||
@ -253,7 +294,7 @@ function App() {
|
||||
setUserNameAvatar,
|
||||
onGoingTrades,
|
||||
fetchOngoingTransactions,
|
||||
ltcBalance,
|
||||
foreignCoinBalance,
|
||||
qortBalance,
|
||||
isAuthenticated,
|
||||
setIsAuthenticated,
|
||||
@ -261,7 +302,7 @@ function App() {
|
||||
setOAuthLoading,
|
||||
updateTransactionInDB,
|
||||
sellOrders,
|
||||
deleteTemporarySellOrder, updateTemporaryFailedTradeBots, fetchTemporarySellOrders, isUsingGateway
|
||||
deleteTemporarySellOrder, updateTemporaryFailedTradeBots, fetchTemporarySellOrders, isUsingGateway, selectedCoin, setSelectedCoin, getCoinLabel
|
||||
};
|
||||
|
||||
|
||||
|
3
src/assets/SVG/Copy.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.92857 0.5H8.57143C9.36071 0.5 10 1.13929 10 1.92857V6.57143C10 7.36071 9.36071 8 8.57143 8H8.21429V4.42857C8.21429 3.24643 7.25357 2.28571 6.07143 2.28571H2.5V1.92857C2.5 1.13929 3.13929 0.5 3.92857 0.5ZM1.42857 3H6.07143C6.86041 3 7.5 3.63959 7.5 4.42857V9.07143C7.5 9.86041 6.86041 10.5 6.07143 10.5H1.42857C0.639593 10.5 0 9.86041 0 9.07143V4.42857C0 3.63959 0.639593 3 1.42857 3Z" fill="white" fill-opacity="0.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 574 B |
BIN
src/assets/img/arrr.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/img/btc.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/img/dgb.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/img/doge.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/img/ltc.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/img/qort.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/img/rvn.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
@ -5,7 +5,7 @@ import { useIndexedDBContext } from "../../contexts/indexedDBContext";
|
||||
|
||||
const fetchTradeInfo = async (qortalAtAddress) => {
|
||||
|
||||
const checkIfOfferingRes = await fetch(`http://127.0.0.1:12391/crosschain/trade/${qortalAtAddress}`)
|
||||
const checkIfOfferingRes = await fetch(`/crosschain/trade/${qortalAtAddress}`)
|
||||
const data = await checkIfOfferingRes.json()
|
||||
return data
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import { ColDef, SizeColumnsToContentStrategy } from 'ag-grid-community';
|
||||
import { ColDef, RowClassParams, RowStyle, SizeColumnsToContentStrategy } from 'ag-grid-community';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
import gameContext from '../../contexts/gameContext';
|
||||
@ -10,8 +10,18 @@ const autoSizeStrategy: SizeColumnsToContentStrategy = {
|
||||
};
|
||||
|
||||
export const OngoingTrades = () => {
|
||||
const { onGoingTrades } = useContext(gameContext);
|
||||
const { onGoingTrades, getCoinLabel, selectedCoin } = useContext(gameContext);
|
||||
const gridRef = useRef<any>(null)
|
||||
|
||||
const filteredOngoingTrades = useMemo(()=> {
|
||||
return onGoingTrades?.filter((item)=> item?.tradeInfo?.foreignBlockchain === selectedCoin)
|
||||
}, [onGoingTrades, selectedCoin])
|
||||
|
||||
const onGridReady = useCallback((params: any) => {
|
||||
// params.api.sizeColumnsToFit(); // Adjust columns to fit the grid width
|
||||
// const allColumnIds = params.columnApi.getAllColumns().map((col: any) => col.getColId());
|
||||
// params.columnApi.autoSizeColumns(allColumnIds); // Automatically adjust the width to fit content
|
||||
}, []);
|
||||
|
||||
const defaultColDef = {
|
||||
resizable: true, // Make columns resizable by default
|
||||
@ -35,9 +45,10 @@ export const OngoingTrades = () => {
|
||||
resizable: true ,
|
||||
flex: 1, minWidth: 100
|
||||
},
|
||||
{ headerName: "Amount (QORT)", valueGetter: (params) => +params.data.tradeInfo.qortAmount, resizable: true, flex: 1, minWidth: 100 },
|
||||
{ headerName: "LTC/QORT", valueGetter: (params) => +params.data.tradeInfo.expectedForeignAmount / +params.data.tradeInfo.qortAmount , resizable: true , flex: 1, minWidth: 100},
|
||||
{ headerName: "Total LTC Value", valueGetter: (params) => +params.data.tradeInfo.expectedForeignAmount, resizable: true , flex: 1, minWidth: 100 },
|
||||
{ headerName: "Amount (QORT)", valueGetter: (params) => +params.data.tradeInfo.qortAmount, resizable: true, flex: 1, minWidth: 150 },
|
||||
{ headerName: `${getCoinLabel()}/QORT`, valueGetter: (params) => +params.data.tradeInfo.expectedForeignAmount / +params.data.tradeInfo.qortAmount , resizable: true , flex: 1, minWidth: 150},
|
||||
{ headerName: `Total ${getCoinLabel()} Value`, valueGetter: (params) => +params.data.tradeInfo.expectedForeignAmount, resizable: true , flex: 1, minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: "Notes", valueGetter: (params) => {
|
||||
if (params.data.tradeInfo.mode === 'TRADING') {
|
||||
@ -54,7 +65,7 @@ export const OngoingTrades = () => {
|
||||
}
|
||||
|
||||
if (params.data.message) return params.data.message
|
||||
}, resizable: true, flex: 1, minWidth: 100
|
||||
}, resizable: true, flex: 1, minWidth:300, autoHeight: true, cellStyle: { whiteSpace: 'normal', wordBreak: 'break-word', padding: '5px' },
|
||||
}
|
||||
];
|
||||
|
||||
@ -65,15 +76,20 @@ export const OngoingTrades = () => {
|
||||
// return null;
|
||||
// };
|
||||
const getRowId = useCallback(function (params: any) {
|
||||
return String(params.data._id);
|
||||
return String(params.data?.qortalAtAddress);
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="ag-theme-alpine-dark" style={{ height: 225, width: '100%' }}>
|
||||
<div className="ag-theme-alpine-dark" style={{ height: 225, width: '100%', display: filteredOngoingTrades?.length === 0 && 'none' }}>
|
||||
<AgGridReact
|
||||
onGridReady={onGridReady}
|
||||
ref={gridRef}
|
||||
|
||||
columnDefs={columnDefs}
|
||||
defaultColDef={defaultColDef}
|
||||
rowData={onGoingTrades}
|
||||
rowData={filteredOngoingTrades}
|
||||
// onRowClicked={onRowClicked}
|
||||
rowSelection="single"
|
||||
getRowId={getRowId}
|
||||
|
@ -1,11 +1,62 @@
|
||||
import { styled } from "@mui/system";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { Box, styled } from "@mui/system";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
|
||||
export const MainContainer = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
});
|
||||
|
||||
export const TextTableTitle = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Inter",
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 400,
|
||||
fontSize: "20px",
|
||||
lineHeight: "40px",
|
||||
userSelect: "none",
|
||||
}));
|
||||
fontFamily: "Inter",
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 400,
|
||||
fontSize: "20px",
|
||||
lineHeight: "40px",
|
||||
userSelect: "none",
|
||||
}));
|
||||
|
||||
export const BuyContainer = styled(Box)(({ theme }) => ({
|
||||
position: "fixed",
|
||||
width: "calc(100% - 14px)",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
bottom: "0px",
|
||||
height: "100px",
|
||||
padding: "18px 14px 12px 14px",
|
||||
background: "#323336",
|
||||
zIndex: 3,
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
width: "calc(100% - 2px)",
|
||||
}
|
||||
}));
|
||||
|
||||
export const BuyContainerDivider = styled(Box)(({ theme }) => ({
|
||||
position: "absolute",
|
||||
width: "60%",
|
||||
height: "1px",
|
||||
background: "lightgray",
|
||||
top: "10px",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
top: "5px",
|
||||
}
|
||||
}));
|
||||
|
||||
export const BuyOrderBtn = styled("button")(({ theme }) => ({
|
||||
borderRadius: "8px",
|
||||
width: "74px",
|
||||
height: "30px",
|
||||
background: "#4D7345",
|
||||
color: "white",
|
||||
cursor: "pointer",
|
||||
border: "1px solid #375232",
|
||||
boxShadow: "0px 2.77px 2.21px 0px #00000005",
|
||||
marginRight: "10px",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
marginRight: "0px",
|
||||
}
|
||||
}));
|
||||
|
@ -55,22 +55,22 @@ export const Terms =() => {
|
||||
</IconButton>
|
||||
<DialogContent dividers>
|
||||
<Typography gutterBottom>
|
||||
The purpose of qort.trade is to make trading LTC for QORT as easy as possible. The maintainers of this site do not profit from its use—there are no additional fees for buying QORT through this site. There are two ways to place a buy order:
|
||||
The purpose of q-trade is to make trading LTC and other coins for QORT as easy as possible. The maintainers of this site do not profit from its use—there are no additional fees for buying QORT through this site. There are two ways to place a buy order:
|
||||
1. Use the gateway
|
||||
2. Use your local node.
|
||||
By using qort.trade, you agree to the following terms and conditions.
|
||||
By using q-trade, you agree to the following terms and conditions.
|
||||
</Typography>
|
||||
|
||||
<Typography gutterBottom>
|
||||
Using the gateway means you trust the maintainer of the node, as your LTC private key will need to be handled by that node to execute a trade order. If you have more than 4 QORT and your public key is already on the blockchain, your LTC private key will be transmitted using q-chat. If not, the message will be encrypted in the same manner as q-chat but stored temporarily in a database to ensure it reaches its destination.
|
||||
Using the gateway means you trust the maintainer of the node, as your foreign coin (i.e. LTC) private key will need to be handled by that node to execute a trade order. If you have more than 4 QORT and your public key is already on the blockchain, your foreign coin private key will be transmitted using q-chat. If not, the message will be encrypted in the same manner as q-chat but stored temporarily in a database to ensure it reaches its destination.
|
||||
</Typography>
|
||||
|
||||
<Typography gutterBottom>
|
||||
If you are uncomfortable using the gateway, we offer the option to use your local node to buy QORT. When logging into the extension, choose the local node configuration, and use the switch button on qort.trade to connect with your local node.
|
||||
If you are uncomfortable using the gateway, we offer the option to use your local node to buy QORT. When logging into the UI, choose the local node configuration.
|
||||
</Typography>
|
||||
|
||||
<Typography gutterBottom>
|
||||
The maintainers of this site are not responsible for any lost LTC, QORT, or other cryptocurrencies that may result from using this site. This is a hobby project, and mistakes in the code may occur. Please proceed with caution.
|
||||
The maintainers and devs of this site are not responsible for any lost foreign coin, QORT, or other cryptocurrencies that may result from using this site. This is a hobby project, and mistakes in the code may occur.
|
||||
</Typography>
|
||||
|
||||
</DialogContent>
|
||||
|
BIN
src/components/common/icons/qtradeLogo.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
@ -1,5 +1,6 @@
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
export const ReusableModalContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
@ -21,15 +22,6 @@ export const ReusableModalContainer = styled(Box)(({ theme }) => ({
|
||||
"0px 4px 5px 0px hsla(0,0%,0%,0.14), \n\t\t0px 1px 10px 0px hsla(0,0%,0%,0.12), \n\t\t0px 2px 4px -1px hsla(0,0%,0%,0.2)",
|
||||
}));
|
||||
|
||||
export const ReusableModalSubContainer = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: "20px",
|
||||
padding: "70px",
|
||||
});
|
||||
|
||||
export const ReusableModalBackdrop = styled(Box)({
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
@ -51,3 +43,16 @@ export const ReusableModalButton = styled(Button)(({ theme }) => ({
|
||||
color: theme.palette.text.primary,
|
||||
boxShadow: "1px 4px 10.5px 0px #0000004D"
|
||||
}));
|
||||
|
||||
export const ReusableModalCloseIcon = styled(CloseIcon)(({ theme }) => ({
|
||||
color: theme.palette.text.primary,
|
||||
cursor: "pointer",
|
||||
fontSize: "30px",
|
||||
position: "absolute",
|
||||
top: "20px",
|
||||
right: "20px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
transform: "scale(1.1)",
|
||||
},
|
||||
}));
|
@ -1,20 +1,26 @@
|
||||
import {
|
||||
ReusableModalBackdrop,
|
||||
ReusableModalCloseIcon,
|
||||
ReusableModalContainer,
|
||||
ReusableModalSubContainer,
|
||||
} from "./ReusableModal-styles";
|
||||
interface ReusableModalProps {
|
||||
backdrop?: boolean;
|
||||
onClickClose: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ReusableModal: React.FC<ReusableModalProps> = ({ backdrop, children }) => {
|
||||
export const ReusableModal: React.FC<ReusableModalProps> = ({
|
||||
backdrop,
|
||||
children,
|
||||
onClickClose,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<ReusableModalContainer>
|
||||
<ReusableModalSubContainer>{children}</ReusableModalSubContainer>
|
||||
<ReusableModalCloseIcon onClick={onClickClose} />
|
||||
{children}
|
||||
</ReusableModalContainer>
|
||||
{backdrop && <ReusableModalBackdrop />}
|
||||
{backdrop && <ReusableModalBackdrop onClick={onClickClose} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
50
src/components/header/AddressQRCode.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import QRCode from "react-qr-code";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
export const AddressQRCode = ({ targetAddress }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
marginTop: '10px'
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<QRCode
|
||||
value={targetAddress} // Your address here
|
||||
size={150} // Adjust size as needed
|
||||
level="M" // Error correction level (L, M, Q, H)
|
||||
bgColor="#FFFFFF" // Background color (white)
|
||||
fgColor="#000000" // Foreground color (black)
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { styled } from "@mui/system";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { Box, Button, Typography, Theme, TextField } from "@mui/material";
|
||||
import { HomeSVG } from "../common/icons/HomeSVG";
|
||||
import { QortalLogoSVG } from "../common/icons/QortalLogoSVG";
|
||||
import { CaretDownSVG } from "../common/icons/CaretDownSVG";
|
||||
@ -16,6 +16,14 @@ export const HeaderNav = styled(Box)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export const BubbleCardColored1 = styled(Box)({
|
||||
height: "77px",
|
||||
width: "77px",
|
||||
background: "linear-gradient(124.49deg, #70BAFF 7.03%, #F29999 94.22%)",
|
||||
boxShadow: "0px 0px 25.8px -1px #1C5A93",
|
||||
borderRadius: "50%",
|
||||
});
|
||||
|
||||
export const HomeIcon = styled(HomeSVG)({
|
||||
cursor: "pointer",
|
||||
});
|
||||
@ -110,7 +118,7 @@ export const GameSelectDropdownMenuItem = styled(Box)(({ theme }) => ({
|
||||
|
||||
export const Username = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Fira Sans, sans-serif",
|
||||
fontSize: "16px",
|
||||
fontSize: "20px",
|
||||
lineHeight: "19.2px",
|
||||
fontWeight: 400,
|
||||
color: theme.palette.text.primary,
|
||||
@ -130,13 +138,14 @@ export const LogoColumn = styled(Box)({
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
});
|
||||
|
||||
export const RightColumn = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: "10px",
|
||||
alignItems: "flex-start",
|
||||
padding: '10px'
|
||||
});
|
||||
|
||||
export const AvatarCircle = styled("img")({
|
||||
borderRadius: "50%",
|
||||
width: "35px",
|
||||
@ -145,12 +154,188 @@ export const AvatarCircle = styled("img")({
|
||||
userSelect: "none",
|
||||
});
|
||||
|
||||
|
||||
export const HeaderText = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Inter",
|
||||
color: theme.palette.text.primary,
|
||||
textAlign: "center",
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
lineHeight: 1.2,
|
||||
userSelect: "none",
|
||||
}));
|
||||
|
||||
export const TotalCol = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
});
|
||||
|
||||
export const CoinActionsRow = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: "5px",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
});
|
||||
|
||||
export const CoinSendBtn = styled(Button)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "#000000",
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontWeight: 500,
|
||||
fontSize: "14px",
|
||||
lineHeight: "16px",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "0px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
border: `1px solid ${theme.palette.text.primary}`,
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
},
|
||||
}));
|
||||
|
||||
export const CoinReceiveBtn = styled(Button)(({ theme }) => ({
|
||||
backgroundColor: "transparent",
|
||||
color: theme.palette.text.primary,
|
||||
border: `1px solid ${theme.palette.text.primary}`,
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontWeight: 500,
|
||||
fontSize: "14px",
|
||||
lineHeight: "16px",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "0px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
color: "#000000",
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
},
|
||||
}));
|
||||
|
||||
export const CoinSelectRow = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
alignSelf: "flex-start",
|
||||
marginBottom: "5px"
|
||||
});
|
||||
|
||||
export const CoinActionContainer = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "25px",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "80%",
|
||||
});
|
||||
|
||||
export const CoinActionRow = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
});
|
||||
|
||||
export const HeaderRow = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
});
|
||||
|
||||
export const SendFont = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Inter",
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 400,
|
||||
fontSize: "18px",
|
||||
lineHeight: "25px",
|
||||
userSelect: "none",
|
||||
}));
|
||||
|
||||
const customInputStyle = (theme: Theme) => {
|
||||
return {
|
||||
fontFamily: "Inter",
|
||||
fontSize: "18px",
|
||||
fontWeight: 300,
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderColor: theme.palette.background.paper,
|
||||
"& label": {
|
||||
color: theme.palette.mode === "light" ? "#808183" : "#edeef0",
|
||||
fontFamily: "Inter",
|
||||
fontSize: "18px",
|
||||
letterSpacing: "0px",
|
||||
},
|
||||
"& label.Mui-focused": {
|
||||
color: theme.palette.mode === "light" ? "#A0AAB4" : "#d7d8da",
|
||||
},
|
||||
"& .MuiInput-underline:after": {
|
||||
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
||||
},
|
||||
"& .MuiOutlinedInput-root": {
|
||||
"& fieldset": {
|
||||
borderColor: "#E0E3E7",
|
||||
},
|
||||
"&:hover fieldset": {
|
||||
borderColor: "#B2BAC2",
|
||||
},
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#6F7E8C",
|
||||
},
|
||||
},
|
||||
"& .MuiInputBase-root": {
|
||||
fontFamily: "Inter",
|
||||
fontSize: "18px",
|
||||
letterSpacing: "0px",
|
||||
},
|
||||
"& .MuiFilledInput-root:after": {
|
||||
borderBottomColor: theme.palette.secondary.main,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const CustomInputField = styled(TextField)(({ theme }) =>
|
||||
customInputStyle(theme as Theme)
|
||||
);
|
||||
|
||||
export const CoinCancelBtn = styled(Button)({
|
||||
backgroundColor: "transparent",
|
||||
color: "#d62525",
|
||||
border: "1px solid #d62525",
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontWeight: 500,
|
||||
fontSize: "14px",
|
||||
height: "32px",
|
||||
width: "80px",
|
||||
lineHeight: "16px",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "0px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
border: "1px solid #d62525",
|
||||
backgroundColor: "#d62525",
|
||||
color: "#000000",
|
||||
},
|
||||
});
|
||||
|
||||
export const CoinConfirmSendBtn = styled(Button)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "#000000",
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontWeight: 500,
|
||||
fontSize: "14px",
|
||||
height: "32px",
|
||||
width: "80px",
|
||||
lineHeight: "16px",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "0px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
border: `1px solid ${theme.palette.text.primary}`,
|
||||
color: "#000000",
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
},
|
||||
}));
|
||||
|
@ -1,41 +1,63 @@
|
||||
import { useState, useEffect, useRef, useContext, ChangeEvent } from "react";
|
||||
import ReactGA from "react-ga4";
|
||||
import { useState, useEffect, useRef, useContext, ChangeEvent, useMemo } from "react";
|
||||
import {
|
||||
AvatarCircle,
|
||||
CaretDownIcon,
|
||||
DropdownContainer,
|
||||
GameSelectDropdown,
|
||||
GameSelectDropdownMenu,
|
||||
GameSelectDropdownMenuItem,
|
||||
BubbleCardColored1,
|
||||
CoinActionContainer,
|
||||
CoinActionRow,
|
||||
CoinActionsRow,
|
||||
CoinCancelBtn,
|
||||
CoinConfirmSendBtn,
|
||||
CoinReceiveBtn,
|
||||
CoinSelectRow,
|
||||
CoinSendBtn,
|
||||
CustomInputField,
|
||||
HeaderNav,
|
||||
HeaderRow,
|
||||
HeaderText,
|
||||
HomeIcon,
|
||||
LogoColumn,
|
||||
NameRow,
|
||||
QortalLogoIcon,
|
||||
RightColumn,
|
||||
SendFont,
|
||||
TotalCol,
|
||||
Username,
|
||||
} from "./Header-styles";
|
||||
import gameContext from "../../contexts/gameContext";
|
||||
import { UserContext } from "../../contexts/userContext";
|
||||
import { cropAddress } from "../../utils/cropAddress";
|
||||
import { BubbleCardColored1 } from "../../pages/Home/Home-Styles";
|
||||
import logoSVG from "../../assets/SVG/LOGO.svg";
|
||||
import qtradeLogo from "../../components/common/icons/qtradeLogo.png";
|
||||
import qortIcon from "../../assets/img/qort.png";
|
||||
import ErrorIcon from "@mui/icons-material/Error";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import Copy from "../../assets/SVG/Copy.svg";
|
||||
import {AddressQRCode} from './AddressQRCode'
|
||||
import {FallingLines} from 'react-loader-spinner'
|
||||
import {
|
||||
Alert,
|
||||
AppBar,
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
Snackbar,
|
||||
SnackbarCloseReason,
|
||||
Switch,
|
||||
Typography,
|
||||
styled,
|
||||
} from "@mui/material";
|
||||
import { sendRequestToExtension } from "../../App";
|
||||
import { Terms } from "../Terms";
|
||||
import ltcIcon from "../../assets/img/ltc.png";
|
||||
import btcIcon from "../../assets/img/btc.png";
|
||||
import dogeIcon from "../../assets/img/doge.png";
|
||||
import rvnIcon from "../../assets/img/rvn.png";
|
||||
import dgbIcon from "../../assets/img/dgb.png";
|
||||
import arrrIcon from "../../assets/img/arrr.png";
|
||||
import { Spacer } from "../common/Spacer";
|
||||
import { ReusableModal } from "../common/reusable-modal/ReusableModal";
|
||||
import { NotificationContext } from "../../contexts/notificationContext";
|
||||
|
||||
const checkIfLocal = async () => {
|
||||
try {
|
||||
@ -59,15 +81,79 @@ export const Label = styled("label")(
|
||||
`
|
||||
);
|
||||
|
||||
export const Header = ({ qortBalance, ltcBalance, mode, setMode }: any) => {
|
||||
interface CoinModalProps {
|
||||
coin: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const getCoinIcon = (coin) => {
|
||||
let img;
|
||||
|
||||
switch (coin) {
|
||||
case "LTC":
|
||||
img = ltcIcon;
|
||||
break;
|
||||
case "BTC":
|
||||
img = btcIcon;
|
||||
break;
|
||||
|
||||
case "DOGE":
|
||||
img = dogeIcon;
|
||||
break;
|
||||
case "RVN":
|
||||
img = rvnIcon;
|
||||
break;
|
||||
|
||||
case "ARRR":
|
||||
img = arrrIcon;
|
||||
break;
|
||||
case "DGB":
|
||||
img = dgbIcon;
|
||||
break;
|
||||
default:
|
||||
null;
|
||||
}
|
||||
return img;
|
||||
};
|
||||
|
||||
const SelectRow = ({ coin }) => {
|
||||
let img = getCoinIcon(coin);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
height: "25px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
height: "20px",
|
||||
width: "auto",
|
||||
}}
|
||||
src={img}
|
||||
/>
|
||||
<p>{coin}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
|
||||
const [openDropdown, setOpenDropdown] = useState<boolean>(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLDivElement>(null);
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [info, setInfo] = useState<any>(null);
|
||||
const [openCoinActionModal, setOpenCoinActionModal] =
|
||||
useState<CoinModalProps | null>(null);
|
||||
const [receiverAddress, setReceiverAddress] = useState<string>("");
|
||||
const [senderAddress, setSenderAddress] = useState<string>("");
|
||||
const [amount, setAmount] = useState<string>("");
|
||||
const [coinAddresses, setCoinAddresses] = useState({});
|
||||
const { isUsingGateway } = useContext(gameContext);
|
||||
const [selectedCoin, setSelectedCoin] = useState("LITECOIN");
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setChecked(false);
|
||||
@ -77,8 +163,10 @@ export const Header = ({ qortBalance, ltcBalance, mode, setMode }: any) => {
|
||||
message: "Change the node you are using at the authentication page",
|
||||
});
|
||||
};
|
||||
const { userInfo } = useContext(gameContext);
|
||||
const { avatar, setAvatar } = useContext(UserContext);
|
||||
const { userInfo, selectedCoin, setSelectedCoin, getCoinLabel } =
|
||||
useContext(gameContext);
|
||||
const { setNotification } = useContext(NotificationContext);
|
||||
|
||||
|
||||
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
|
||||
padding: 8,
|
||||
@ -151,119 +239,510 @@ export const Header = ({ qortBalance, ltcBalance, mode, setMode }: any) => {
|
||||
// }
|
||||
// }, [userInfo]);
|
||||
|
||||
const sendCoin = async ()=> {
|
||||
try {
|
||||
const coin = openCoinActionModal.coin === "QORT" ? 'QORT' : getCoinLabel()
|
||||
if(!coin) return
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "info",
|
||||
message: "Sending Coin...",
|
||||
autoHideDurationOff: true
|
||||
});
|
||||
const response = await qortalRequest({
|
||||
action: "SEND_COIN",
|
||||
coin,
|
||||
destinationAddress: senderAddress,
|
||||
amount: +amount
|
||||
});
|
||||
if(response?.error){
|
||||
throw new Error(response?.error || "Failed to send coin.")
|
||||
}
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "success",
|
||||
message: "Coin sent",
|
||||
});
|
||||
setAmount('')
|
||||
} catch (error) {
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "error",
|
||||
message: error?.error || error?.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderNav
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<>
|
||||
<AppBar
|
||||
position="sticky"
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
background: "rgba(39, 40, 44, 1)",
|
||||
}}
|
||||
>
|
||||
<LogoColumn>
|
||||
<img
|
||||
src={logoSVG}
|
||||
style={{
|
||||
height: "24px",
|
||||
}}
|
||||
/>
|
||||
</LogoColumn>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
<LogoColumn>
|
||||
<img
|
||||
src={qtradeLogo}
|
||||
style={{
|
||||
height: "40px",
|
||||
}}
|
||||
/>
|
||||
</LogoColumn>
|
||||
<FormControlLabel
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
control={
|
||||
<LocalNodeSwitch
|
||||
checked={isUsingGateway}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
}
|
||||
label="Is using Gateway"
|
||||
/>
|
||||
</Box>
|
||||
</AppBar>
|
||||
<HeaderNav
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<Spacer height="10px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<NameRow>
|
||||
{userInfo?.name ? (
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
src={`/arbitrary/THUMBNAIL/${userInfo?.name}/qortal_avatar?async=true`}
|
||||
alt={`${userInfo?.name}`}
|
||||
>
|
||||
{userInfo?.name?.charAt(0)?.toUpperCase()}
|
||||
</Avatar>
|
||||
) : userInfo?.address ? (
|
||||
<BubbleCardColored1 style={{ height: "35px", width: "35px" }} />
|
||||
) : null}
|
||||
{userInfo?.name ? (
|
||||
<Username>{userInfo?.name}</Username>
|
||||
) : userInfo?.address ? (
|
||||
<Username>{cropAddress(userInfo?.address)}</Username>
|
||||
) : null}
|
||||
</NameRow>
|
||||
<Terms />
|
||||
</Box>
|
||||
|
||||
<RightColumn
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
backgroundColor: "#292929",
|
||||
"&.MuiCard-root": {
|
||||
cursor: "default",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<HeaderText>Total Balance</HeaderText>
|
||||
<Spacer height="10px" />
|
||||
<TotalCol>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={qortIcon}
|
||||
style={{
|
||||
height: "25px",
|
||||
width: "auto",
|
||||
}}
|
||||
/>
|
||||
<HeaderText>{qortBalance} QORT</HeaderText>
|
||||
</Box>
|
||||
<CoinActionsRow>
|
||||
<CoinSendBtn
|
||||
onClick={() => {
|
||||
setOpenCoinActionModal({
|
||||
coin: "QORT",
|
||||
type: "send",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</CoinSendBtn>
|
||||
<CoinReceiveBtn
|
||||
onClick={() => {
|
||||
setOpenCoinActionModal({
|
||||
coin: "QORT",
|
||||
type: "receive",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Receive
|
||||
</CoinReceiveBtn>
|
||||
</CoinActionsRow>
|
||||
</TotalCol>
|
||||
<Spacer height="10px" />
|
||||
<TotalCol>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={getCoinIcon(getCoinLabel())}
|
||||
style={{
|
||||
height: "25px",
|
||||
width: "auto",
|
||||
}}
|
||||
/>
|
||||
{foreignCoinBalance === null ? (
|
||||
<FallingLines
|
||||
color="white"
|
||||
width="30"
|
||||
visible={true}
|
||||
/>
|
||||
) : foreignCoinBalance}{" "}
|
||||
{getCoinLabel()}
|
||||
</Box>
|
||||
<CoinActionsRow>
|
||||
<CoinSendBtn
|
||||
onClick={() => {
|
||||
setOpenCoinActionModal({
|
||||
coin: selectedCoin,
|
||||
type: "send",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</CoinSendBtn>
|
||||
<CoinReceiveBtn
|
||||
onClick={() => {
|
||||
setOpenCoinActionModal({
|
||||
coin: selectedCoin,
|
||||
type: "receive",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Receive
|
||||
</CoinReceiveBtn>
|
||||
</CoinActionsRow>
|
||||
</TotalCol>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</RightColumn>
|
||||
|
||||
<CoinSelectRow>
|
||||
<Select
|
||||
size="small"
|
||||
size="small"
|
||||
value={selectedCoin}
|
||||
onChange={(e) => setSelectedCoin(e.target.value)}
|
||||
>
|
||||
<MenuItem value={"LITECOIN"}>LTC</MenuItem>
|
||||
<MenuItem value={"LITECOIN"}>
|
||||
<SelectRow coin="LTC" />
|
||||
</MenuItem>
|
||||
<MenuItem value={"DOGECOIN"}>
|
||||
<SelectRow coin="DOGE" />
|
||||
</MenuItem>
|
||||
<MenuItem value={"BITCOIN"}>
|
||||
<SelectRow coin="BTC" />
|
||||
</MenuItem>
|
||||
<MenuItem value={"DIGIBYTE"}>
|
||||
<SelectRow coin="DGB" />
|
||||
</MenuItem>
|
||||
<MenuItem value={"RAVENCOIN"}>
|
||||
<SelectRow coin="RVN" />
|
||||
</MenuItem>
|
||||
<MenuItem value={"PIRATECHAIN"}>
|
||||
<SelectRow coin="ARRR" />
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<RightColumn>
|
||||
<HeaderText>
|
||||
Balance: {qortBalance} QORT |{" "}
|
||||
{ltcBalance === null ? "N/A" : ltcBalance} LTC
|
||||
</HeaderText>
|
||||
<NameRow>
|
||||
{userInfo?.name ? (
|
||||
<Username>{userInfo?.name}</Username>
|
||||
) : userInfo?.address ? (
|
||||
<Username>{cropAddress(userInfo?.address)}</Username>
|
||||
) : null}
|
||||
|
||||
{userInfo?.name ? (
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "24px",
|
||||
width: "24px",
|
||||
fontSize: "15px",
|
||||
}}
|
||||
src={`/arbitrary/THUMBNAIL/${userInfo?.name}/qortal_avatar?encoding=base64&rebuild=false`}
|
||||
alt={`${userInfo?.name}`}
|
||||
>
|
||||
{userInfo?.name?.charAt(0)?.toUpperCase()}
|
||||
</Avatar>
|
||||
) : userInfo?.address ? (
|
||||
<BubbleCardColored1 style={{ height: "35px", width: "35px" }} />
|
||||
) : (
|
||||
<QortalLogoIcon
|
||||
height="35"
|
||||
width="35"
|
||||
color="none"
|
||||
onClickFunc={() => {
|
||||
window.open("https://www.qortal.dev", "_blank")?.focus();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NameRow>
|
||||
</RightColumn>
|
||||
<FormControlLabel
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
control={
|
||||
<LocalNodeSwitch checked={isUsingGateway} onChange={handleChange} />
|
||||
}
|
||||
label="Is using Gateway"
|
||||
/>
|
||||
<Terms />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Button onClick={() => setMode("buy")}>Buy QORT</Button>
|
||||
<Button onClick={() => setMode("sell")}>SELL QORT</Button>
|
||||
</Box>
|
||||
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert
|
||||
</CoinSelectRow>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
open={open}
|
||||
autoHideDuration={info?.autoHideDurationOff ? null : 6000}
|
||||
onClose={handleClose}
|
||||
severity={info?.type}
|
||||
variant="filled"
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
{info?.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</HeaderNav>
|
||||
{info?.type && (
|
||||
<Alert
|
||||
onClose={handleClose}
|
||||
severity={info?.type}
|
||||
variant="filled"
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
{info?.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
</Snackbar>
|
||||
{openCoinActionModal && (
|
||||
<ReusableModal
|
||||
onClickClose={() => {
|
||||
setOpenCoinActionModal(null);
|
||||
setAmount('')
|
||||
setSenderAddress('')
|
||||
}}
|
||||
backdrop
|
||||
>
|
||||
<CoinActionContainer>
|
||||
{openCoinActionModal.type === "send" ? <>
|
||||
<CoinActionRow>
|
||||
<HeaderRow>
|
||||
{openCoinActionModal.type === "send" &&
|
||||
openCoinActionModal.coin === "QORT" ? (
|
||||
<>
|
||||
<SendFont>Send {openCoinActionModal.coin}</SendFont>
|
||||
<img
|
||||
src={qortIcon}
|
||||
style={{
|
||||
height: "25px",
|
||||
width: "auto",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : openCoinActionModal.type === "send" &&
|
||||
openCoinActionModal.coin !== "QORT" ? (
|
||||
<>
|
||||
<SendFont>Send {openCoinActionModal.coin}</SendFont>
|
||||
<img
|
||||
src={getCoinIcon(getCoinLabel())}
|
||||
style={{
|
||||
height: "25px",
|
||||
width: "auto",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</HeaderRow>
|
||||
</CoinActionRow>
|
||||
<CoinActionRow>
|
||||
<FormControl fullWidth>
|
||||
<CustomInputField
|
||||
style={{ flexGrow: 1 }}
|
||||
name={
|
||||
openCoinActionModal.type === "send"
|
||||
? `${openCoinActionModal.coin === "QORT" ? 'Recipient Address or Name' : 'Recipient Address'}`
|
||||
: "Receive Address"
|
||||
}
|
||||
label={
|
||||
openCoinActionModal.type === "send"
|
||||
? `${openCoinActionModal.coin === "QORT" ? 'Recipient Address or Name' : 'Recipient Address'}`
|
||||
: "Receive Address"
|
||||
}
|
||||
variant="filled"
|
||||
value={
|
||||
openCoinActionModal.type === "send"
|
||||
? senderAddress
|
||||
: receiverAddress
|
||||
}
|
||||
required
|
||||
onChange={(e) => {
|
||||
if (openCoinActionModal.type === "send") {
|
||||
setSenderAddress(e.target.value);
|
||||
} else {
|
||||
setReceiverAddress(e.target.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</CoinActionRow>
|
||||
{openCoinActionModal.type === "send" && (
|
||||
<CoinActionRow>
|
||||
<FormControl fullWidth>
|
||||
<CustomInputField
|
||||
style={{ flexGrow: 1 }}
|
||||
name="Amount"
|
||||
label="Amount"
|
||||
variant="filled"
|
||||
type="number"
|
||||
value={
|
||||
amount
|
||||
}
|
||||
required
|
||||
onChange={(e) => {
|
||||
setAmount(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</CoinActionRow>
|
||||
)}
|
||||
</> : (
|
||||
<>
|
||||
<ReceiveCoin setOpen={setOpen} setInfo={setInfo} coinAddresses={coinAddresses} setCoinAddresses={setCoinAddresses} selectedCoin={openCoinActionModal.coin === "QORT" ? 'QORT' :getCoinLabel()} />
|
||||
</>
|
||||
)}
|
||||
{openCoinActionModal.type === 'send' && (
|
||||
<CoinActionRow style={{gap: "10px"}}>
|
||||
{/* <CoinCancelBtn onClick={() => setOpenCoinActionModal(null)}>
|
||||
Cancel
|
||||
</CoinCancelBtn> */}
|
||||
<CoinConfirmSendBtn
|
||||
onClick={() => {
|
||||
if(openCoinActionModal.type === 'send'){
|
||||
sendCoin()
|
||||
}
|
||||
setNotification({
|
||||
alertType: "alertInfo",
|
||||
msg: "Sending...",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{openCoinActionModal.type === "send" ? "Send" : "Receive"}
|
||||
</CoinConfirmSendBtn>
|
||||
</CoinActionRow>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
</CoinActionContainer>
|
||||
</ReusableModal>
|
||||
)}
|
||||
</HeaderNav>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddressBox = styled(Box)`
|
||||
display: flex;
|
||||
border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5));
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
word-break: break-word;
|
||||
padding: 5px 15px 5px 15px;
|
||||
gap: 5px;
|
||||
border-radius: 100px;
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 14.52px;
|
||||
text-align: left;
|
||||
color: var(--50-white, rgba(255, 255, 255, 0.5));
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
&:hover {
|
||||
background-color: rgba(41, 41, 43, 1);
|
||||
color: white;
|
||||
svg path {
|
||||
fill: white; // Fill color changes to white on hover
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
|
||||
const ReceiveCoin = ({coinAddresses, setCoinAddresses, selectedCoin, setOpen, setInfo})=> {
|
||||
const [errorMsg, setErrorMsg] = useState('')
|
||||
const foreignAddress = useMemo(()=> {
|
||||
return coinAddresses[selectedCoin] || null
|
||||
}, [coinAddresses, selectedCoin])
|
||||
|
||||
const getForeignAddress = async (coin)=> {
|
||||
try {
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "info",
|
||||
message: "Retrieving address...",
|
||||
});
|
||||
const response = await qortalRequest({
|
||||
action: "GET_USER_WALLET",
|
||||
coin
|
||||
});
|
||||
if(response?.address){
|
||||
setCoinAddresses((prev)=> {
|
||||
return {
|
||||
...prev,
|
||||
[coin]: response.address
|
||||
}
|
||||
})
|
||||
}
|
||||
if(response?.error){
|
||||
throw new Error(response?.error || "Failed to send coin.")
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorMsg(error?.message)
|
||||
} finally {
|
||||
setOpen(false);
|
||||
setInfo(null);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=> {
|
||||
if(!selectedCoin) return
|
||||
if(!coinAddresses[selectedCoin]){
|
||||
getForeignAddress(selectedCoin)
|
||||
}
|
||||
}, [selectedCoin, coinAddresses])
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<Typography sx={{
|
||||
color: 'white'
|
||||
}}>{`Send ${selectedCoin} to your address below`}</Typography>
|
||||
<Spacer height="20px" />
|
||||
{foreignAddress && (
|
||||
<CopyToClipboard text={foreignAddress} onCopy={()=> {
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "info",
|
||||
message: "Address copied!",
|
||||
});
|
||||
}}>
|
||||
<AddressBox>
|
||||
{foreignAddress} <img src={Copy} />
|
||||
</AddressBox>
|
||||
</CopyToClipboard>
|
||||
)}
|
||||
{foreignAddress && (
|
||||
<>
|
||||
<AddressQRCode targetAddress={foreignAddress} />
|
||||
</>
|
||||
)}
|
||||
{errorMsg && (
|
||||
<>
|
||||
<Spacer height="20px" />
|
||||
<Typography sx={{
|
||||
color: 'white'
|
||||
}}>{errorMsg}</Typography>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
57
src/components/history/History-styles.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { Box, styled } from "@mui/system";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
|
||||
type HistoryBtnProp = {
|
||||
activeBtn: boolean;
|
||||
};
|
||||
|
||||
export const HistoryButtonRow = styled(Box)({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
margin: "5px 5px 5px 0",
|
||||
});
|
||||
|
||||
export const HistoryButton = styled(Button, {
|
||||
shouldForwardProp: (prop) => prop !== "activeBtn",
|
||||
})<HistoryBtnProp>(({ theme, activeBtn }) => ({
|
||||
fontFamily: "Inter",
|
||||
color: activeBtn ? theme.palette.text.primary : theme.palette.primary.main,
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
height: "30px",
|
||||
lineHeight: "40px",
|
||||
userSelect: "none",
|
||||
background: activeBtn ? theme.palette.primary.main : "transparent",
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
background: theme.palette.primary.main,
|
||||
color: theme.palette.text.primary,
|
||||
cursor: "pointer",
|
||||
},
|
||||
}));
|
||||
|
||||
export const Refresh = styled(RefreshIcon)({
|
||||
cursor: "pointer",
|
||||
color: "#fff",
|
||||
fontSize: "25px",
|
||||
marginLeft: "5px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
transform: "scale(1.1)",
|
||||
},
|
||||
});
|
||||
|
||||
export const ShowingFont = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Inter",
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "25px",
|
||||
marginBottom: "5px",
|
||||
userSelect: "none",
|
||||
}));
|
131
src/components/history/History.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
ButtonBase,
|
||||
Snackbar
|
||||
} from "@mui/material";
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import gameContext from "../../contexts/gameContext";
|
||||
import HistoryList from "./HistoryList";
|
||||
import { ShowingFont, Refresh, HistoryButtonRow, HistoryButton } from "./History-styles";
|
||||
|
||||
export const History = ({ qortAddress, show }) => {
|
||||
const [buyHistory, setBuyHistory] = useState({});
|
||||
const [sellHistory, setSellHistory] = useState({});
|
||||
|
||||
const { selectedCoin } = useContext(gameContext);
|
||||
const [mode, setMode] = useState("buyHistory");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const selectedHistory = useMemo(() => {
|
||||
if (mode === "buyHistory") return buyHistory[selectedCoin] || [];
|
||||
if (mode === "sellHistory") return sellHistory[selectedCoin] || [];
|
||||
}, [selectedCoin, buyHistory, sellHistory, mode]);
|
||||
const getBuyHistory = useCallback(
|
||||
(address, foreignBlockchain, mode, limit = 20) => {
|
||||
setOpen(true);
|
||||
let historyUrl;
|
||||
if (mode === "buyHistory") {
|
||||
historyUrl = `/crosschain/trades?foreignBlockchain=${foreignBlockchain}&buyerAddress=${address}&limit=${limit}&reverse=true`;
|
||||
}
|
||||
if (mode === "sellHistory") {
|
||||
historyUrl = `/crosschain/trades?foreignBlockchain=${foreignBlockchain}&sellerAddress=${address}&limit=${limit}&reverse=true`;
|
||||
}
|
||||
|
||||
fetch(historyUrl)
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
if (mode === "buyHistory") {
|
||||
setBuyHistory((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[foreignBlockchain]: data,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (mode === "sellHistory") {
|
||||
setSellHistory((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[foreignBlockchain]: data,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setOpen(false);
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!qortAddress || !selectedCoin) return;
|
||||
if (mode === "buyHistory" && buyHistory[selectedCoin]) return;
|
||||
if (mode === "sellHistory" && sellHistory[selectedCoin]) return;
|
||||
|
||||
getBuyHistory(qortAddress, selectedCoin, mode);
|
||||
}, [qortAddress, selectedCoin, buyHistory, mode]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
display: show ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<HistoryButtonRow>
|
||||
<HistoryButton
|
||||
activeBtn={mode === "buyHistory"}
|
||||
onClick={() => {
|
||||
setMode("buyHistory");
|
||||
}}
|
||||
>
|
||||
Buy History
|
||||
</HistoryButton>
|
||||
<HistoryButton
|
||||
activeBtn={mode === "sellHistory"}
|
||||
onClick={() => {
|
||||
setMode("sellHistory");
|
||||
}}
|
||||
>
|
||||
Sell History
|
||||
</HistoryButton>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
getBuyHistory(qortAddress, selectedCoin, mode);
|
||||
}}
|
||||
>
|
||||
<Refresh />
|
||||
</ButtonBase>
|
||||
</HistoryButtonRow>
|
||||
<ShowingFont>Showing most recent 20 results</ShowingFont>
|
||||
<HistoryList qortAddress={qortAddress} historyList={selectedHistory} />
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
open={open}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
onClose={() => setOpen(false)}
|
||||
severity="info"
|
||||
variant="filled"
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
{"Fetching History"}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
);
|
||||
};
|
189
src/components/history/HistoryList.tsx
Normal file
@ -0,0 +1,189 @@
|
||||
import { ColDef } from "ag-grid-community";
|
||||
import { AgGridReact } from "ag-grid-react";
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { autoSizeStrategy, baseLocalHost } from "../Grids/TradeOffers";
|
||||
import { Alert, Box, Snackbar, SnackbarCloseReason, Typography } from "@mui/material";
|
||||
import gameContext from "../../contexts/gameContext";
|
||||
import { formatTimestampForum } from "../../utils/formatTime";
|
||||
|
||||
const defaultColDef = {
|
||||
resizable: true, // Make columns resizable by default
|
||||
sortable: true, // Make columns sortable by default
|
||||
suppressMovable: true, // Prevent columns from being movable
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default function HistoryList({ qortAddress, historyList }) {
|
||||
const gridRef = useRef<any>(null);
|
||||
const { getCoinLabel, selectedCoin} = useContext(gameContext)
|
||||
const [qortalNames, setQortalNames] = useState({});
|
||||
|
||||
|
||||
const onGridReady = useCallback((params: any) => {
|
||||
params.api.sizeColumnsToFit(); // Adjust columns to fit the grid width
|
||||
const allColumnIds = params.columnApi
|
||||
.getAllColumns()
|
||||
.map((col: any) => col.getColId());
|
||||
params.columnApi.autoSizeColumns(allColumnIds); // Automatically adjust the width to fit content
|
||||
}, []);
|
||||
|
||||
const getName = async (address) => {
|
||||
try {
|
||||
const response = await fetch("/names/address/" + address);
|
||||
const nameData = await response.json();
|
||||
if (nameData?.length > 0) {
|
||||
setQortalNames((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[address]: nameData[0].name,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
setQortalNames((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[address]: null,
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// error
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const columnDefs: ColDef[] = useMemo(()=> {
|
||||
return [
|
||||
{
|
||||
headerName: "QORT AMOUNT",
|
||||
field: "qortAmount",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: `${getCoinLabel()}/QORT`,
|
||||
valueGetter: (params) =>
|
||||
+params.data.foreignAmount / +params.data.qortAmount,
|
||||
sortable: true,
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: `Total ${getCoinLabel()} Value`,
|
||||
field: "foreignAmount",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: "Time",
|
||||
field: "tradeTimestamp",
|
||||
valueGetter: (params) =>
|
||||
formatTimestampForum(params.data.tradeTimestamp),
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 200, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: "Buyer",
|
||||
field: "buyerReceivingAddress",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 200, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
valueGetter: (params) => {
|
||||
if (params?.data?.buyerReceivingAddress) {
|
||||
if (qortalNames[params?.data?.buyerReceivingAddress]) {
|
||||
return qortalNames[params?.data?.buyerReceivingAddress];
|
||||
} else if (qortalNames[params?.data?.buyerReceivingAddress] === undefined) {
|
||||
getName(params?.data?.buyerReceivingAddress);
|
||||
|
||||
return params?.data?.buyerReceivingAddress;
|
||||
} else {
|
||||
return params?.data?.buyerReceivingAddress;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "Seller",
|
||||
field: "sellerAddress",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 200, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
valueGetter: (params) => {
|
||||
if (params?.data?.sellerAddress) {
|
||||
if (qortalNames[params?.data?.sellerAddress]) {
|
||||
return qortalNames[params?.data?.sellerAddress];
|
||||
} else if (qortalNames[params?.data?.sellerAddress] === undefined) {
|
||||
getName(params?.data?.sellerAddress);
|
||||
|
||||
return params?.data?.sellerAddress;
|
||||
} else {
|
||||
return params?.data?.sellerAddress;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
}, [selectedCoin, qortalNames, getCoinLabel])
|
||||
|
||||
|
||||
|
||||
// const onSelectionChanged = (event: any) => {
|
||||
// const selectedRows = event.api.getSelectedRows();
|
||||
// if(selectedRows[0]){
|
||||
// setSelectedTrade(selectedRows[0])
|
||||
// } else {
|
||||
// setSelectedTrade(null)
|
||||
// }
|
||||
// };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="ag-theme-alpine-dark"
|
||||
style={{ height: 400, width: "100%" }}
|
||||
>
|
||||
<AgGridReact
|
||||
ref={gridRef}
|
||||
columnDefs={columnDefs}
|
||||
defaultColDef={defaultColDef}
|
||||
rowData={historyList}
|
||||
// onRowClicked={onRowClicked}
|
||||
// onSelectionChanged={onSelectionChanged}
|
||||
// getRowStyle={getRowStyle}
|
||||
autoSizeStrategy={autoSizeStrategy}
|
||||
rowSelection="single" // Enable multi-select
|
||||
suppressHorizontalScroll={false} // Allow horizontal scroll on mobile if needed
|
||||
suppressCellFocus={true} // Prevents cells from stealing focus in mobile
|
||||
// pagination={true}
|
||||
// paginationPageSize={10}
|
||||
onGridReady={onGridReady}
|
||||
// domLayout='autoHeight'
|
||||
// getRowId={(params) => params.data.qortalAtAddress} // Ensure rows have unique IDs
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,10 +1,24 @@
|
||||
import { Alert, Box, Button, DialogActions, DialogContent, DialogTitle, IconButton, InputLabel, Snackbar, SnackbarCloseReason, TextField, Typography, styled } from '@mui/material'
|
||||
import React, { useContext } from 'react'
|
||||
import { BootstrapDialog } from '../Terms'
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { Spacer } from '../common/Spacer';
|
||||
import gameContext from '../../contexts/gameContext';
|
||||
import TradeBotList from './TradeBotList';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
Snackbar,
|
||||
SnackbarCloseReason,
|
||||
TextField,
|
||||
Typography,
|
||||
styled,
|
||||
} from "@mui/material";
|
||||
import React, { useContext } from "react";
|
||||
import { BootstrapDialog } from "../Terms";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Spacer } from "../common/Spacer";
|
||||
import gameContext from "../../contexts/gameContext";
|
||||
import TradeBotList from "./TradeBotList";
|
||||
|
||||
export const CustomLabel = styled(InputLabel)`
|
||||
font-weight: 400;
|
||||
@ -12,188 +26,223 @@ export const CustomLabel = styled(InputLabel)`
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
`
|
||||
`;
|
||||
|
||||
export const minimumAmountSellTrades = {
|
||||
'LITECOIN': {
|
||||
LITECOIN: {
|
||||
value: 0.01,
|
||||
ticker: 'LTC'
|
||||
}
|
||||
}
|
||||
ticker: "LTC",
|
||||
},
|
||||
DOGECOIN: {
|
||||
value: 1,
|
||||
ticker: "DOGE",
|
||||
},
|
||||
BITCOIN: {
|
||||
value: 0.001,
|
||||
ticker: "BTC",
|
||||
},
|
||||
DIGIBYTE: {
|
||||
value: 0.01,
|
||||
ticker: "DGB",
|
||||
},
|
||||
RAVENCOIN: {
|
||||
value: 0.01,
|
||||
ticker: "RVN",
|
||||
},
|
||||
PIRATECHAIN: {
|
||||
value: 0.0002,
|
||||
ticker: "ARRR",
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomInput = styled(TextField)({
|
||||
width: "183px", // Adjust the width as needed
|
||||
borderRadius: "5px",
|
||||
// backgroundColor: "rgba(30, 30, 32, 1)",
|
||||
width: "183px", // Adjust the width as needed
|
||||
borderRadius: "5px",
|
||||
// backgroundColor: "rgba(30, 30, 32, 1)",
|
||||
outline: "none",
|
||||
input: {
|
||||
fontSize: 10,
|
||||
fontFamily: "Inter",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
"&::placeholder": {
|
||||
fontSize: 16,
|
||||
color: "rgba(255, 255, 255, 0.2)",
|
||||
},
|
||||
outline: "none",
|
||||
input: {
|
||||
fontSize: 10,
|
||||
fontFamily: "Inter",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
"&::placeholder": {
|
||||
fontSize: 16,
|
||||
color: "rgba(255, 255, 255, 0.2)",
|
||||
},
|
||||
outline: "none",
|
||||
padding: "10px",
|
||||
padding: "10px",
|
||||
},
|
||||
"& .MuiOutlinedInput-root": {
|
||||
"& fieldset": {
|
||||
border: "0.5px solid rgba(255, 255, 255, 0.5)",
|
||||
},
|
||||
"& .MuiOutlinedInput-root": {
|
||||
"& fieldset": {
|
||||
border: '0.5px solid rgba(255, 255, 255, 0.5)',
|
||||
},
|
||||
"&:hover fieldset": {
|
||||
border: '0.5px solid rgba(255, 255, 255, 0.5)',
|
||||
},
|
||||
"&.Mui-focused fieldset": {
|
||||
border: '0.5px solid rgba(255, 255, 255, 0.5)',
|
||||
},
|
||||
"&:hover fieldset": {
|
||||
border: "0.5px solid rgba(255, 255, 255, 0.5)",
|
||||
},
|
||||
"& .MuiInput-underline:before": {
|
||||
borderBottom: "none",
|
||||
"&.Mui-focused fieldset": {
|
||||
border: "0.5px solid rgba(255, 255, 255, 0.5)",
|
||||
},
|
||||
"& .MuiInput-underline:hover:not(.Mui-disabled):before": {
|
||||
borderBottom: "none",
|
||||
},
|
||||
"& .MuiInput-underline:after": {
|
||||
borderBottom: "none",
|
||||
},
|
||||
});
|
||||
},
|
||||
"& .MuiInput-underline:before": {
|
||||
borderBottom: "none",
|
||||
},
|
||||
"& .MuiInput-underline:hover:not(.Mui-disabled):before": {
|
||||
borderBottom: "none",
|
||||
},
|
||||
"& .MuiInput-underline:after": {
|
||||
borderBottom: "none",
|
||||
},
|
||||
});
|
||||
|
||||
export const CreateSell = ({ qortAddress, show }) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [qortAmount, setQortAmount] = React.useState(0);
|
||||
const [foreignAmount, setForeignAmount] = React.useState(0);
|
||||
const {
|
||||
updateTemporaryFailedTradeBots,
|
||||
sellOrders,
|
||||
fetchTemporarySellOrders,
|
||||
isUsingGateway,
|
||||
getCoinLabel,
|
||||
selectedCoin,
|
||||
} = useContext(gameContext);
|
||||
const [openAlert, setOpenAlert] = React.useState(false);
|
||||
const [info, setInfo] = React.useState<any>(null);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
setForeignAmount(0);
|
||||
setQortAmount(0);
|
||||
};
|
||||
|
||||
export const CreateSell = ({qortAddress, show}) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [qortAmount, setQortAmount] = React.useState(0)
|
||||
const [foreignAmount, setForeignAmount] = React.useState(0)
|
||||
const {updateTemporaryFailedTradeBots, sellOrders, fetchTemporarySellOrders, isUsingGateway} = useContext(gameContext)
|
||||
const [openAlert, setOpenAlert] = React.useState(false)
|
||||
const [info, setInfo] = React.useState<any>(null)
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
setForeignAmount(0)
|
||||
setQortAmount(0)
|
||||
};
|
||||
const createSellOrder = async () => {
|
||||
try {
|
||||
setInfo({
|
||||
type: "info",
|
||||
message: "Attempting to create sell order. Please wait...",
|
||||
});
|
||||
const res = await qortalRequestWithTimeout(
|
||||
{
|
||||
action: "CREATE_TRADE_SELL_ORDER",
|
||||
qortAmount,
|
||||
foreignBlockchain: selectedCoin,
|
||||
foreignAmount: qortAmount * foreignAmount,
|
||||
},
|
||||
900000
|
||||
);
|
||||
|
||||
const createSellOrder = async() => {
|
||||
try {
|
||||
setOpen(true)
|
||||
setInfo({
|
||||
type: 'info',
|
||||
message: "Attempting to create sell order. Please wait..."
|
||||
})
|
||||
const res = await qortalRequestWithTimeout({
|
||||
action: "CREATE_TRADE_SELL_ORDER",
|
||||
qortAmount,
|
||||
foreignBlockchain: 'LITECOIN',
|
||||
foreignAmount
|
||||
}, 900000);
|
||||
|
||||
if(res?.error && res?.failedTradeBot){
|
||||
await updateTemporaryFailedTradeBots({
|
||||
atAddress: res?.failedTradeBot?.atAddress,
|
||||
status: 'FAILED',
|
||||
qortAddress: res?.failedTradeBot?.creatorAddress,
|
||||
|
||||
})
|
||||
fetchTemporarySellOrders()
|
||||
setOpenAlert(true)
|
||||
setInfo({
|
||||
type: 'error',
|
||||
message: "Unable to create sell order. Please try again."
|
||||
})
|
||||
}
|
||||
if(!res?.error){
|
||||
setOpenAlert(true)
|
||||
setForeignAmount(0)
|
||||
setQortAmount(0)
|
||||
setOpen(false)
|
||||
|
||||
setInfo({
|
||||
type: 'success',
|
||||
message: "Sell order created. Please wait a couple of minutes for the network to propogate the changes."
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
if(error?.error && error?.failedTradeBot){
|
||||
await updateTemporaryFailedTradeBots({
|
||||
atAddress: error?.failedTradeBot?.atAddress,
|
||||
status: 'FAILED',
|
||||
qortAddress: error?.failedTradeBot?.creatorAddress,
|
||||
|
||||
})
|
||||
fetchTemporarySellOrders()
|
||||
setOpenAlert(true)
|
||||
setInfo({
|
||||
type: 'error',
|
||||
message: "Unable to create sell order. Please try again."
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCloseAlert = (
|
||||
event?: React.SyntheticEvent | Event,
|
||||
reason?: SnackbarCloseReason,
|
||||
) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
if (res?.error && res?.failedTradeBot) {
|
||||
await updateTemporaryFailedTradeBots({
|
||||
atAddress: res?.failedTradeBot?.atAddress,
|
||||
status: "FAILED",
|
||||
qortAddress: res?.failedTradeBot?.creatorAddress,
|
||||
});
|
||||
fetchTemporarySellOrders();
|
||||
setOpenAlert(true);
|
||||
setInfo({
|
||||
type: "error",
|
||||
message: "Unable to create sell order. Please try again.",
|
||||
});
|
||||
}
|
||||
if (!res?.error) {
|
||||
setOpenAlert(true);
|
||||
setForeignAmount(0);
|
||||
setQortAmount(0);
|
||||
setOpen(false);
|
||||
|
||||
setOpenAlert(false);
|
||||
setInfo(null)
|
||||
};
|
||||
setInfo({
|
||||
type: "success",
|
||||
message:
|
||||
"Sell order created. Please wait a couple of minutes for the network to propogate the changes.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.error && error?.failedTradeBot) {
|
||||
await updateTemporaryFailedTradeBots({
|
||||
atAddress: error?.failedTradeBot?.atAddress,
|
||||
status: "FAILED",
|
||||
qortAddress: error?.failedTradeBot?.creatorAddress,
|
||||
});
|
||||
fetchTemporarySellOrders();
|
||||
setOpenAlert(true);
|
||||
setInfo({
|
||||
type: "error",
|
||||
message: "Unable to create sell order. Please try again.",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(isUsingGateway){
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: show ? 'flex' : 'none',
|
||||
height: '500px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<Typography sx={{
|
||||
color: 'white',
|
||||
maxWidth: '340px',
|
||||
padding: '10px'
|
||||
}}>
|
||||
Managing your sell orders is not possible using a gateway node. Please switch to a local or custom node at the authentication page
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
const handleCloseAlert = (
|
||||
event?: React.SyntheticEvent | Event,
|
||||
reason?: SnackbarCloseReason
|
||||
) => {
|
||||
if (reason === "clickaway") {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: show ? 'block' : 'none'
|
||||
}}>
|
||||
<Button onClick={handleClickOpen}>New Sell Order</Button>
|
||||
<TradeBotList qortAddress={qortAddress} failedTradeBots={sellOrders.filter((item)=> item.status === 'FAILED')} />
|
||||
setOpenAlert(false);
|
||||
setInfo(null);
|
||||
};
|
||||
|
||||
<BootstrapDialog
|
||||
if (isUsingGateway) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: show ? "flex" : "none",
|
||||
height: "500px",
|
||||
alignItems: "flex-start",
|
||||
marginTop: "20px",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "white",
|
||||
maxWidth: "340px",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
Managing your sell orders is not possible using a gateway node. Please
|
||||
switch to a local or custom node at the authentication page
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: show ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleClickOpen}>New Sell Order</Button>
|
||||
<TradeBotList
|
||||
qortAddress={qortAddress}
|
||||
failedTradeBots={sellOrders.filter((item) => item.status === "FAILED")}
|
||||
/>
|
||||
|
||||
<BootstrapDialog
|
||||
onClose={handleClose}
|
||||
aria-labelledby="customized-dialog-title"
|
||||
open={open}
|
||||
sx={{
|
||||
'& .MuiDialogContent-root': {
|
||||
width: '300px'
|
||||
},
|
||||
"& .MuiDialogContent-root": {
|
||||
width: "300px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{ m: 0, p: 2 }} id="customized-dialog-title">
|
||||
New Sell Order - QORT for LTC
|
||||
{`New Sell Order - QORT for ${getCoinLabel()}`}
|
||||
</DialogTitle>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={handleClose}
|
||||
sx={(theme) => ({
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme.palette.grey[500],
|
||||
@ -202,8 +251,10 @@ export const CreateSell = ({qortAddress, show}) => {
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<DialogContent dividers>
|
||||
<Box>
|
||||
<CustomLabel htmlFor="standard-adornment-name">QORT amount</CustomLabel>
|
||||
<Box>
|
||||
<CustomLabel htmlFor="standard-adornment-name">
|
||||
QORT amount
|
||||
</CustomLabel>
|
||||
<Spacer height="5px" />
|
||||
<CustomInput
|
||||
id="standard-adornment-name"
|
||||
@ -214,7 +265,7 @@ export const CreateSell = ({qortAddress, show}) => {
|
||||
/>
|
||||
<Spacer height="6px" />
|
||||
<CustomLabel htmlFor="standard-adornment-amount">
|
||||
Price Each (LTC)
|
||||
{`Price of Each QORT (in ${getCoinLabel()})`}
|
||||
</CustomLabel>
|
||||
<Spacer height="5px" />
|
||||
<CustomInput
|
||||
@ -225,34 +276,54 @@ export const CreateSell = ({qortAddress, show}) => {
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Spacer height="6px" />
|
||||
<Typography>{qortAmount * foreignAmount} LTC for {qortAmount} QORT</Typography>
|
||||
<Typography sx={{
|
||||
fontSize: '12px'
|
||||
}}>Total sell amount needs to be greater than: {minimumAmountSellTrades.LITECOIN.value} {' '} {minimumAmountSellTrades.LITECOIN.ticker}</Typography>
|
||||
<Typography>
|
||||
{`${qortAmount * foreignAmount} ${getCoinLabel()}`} for{" "}
|
||||
{qortAmount} QORT
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
Total sell amount needs to be greater than:{" "}
|
||||
{minimumAmountSellTrades[selectedCoin]?.value}{" "}
|
||||
{minimumAmountSellTrades[selectedCoin]?.ticker}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button autoFocus onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button disabled={!qortAmount || !(qortAmount * foreignAmount > minimumAmountSellTrades.LITECOIN.value)} autoFocus onClick={createSellOrder}>
|
||||
<Button
|
||||
disabled={
|
||||
!qortAmount ||
|
||||
!(
|
||||
qortAmount * foreignAmount >
|
||||
minimumAmountSellTrades[selectedCoin]?.value
|
||||
)
|
||||
}
|
||||
autoFocus
|
||||
onClick={createSellOrder}
|
||||
>
|
||||
Create sell order
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</BootstrapDialog>
|
||||
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={openAlert} onClose={handleCloseAlert}>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
open={openAlert}
|
||||
onClose={handleCloseAlert}
|
||||
>
|
||||
<Alert
|
||||
|
||||
|
||||
onClose={handleCloseAlert}
|
||||
severity={info?.type}
|
||||
variant="filled"
|
||||
sx={{ width: '100%' }}
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
{info?.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -8,9 +8,16 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { autoSizeStrategy } from "../Grids/TradeOffers";
|
||||
import { Alert, Box, Snackbar, SnackbarCloseReason, Typography } from "@mui/material";
|
||||
import { autoSizeStrategy, baseLocalHost } from "../Grids/TradeOffers";
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Snackbar,
|
||||
SnackbarCloseReason,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import gameContext from "../../contexts/gameContext";
|
||||
import { BuyContainerDivider } from "../Grids/Table-styles";
|
||||
|
||||
const defaultColDef = {
|
||||
resizable: true, // Make columns resizable by default
|
||||
@ -18,58 +25,22 @@ const defaultColDef = {
|
||||
suppressMovable: true, // Prevent columns from being movable
|
||||
};
|
||||
|
||||
const columnDefs: ColDef[] = [
|
||||
{
|
||||
headerCheckboxSelection: false, // Adds a checkbox in the header for selecting all rows
|
||||
checkboxSelection: true, // Adds checkboxes in each row for selection
|
||||
headerName: "Select", // You can customize the header name
|
||||
width: 50, // Adjust the width as needed
|
||||
pinned: "left", // Optional, to pin this column on the left
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
headerName: "QORT AMOUNT",
|
||||
field: "qortAmount",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: "LTC/QORT",
|
||||
valueGetter: (params) =>
|
||||
+params.data.foreignAmount / +params.data.qortAmount,
|
||||
sortable: true,
|
||||
sort: "asc",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: "Total LTC Value",
|
||||
field: "foreignAmount",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: "Status",
|
||||
field: "status",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 300, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default function TradeBotList({ qortAddress, failedTradeBots }) {
|
||||
const [tradeBotList, setTradeBotList] = useState([]);
|
||||
const [selectedTrade, setSelectedTrade] = useState(null);
|
||||
const tradeBotListRef = useRef([])
|
||||
const tradeBotListRef = useRef([]);
|
||||
const offeringTrades = useRef<any[]>([]);
|
||||
const qortAddressRef = useRef(null);
|
||||
const gridRef = useRef<any>(null);
|
||||
const {updateTemporaryFailedTradeBots, fetchTemporarySellOrders, deleteTemporarySellOrder} = useContext(gameContext)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [info, setInfo] = useState<any>(null)
|
||||
const {
|
||||
updateTemporaryFailedTradeBots,
|
||||
fetchTemporarySellOrders,
|
||||
deleteTemporarySellOrder,
|
||||
getCoinLabel,
|
||||
selectedCoin,
|
||||
} = useContext(gameContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [info, setInfo] = useState<any>(null);
|
||||
const filteredOutTradeBotListWithoutFailed = useMemo(() => {
|
||||
const list = tradeBotList.filter(
|
||||
(item) =>
|
||||
@ -77,7 +48,7 @@ export default function TradeBotList({ qortAddress, failedTradeBots }) {
|
||||
(failedItem) => failedItem.atAddress === item.atAddress
|
||||
)
|
||||
);
|
||||
return list
|
||||
return list;
|
||||
}, [failedTradeBots, tradeBotList]);
|
||||
|
||||
const onGridReady = useCallback((params: any) => {
|
||||
@ -88,6 +59,49 @@ export default function TradeBotList({ qortAddress, failedTradeBots }) {
|
||||
params.columnApi.autoSizeColumns(allColumnIds); // Automatically adjust the width to fit content
|
||||
}, []);
|
||||
|
||||
const columnDefs: ColDef[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
headerCheckboxSelection: false, // Adds a checkbox in the header for selecting all rows
|
||||
checkboxSelection: true, // Adds checkboxes in each row for selection
|
||||
headerName: "Select", // You can customize the header name
|
||||
width: 50, // Adjust the width as needed
|
||||
pinned: "left", // Optional, to pin this column on the left
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
headerName: "QORT AMOUNT",
|
||||
field: "qortAmount",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: `${getCoinLabel()}/QORT`,
|
||||
valueGetter: (params) =>
|
||||
+params.data.foreignAmount / +params.data.qortAmount,
|
||||
sortable: true,
|
||||
sort: "asc",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: `Total ${getCoinLabel()} Value`,
|
||||
field: "foreignAmount",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 150, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: "Status",
|
||||
field: "status",
|
||||
flex: 1, // Flex makes this column responsive
|
||||
minWidth: 300, // Ensure it doesn't shrink too much
|
||||
resizable: true,
|
||||
},
|
||||
];
|
||||
}, [selectedCoin, getCoinLabel]);
|
||||
useEffect(() => {
|
||||
if (qortAddress) {
|
||||
qortAddressRef.current = qortAddress;
|
||||
@ -154,112 +168,159 @@ export default function TradeBotList({ qortAddress, failedTradeBots }) {
|
||||
tradeBotListRef.current = sellTrades;
|
||||
};
|
||||
|
||||
const restartTradeOffers = () => {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.close(1000, "forced"); // Close with a custom reason
|
||||
socketRef.current = null;
|
||||
}
|
||||
offeringTrades.current = [];
|
||||
setTradeBotList([]);
|
||||
tradeBotListRef.current = [];
|
||||
};
|
||||
|
||||
const socketRef = useRef(null);
|
||||
|
||||
const initTradeOffersWebSocket = (restarted = false) => {
|
||||
let tradeOffersSocketCounter = 0;
|
||||
let socketTimeout: any;
|
||||
// let socketLink = `ws://127.0.0.1:12391/websockets/crosschain/tradebot?foreignBlockchain=LITECOIN`;
|
||||
let socketLink = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/websockets/crosschain/tradebot?foreignBlockchain=LITECOIN`;
|
||||
const socket = new WebSocket(socketLink);
|
||||
socket.onopen = () => {
|
||||
let socketLink = `${
|
||||
window.location.protocol === "https:" ? "wss:" : "ws:"
|
||||
}//${baseLocalHost}/websockets/crosschain/tradebot?foreignBlockchain=${selectedCoin}`;
|
||||
socketRef.current = new WebSocket(socketLink);
|
||||
socketRef.current.onopen = () => {
|
||||
setTimeout(pingSocket, 50);
|
||||
tradeOffersSocketCounter += 1;
|
||||
};
|
||||
socket.onmessage = (e) => {
|
||||
socketRef.current.onmessage = (e) => {
|
||||
tradeOffersSocketCounter += 1;
|
||||
restarted = false;
|
||||
processTradeBots(JSON.parse(e.data));
|
||||
};
|
||||
socket.onclose = () => {
|
||||
socketRef.current.onclose = (event) => {
|
||||
clearTimeout(socketTimeout);
|
||||
if (event.reason === "forced") {
|
||||
return;
|
||||
}
|
||||
restartTradeOffersWebSocket();
|
||||
};
|
||||
socket.onerror = (e) => {
|
||||
socketRef.current.onerror = (e) => {
|
||||
clearTimeout(socketTimeout);
|
||||
};
|
||||
const pingSocket = () => {
|
||||
socket.send("ping");
|
||||
socketRef.current.send("ping");
|
||||
socketTimeout = setTimeout(pingSocket, 295000);
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(!qortAddress) return
|
||||
initTradeOffersWebSocket();
|
||||
}, [qortAddress]);
|
||||
if (!qortAddress) return;
|
||||
if (selectedCoin === null) return;
|
||||
restartTradeOffers();
|
||||
|
||||
setTimeout(() => {
|
||||
initTradeOffersWebSocket();
|
||||
}, 500);
|
||||
return () => {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.close(1000, "forced");
|
||||
}
|
||||
};
|
||||
}, [qortAddress, selectedCoin]);
|
||||
|
||||
const onSelectionChanged = (event: any) => {
|
||||
const selectedRows = event.api.getSelectedRows();
|
||||
if(selectedRows[0]){
|
||||
setSelectedTrade(selectedRows[0])
|
||||
if (selectedRows[0]) {
|
||||
setSelectedTrade(selectedRows[0]);
|
||||
} else {
|
||||
setSelectedTrade(null)
|
||||
setSelectedTrade(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = (
|
||||
event?: React.SyntheticEvent | Event,
|
||||
reason?: SnackbarCloseReason,
|
||||
reason?: SnackbarCloseReason
|
||||
) => {
|
||||
if (reason === 'clickaway') {
|
||||
if (reason === "clickaway") {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
setInfo(null)
|
||||
setInfo(null);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const cancelSell = async ()=> {
|
||||
const cancelSell = async () => {
|
||||
try {
|
||||
if(!selectedTrade) return
|
||||
setOpen(true)
|
||||
if (!selectedTrade) return;
|
||||
setOpen(true);
|
||||
|
||||
setInfo({
|
||||
type: 'info',
|
||||
message: "Attempting to cancel sell order"
|
||||
})
|
||||
const res = await qortalRequestWithTimeout({
|
||||
action: "CANCEL_TRADE_SELL_ORDER",
|
||||
qortAmount: selectedTrade.qortAmount,
|
||||
foreignBlockchain: 'LITECOIN',
|
||||
foreignAmount: selectedTrade.foreignAmount,
|
||||
atAddress: selectedTrade.atAddress
|
||||
}, 900000);
|
||||
if(res?.signature){
|
||||
await deleteTemporarySellOrder(selectedTrade.atAddress)
|
||||
|
||||
|
||||
setSelectedTrade(null)
|
||||
setOpen(true)
|
||||
setInfo({
|
||||
type: 'success',
|
||||
message: "Sell order canceled. Please wait a couple of minutes for the network to propogate the changes"
|
||||
})
|
||||
}
|
||||
if(res?.error && res?.failedTradeBot){
|
||||
setOpen(true)
|
||||
setInfo({
|
||||
type: 'error',
|
||||
message: "Unable to cancel sell order. Please try again."
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
if(error?.error && error?.failedTradeBot){
|
||||
setOpen(true)
|
||||
setInfo({
|
||||
type: 'error',
|
||||
message: "Unable to cancel sell order. Please try again."
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
type: "info",
|
||||
message: "Attempting to cancel sell order",
|
||||
});
|
||||
const res = await qortalRequestWithTimeout(
|
||||
{
|
||||
action: "CANCEL_TRADE_SELL_ORDER",
|
||||
qortAmount: selectedTrade.qortAmount,
|
||||
foreignBlockchain: selectedTrade.foreignBlockchain,
|
||||
foreignAmount: selectedTrade.foreignAmount,
|
||||
atAddress: selectedTrade.atAddress,
|
||||
},
|
||||
900000
|
||||
);
|
||||
if (res?.signature) {
|
||||
await deleteTemporarySellOrder(selectedTrade.atAddress);
|
||||
|
||||
setSelectedTrade(null);
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "success",
|
||||
message:
|
||||
"Sell order canceled. Please wait a couple of minutes for the network to propogate the changes",
|
||||
});
|
||||
}
|
||||
if (res?.error && res?.failedTradeBot) {
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "error",
|
||||
message: "Unable to cancel sell order. Please try again.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.error && error?.failedTradeBot) {
|
||||
setOpen(true);
|
||||
setInfo({
|
||||
type: "error",
|
||||
message: "Unable to cancel sell order. Please try again.",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const CancelButton = () => {
|
||||
return (
|
||||
<button disabled={!selectedTrade || selectedTrade?.status === 'PENDING'} onClick={cancelSell} style={{borderRadius: '8px', width: '150px', height:"30px", background: (!selectedTrade || selectedTrade?.status === 'PENDING') ? 'gray' : "#4D7345",
|
||||
color: 'white', cursor: (!selectedTrade || selectedTrade?.status === 'PENDING') ? 'default' : 'pointer', border: '1px solid #375232', boxShadow: '0px 2.77px 2.21px 0px #00000005'
|
||||
}}>
|
||||
<button
|
||||
disabled={!selectedTrade || selectedTrade?.status === "PENDING"}
|
||||
onClick={cancelSell}
|
||||
style={{
|
||||
borderRadius: "8px",
|
||||
width: "150px",
|
||||
height: "auto",
|
||||
minHeight: "30px",
|
||||
background:
|
||||
!selectedTrade || selectedTrade?.status === "PENDING"
|
||||
? "gray"
|
||||
: "#4D7345",
|
||||
color: "white",
|
||||
cursor:
|
||||
!selectedTrade || selectedTrade?.status === "PENDING"
|
||||
? "default"
|
||||
: "pointer",
|
||||
border: "1px solid #375232",
|
||||
boxShadow: "0px 2.77px 2.21px 0px #00000005",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
>
|
||||
Cancel sell order
|
||||
</button>
|
||||
);
|
||||
@ -298,62 +359,73 @@ export default function TradeBotList({ qortAddress, failedTradeBots }) {
|
||||
|
||||
)} */}
|
||||
</div>
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
position: 'fixed',
|
||||
bottom: '0px',
|
||||
height: '100px',
|
||||
padding: '7px',
|
||||
background: '#181d1f',
|
||||
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
flexDirection: 'column',
|
||||
width: '100%'
|
||||
}}>
|
||||
{/* <Typography sx={{
|
||||
<div
|
||||
style={{
|
||||
height: "120px",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: "calc(100% - 14px)",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
position: "fixed",
|
||||
bottom: "0px",
|
||||
height: "100px",
|
||||
padding: "7px",
|
||||
background: "#323336",
|
||||
}}
|
||||
>
|
||||
<BuyContainerDivider />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "5px",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{/* <Typography sx={{
|
||||
fontSize: '16px',
|
||||
color: 'white',
|
||||
width: 'calc(100% - 75px)'
|
||||
}}>{selectedTotalQORT?.toFixed(3)} QORT</Typography> */}
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
width: 'calc(100% - 75px)'
|
||||
}}>
|
||||
{/* <Typography sx={{
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
width: "calc(100% - 75px)",
|
||||
}}
|
||||
>
|
||||
{/* <Typography sx={{
|
||||
fontSize: '16px',
|
||||
color: selectedTotalLTC > ltcBalance ? 'red' : 'white',
|
||||
color: selectedTotalLTC > foreignCoinBalance ? 'red' : 'white',
|
||||
}}><span>{selectedTotalLTC?.toFixed(4)}</span> <span style={{
|
||||
marginLeft: 'auto'
|
||||
}}>LTC</span></Typography> */}
|
||||
|
||||
|
||||
</Box>
|
||||
{/* <Typography sx={{
|
||||
</Box>
|
||||
{/* <Typography sx={{
|
||||
fontSize: '16px',
|
||||
color: 'white',
|
||||
|
||||
}}><span>{ltcBalance?.toFixed(4)}</span> <span style={{
|
||||
}}><span>{foreignCoinBalance?.toFixed(4)}</span> <span style={{
|
||||
marginLeft: 'auto'
|
||||
}}>LTC balance</span></Typography> */}
|
||||
</Box>
|
||||
{CancelButton()}
|
||||
</Box>
|
||||
{CancelButton()}
|
||||
</Box>
|
||||
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={open} onClose={handleClose}>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert
|
||||
|
||||
|
||||
onClose={handleClose}
|
||||
severity={info?.type}
|
||||
variant="filled"
|
||||
sx={{ width: '100%' }}
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
{info?.message}
|
||||
</Alert>
|
||||
|
@ -14,7 +14,7 @@ export interface UserNameAvatar {
|
||||
}
|
||||
|
||||
export interface IContextProps {
|
||||
ltcBalance: number | null;
|
||||
foreignCoinBalance: number | null;
|
||||
qortBalance: number | null;
|
||||
userInfo: any;
|
||||
setUserInfo: (val: any) => void;
|
||||
@ -32,11 +32,14 @@ export interface IContextProps {
|
||||
updateTemporaryFailedTradeBots: (val: any)=> void;
|
||||
fetchTemporarySellOrders: ()=> void;
|
||||
isUsingGateway: boolean;
|
||||
selectedCoin: string;
|
||||
setSelectedCoin: (val: any)=> void;
|
||||
getCoinLabel: ()=> string | null | void;
|
||||
}
|
||||
|
||||
const defaultState: IContextProps = {
|
||||
qortBalance: null,
|
||||
ltcBalance: null,
|
||||
foreignCoinBalance: null,
|
||||
userInfo: null,
|
||||
setUserInfo: () => {},
|
||||
userNameAvatar: {},
|
||||
@ -52,7 +55,10 @@ const defaultState: IContextProps = {
|
||||
deleteTemporarySellOrder: ()=> {},
|
||||
updateTemporaryFailedTradeBots: ()=> {},
|
||||
fetchTemporarySellOrders: ()=> {},
|
||||
isUsingGateway: true
|
||||
isUsingGateway: true,
|
||||
selectedCoin: 'LITECOIN',
|
||||
setSelectedCoin: ()=> {},
|
||||
getCoinLabel: ()=> {}
|
||||
};
|
||||
|
||||
export default React.createContext(defaultState);
|
||||
|
@ -1,64 +1,9 @@
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
|
||||
export const BubbleBoard = styled(Box)({
|
||||
position: "relative",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(9, 1fr)",
|
||||
gridTemplateRows: "repeat(4, 1fr)",
|
||||
gap: "15px",
|
||||
width: "815px",
|
||||
height: "353px",
|
||||
});
|
||||
|
||||
export const BubbleCard = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "77px",
|
||||
width: "77px",
|
||||
background: "#ffffff05",
|
||||
borderRadius: "50%",
|
||||
fontFamily: "Fredoka One, sans-serif",
|
||||
fontWeight: 500,
|
||||
fontSize: "40px",
|
||||
lineHeight: "48.4px",
|
||||
textAlign: "center",
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
|
||||
export const BubbleCardColored1 = styled(Box)({
|
||||
height: "77px",
|
||||
width: "77px",
|
||||
background: "linear-gradient(124.49deg, #70BAFF 7.03%, #F29999 94.22%)",
|
||||
boxShadow: "0px 0px 25.8px -1px #1C5A93",
|
||||
borderRadius: "50%",
|
||||
});
|
||||
|
||||
export const BubbleCardColored2 = styled(Box)({
|
||||
height: "77px",
|
||||
width: "77px",
|
||||
background: "linear-gradient(36.5deg, #70BAFF 19.69%, #F29999 90.73%)",
|
||||
boxShadow: "0px 0px 25.8px -1px #1C5A93",
|
||||
borderRadius: "50%",
|
||||
});
|
||||
|
||||
export const BubbleCardColored3 = styled(Box)({
|
||||
height: "77px",
|
||||
width: "77px",
|
||||
background: "linear-gradient(180deg, #70BAFF -24.68%, #ACABD0 25.49%, #F29999 74.03%)",
|
||||
boxShadow: "0px 0px 25.8px -1px #1C5A93",
|
||||
borderRadius: "50%",
|
||||
});
|
||||
|
||||
export const BubbleCardColored4 = styled(Box)({
|
||||
height: "77px",
|
||||
width: "77px",
|
||||
background: "linear-gradient(275.71deg, #70BAFF 35.99%, #F29999 95.61%)",
|
||||
boxShadow: "0px 0px 25.8px -1px #1C5A93",
|
||||
borderRadius: "50%",
|
||||
});
|
||||
type TabProp = {
|
||||
activeTab: boolean;
|
||||
};
|
||||
|
||||
export const MainCol = styled(Box)({
|
||||
display: "flex",
|
||||
@ -120,3 +65,53 @@ export const HomeWrapper = styled(Box)({
|
||||
height: "90vh",
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
export const TabsContainer = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
});
|
||||
|
||||
export const TabsRow = styled(Box)({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: "5px",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#323336",
|
||||
width: "fit-content",
|
||||
borderRadius: "5px",
|
||||
padding: "3px 0",
|
||||
});
|
||||
|
||||
export const Tab = styled(Box, {
|
||||
shouldForwardProp: (prop) => prop !== "activeTab"
|
||||
})<TabProp>(({ theme, activeTab }) => ({
|
||||
color: activeTab ? "#323336" : "#e8e8e8",
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontSize: "16px",
|
||||
lineHeight: "19.2px",
|
||||
fontWeight: 400,
|
||||
backgroundColor: activeTab ? "#e8e8e8" : "transparent",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "5px",
|
||||
height: "auto",
|
||||
transition: "all 0.4s ease-in-out",
|
||||
userSelect: "none",
|
||||
"&:hover": {
|
||||
color: "#323336",
|
||||
backgroundColor: "#babbbc",
|
||||
cursor: activeTab ? "auto" : "pointer",
|
||||
},
|
||||
}));
|
||||
|
||||
export const TabDivider = styled(Box, {
|
||||
shouldForwardProp: (prop) => prop !== "activeTab"
|
||||
})<TabProp>(({ theme, activeTab }) => ({
|
||||
width: "1px",
|
||||
height: "25px",
|
||||
margin: "0 3px",
|
||||
backgroundColor: activeTab ? "transparent" : "#a4a4a5",
|
||||
}));
|
@ -1,45 +1,41 @@
|
||||
import { FC, useContext, useEffect, useState } from "react";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { AppContainer } from "../../App-styles";
|
||||
|
||||
import axios from "axios";
|
||||
import { Header } from "../../components/header/Header";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import gameContext from "../../contexts/gameContext";
|
||||
import { Link } from "react-router-dom";
|
||||
import { NotificationContext } from "../../contexts/notificationContext";
|
||||
|
||||
import { TradeOffers } from "../../components/Grids/TradeOffers";
|
||||
import { OngoingTrades } from "../../components/Grids/OngoingTrades";
|
||||
import { Box, Button, CircularProgress } from "@mui/material";
|
||||
import { Box } from "@mui/material";
|
||||
import { TextTableTitle } from "../../components/Grids/Table-styles";
|
||||
import { Spacer } from "../../components/common/Spacer";
|
||||
import { ReusableModal } from "../../components/common/reusable-modal/ReusableModal";
|
||||
import { OAuthButton, OAuthButtonRow } from "./Home-Styles";
|
||||
import { Tab, TabDivider, TabsContainer, TabsRow } from "./Home-Styles";
|
||||
import { CreateSell } from "../../components/sell/CreateSell";
|
||||
import { History } from "../../components/history/History";
|
||||
|
||||
interface IsInstalledProps {}
|
||||
|
||||
export const HomePage: FC<IsInstalledProps> = ({}) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
export const HomePage = () => {
|
||||
const {
|
||||
qortBalance,
|
||||
ltcBalance,
|
||||
foreignCoinBalance,
|
||||
userInfo,
|
||||
isAuthenticated,
|
||||
setIsAuthenticated,
|
||||
OAuthLoading,
|
||||
setOAuthLoading,
|
||||
onGoingTrades,
|
||||
selectedCoin,
|
||||
} = useContext(gameContext);
|
||||
const { setNotification } = useContext(NotificationContext);
|
||||
const [mode, setMode] = useState("buy");
|
||||
|
||||
const filteredOngoingTrades = useMemo(() => {
|
||||
return onGoingTrades?.filter(
|
||||
(item) => item?.tradeInfo?.foreignBlockchain === selectedCoin
|
||||
);
|
||||
}, [onGoingTrades, selectedCoin]);
|
||||
const checkIfAuthenticated = async () => {
|
||||
try {
|
||||
setOAuthLoading(true);
|
||||
|
||||
setIsAuthenticated(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setOAuthLoading(false);
|
||||
}
|
||||
@ -50,57 +46,74 @@ export const HomePage: FC<IsInstalledProps> = ({}) => {
|
||||
}, [userInfo?.address]);
|
||||
|
||||
return (
|
||||
<AppContainer>
|
||||
<>
|
||||
<Header
|
||||
qortBalance={qortBalance}
|
||||
ltcBalance={ltcBalance}
|
||||
mode={mode}
|
||||
setMode={setMode}
|
||||
foreignCoinBalance={foreignCoinBalance}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: mode === "buy" ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<Spacer height="10px" />
|
||||
<Box
|
||||
sx={{
|
||||
padding: "0 10px",
|
||||
<AppContainer>
|
||||
<TabsContainer>
|
||||
<TabsRow>
|
||||
<Tab activeTab={mode === "buy"} onClick={() => setMode("buy")}>
|
||||
Buy QORT
|
||||
</Tab>
|
||||
{/* <TabDivider activeTab={mode === "buy" || mode === "sell"} /> */}
|
||||
<Tab activeTab={mode === "sell"} onClick={() => setMode("sell")}>
|
||||
Sell QORT
|
||||
</Tab>
|
||||
{/* <TabDivider activeTab={mode === "sell" || mode === "history"} /> */}
|
||||
{/* <Tab
|
||||
activeTab={mode === "history"}
|
||||
onClick={() => setMode("history")}
|
||||
>
|
||||
Trade History
|
||||
</Tab> */}
|
||||
</TabsRow>
|
||||
</TabsContainer>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: mode === "buy" ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<TextTableTitle
|
||||
<Spacer height="10px" />
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
My Pending Orders
|
||||
</TextTableTitle>
|
||||
</Box>
|
||||
<Spacer height="10px" />
|
||||
<OngoingTrades />
|
||||
<Spacer height="10px" />
|
||||
<Box
|
||||
sx={{
|
||||
padding: "0 10px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<TextTableTitle
|
||||
<TextTableTitle
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{`My Pending Orders: ${filteredOngoingTrades?.length}`}
|
||||
</TextTableTitle>
|
||||
</Box>
|
||||
<Spacer height="10px" />
|
||||
<OngoingTrades />
|
||||
<Spacer height="10px" />
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
Open Market Sell Orders
|
||||
</TextTableTitle>
|
||||
</Box>
|
||||
<Spacer height="10px" />
|
||||
<TradeOffers ltcBalance={ltcBalance} />
|
||||
</div>
|
||||
<TextTableTitle
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
Open Market Sell Orders
|
||||
</TextTableTitle>
|
||||
</Box>
|
||||
<Spacer height="10px" />
|
||||
<TradeOffers foreignCoinBalance={foreignCoinBalance} />
|
||||
</div>
|
||||
|
||||
<CreateSell show={mode === "sell"} qortAddress={userInfo?.address} />
|
||||
</AppContainer>
|
||||
<CreateSell show={mode === "sell"} qortAddress={userInfo?.address} />
|
||||
<History show={mode === "history"} qortAddress={userInfo?.address} />
|
||||
</AppContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,25 @@
|
||||
import moment from "moment";
|
||||
|
||||
export function formatTime(seconds: number): string {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
// Pad the seconds with a leading zero if less than 10
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
|
||||
export function formatTimestampForum(timestamp: number): string {
|
||||
const now = moment();
|
||||
const timestampMoment = moment(timestamp);
|
||||
const elapsedTime = now.diff(timestampMoment, 'minutes');
|
||||
|
||||
if (elapsedTime < 1) {
|
||||
return `Just now - ${timestampMoment.format('h:mm A')}`;
|
||||
} else if (elapsedTime < 60) {
|
||||
return `${elapsedTime}m ago - ${timestampMoment.format('h:mm A')}`;
|
||||
} else if (elapsedTime < 1440) {
|
||||
return `${Math.floor(elapsedTime / 60)}h ago - ${timestampMoment.format('h:mm A')}`;
|
||||
} else {
|
||||
return timestampMoment.format('MMM D, YYYY - h:mm A');
|
||||
}
|
||||
}
|