diff --git a/package-lock.json b/package-lock.json
index d6a4b93..95abc3a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,10 +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",
@@ -3967,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",
@@ -6328,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",
@@ -6368,6 +6383,18 @@
"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",
@@ -6414,6 +6441,18 @@
"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",
@@ -7286,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",
diff --git a/package.json b/package.json
index f23f0d3..77087a4 100644
--- a/package.json
+++ b/package.json
@@ -21,10 +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",
diff --git a/src/App.tsx b/src/App.tsx
index 13fa6bb..a99a84e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -256,7 +256,7 @@ function App() {
};
}, [userInfo?.address]);
- const getCoinLabel = (coin?: string)=> {
+ const getCoinLabel = (coin?: string)=> {
switch(coin || selectedCoin){
case "LITECOIN":{
diff --git a/src/assets/SVG/Copy.svg b/src/assets/SVG/Copy.svg
new file mode 100644
index 0000000..0348fc9
--- /dev/null
+++ b/src/assets/SVG/Copy.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/header/AddressQRCode.tsx b/src/components/header/AddressQRCode.tsx
new file mode 100644
index 0000000..f4858fb
--- /dev/null
+++ b/src/components/header/AddressQRCode.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/header/Header-styles.tsx b/src/components/header/Header-styles.tsx
index 7778779..b0898f7 100644
--- a/src/components/header/Header-styles.tsx
+++ b/src/components/header/Header-styles.tsx
@@ -216,6 +216,7 @@ export const CoinSelectRow = styled(Box)({
flexDirection: "column",
gap: "5px",
alignSelf: "flex-start",
+ marginBottom: '5px'
});
export const CoinActionContainer = styled(Box)({
diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx
index 3fa5edc..4d8c57f 100644
--- a/src/components/header/Header.tsx
+++ b/src/components/header/Header.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useRef, useContext, ChangeEvent } from "react";
+import { useState, useEffect, useRef, useContext, ChangeEvent, useMemo } from "react";
import {
BubbleCardColored1,
CoinActionContainer,
@@ -25,6 +25,10 @@ import { UserContext } from "../../contexts/userContext";
import { cropAddress } from "../../utils/cropAddress";
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 {
Alert,
AppBar,
@@ -39,6 +43,7 @@ import {
Snackbar,
SnackbarCloseReason,
Switch,
+ Typography,
styled,
} from "@mui/material";
import { sendRequestToExtension } from "../../App";
@@ -145,7 +150,8 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
useState(null);
const [receiverAddress, setReceiverAddress] = useState("");
const [senderAddress, setSenderAddress] = useState("");
-
+ const [amount, setAmount] = useState("");
+ const [coinAddresses, setCoinAddresses] = useState({});
const { isUsingGateway } = useContext(gameContext);
const handleChange = (event: ChangeEvent) => {
@@ -160,6 +166,7 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
useContext(gameContext);
const { setNotification } = useContext(NotificationContext);
+
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
padding: 8,
"& .MuiSwitch-track": {
@@ -231,6 +238,40 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
// }
// }, [userInfo]);
+ const sendCoin = async ()=> {
+ try {
+ const coin = 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 (
<>
{
- {
>
{info?.message}
+ )}
+
{openCoinActionModal && (
{
setOpenCoinActionModal(null);
+ setAmount('')
+ setSenderAddress('')
}}
backdrop
>
+ {openCoinActionModal.type === "send" ? <>
{openCoinActionModal.type === "send" &&
@@ -523,12 +570,12 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
style={{ flexGrow: 1 }}
name={
openCoinActionModal.type === "send"
- ? "Sender Address"
+ ? "Recipient Address"
: "Receive Address"
}
label={
openCoinActionModal.type === "send"
- ? "Sender Address"
+ ? "Recipient Address"
: "Receive Address"
}
variant="filled"
@@ -548,25 +595,171 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
/>
-
- setOpenCoinActionModal(null)}>
- Cancel
-
- {
- setNotification({
- alertType: "alertInfo",
- msg: "Sending...",
- });
- }}
- >
- {openCoinActionModal.type === "send" ? "Send" : "Receive"}
-
-
+ {openCoinActionModal.type === "send" && (
+
+
+ {
+ setAmount(e.target.value)
+ }}
+ />
+
+
+ )}
+ > : (
+ <>
+
+ >
+ )}
+ {openCoinActionModal.type === 'send' && (
+
+ setOpenCoinActionModal(null)}>
+ Cancel
+
+ {
+ if(openCoinActionModal.type === 'send'){
+ sendCoin()
+ }
+ setNotification({
+ alertType: "alertInfo",
+ msg: "Sending...",
+ });
+ }}
+ >
+ {openCoinActionModal.type === "send" ? "Send" : "Receive"}
+
+
+ )}
+
+
+
)}
+
>
);
};
+
+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 (
+
+ {`Send ${selectedCoin} to your address below`}
+
+ {foreignAddress && (
+ {
+ setOpen(true);
+ setInfo({
+ type: "info",
+ message: "Address copied!",
+ });
+ }}>
+
+ {foreignAddress}
+
+
+ )}
+ {foreignAddress && (
+ <>
+
+ >
+ )}
+ {errorMsg && (
+ <>
+
+ {errorMsg}
+ >
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/src/contexts/gameContext.ts b/src/contexts/gameContext.ts
index ab2d9dd..010b0cd 100644
--- a/src/contexts/gameContext.ts
+++ b/src/contexts/gameContext.ts
@@ -34,7 +34,7 @@ export interface IContextProps {
isUsingGateway: boolean;
selectedCoin: string;
setSelectedCoin: (val: any)=> void;
- getCoinLabel: ()=> void;
+ getCoinLabel: ()=> string | null;
}
const defaultState: IContextProps = {
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
index 69fdc30..0396f54 100644
--- a/src/pages/Home/Home.tsx
+++ b/src/pages/Home/Home.tsx
@@ -23,7 +23,7 @@ export const HomePage = () => {
selectedCoin,
} = useContext(gameContext);
const { setNotification } = useContext(NotificationContext);
- const [mode, setMode] = useState("history");
+ const [mode, setMode] = useState("buy");
const filteredOngoingTrades = useMemo(() => {
return onGoingTrades?.filter(
(item) => item?.tradeInfo?.foreignBlockchain === selectedCoin