mirror of
https://github.com/Qortal/q-trade.git
synced 2025-06-15 02:41:21 +00:00
send and receiving foreign coins
This commit is contained in:
parent
3de91a32d3
commit
1350ef44c6
44
package-lock.json
generated
44
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -256,7 +256,7 @@ function App() {
|
||||
};
|
||||
}, [userInfo?.address]);
|
||||
|
||||
const getCoinLabel = (coin?: string)=> {
|
||||
const getCoinLabel = (coin?: string)=> {
|
||||
switch(coin || selectedCoin){
|
||||
case "LITECOIN":{
|
||||
|
||||
|
3
src/assets/SVG/Copy.svg
Normal file
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 |
50
src/components/header/AddressQRCode.tsx
Normal file
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>
|
||||
);
|
||||
};
|
@ -216,6 +216,7 @@ export const CoinSelectRow = styled(Box)({
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
alignSelf: "flex-start",
|
||||
marginBottom: '5px'
|
||||
});
|
||||
|
||||
export const CoinActionContainer = styled(Box)({
|
||||
|
@ -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<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 handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
@ -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 (
|
||||
<>
|
||||
<AppBar
|
||||
@ -444,10 +485,11 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
autoHideDuration={info?.autoHideDurationOff ? null : 6000}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert
|
||||
{info?.type && (
|
||||
<Alert
|
||||
onClose={handleClose}
|
||||
severity={info?.type}
|
||||
variant="filled"
|
||||
@ -455,15 +497,20 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
|
||||
>
|
||||
{info?.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
</Snackbar>
|
||||
{openCoinActionModal && (
|
||||
<ReusableModal
|
||||
onClickClose={() => {
|
||||
setOpenCoinActionModal(null);
|
||||
setAmount('')
|
||||
setSenderAddress('')
|
||||
}}
|
||||
backdrop
|
||||
>
|
||||
<CoinActionContainer>
|
||||
{openCoinActionModal.type === "send" ? <>
|
||||
<CoinActionRow>
|
||||
<HeaderRow>
|
||||
{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) => {
|
||||
/>
|
||||
</FormControl>
|
||||
</CoinActionRow>
|
||||
<CoinActionRow style={{gap: "10px"}}>
|
||||
<CoinCancelBtn onClick={() => setOpenCoinActionModal(null)}>
|
||||
Cancel
|
||||
</CoinCancelBtn>
|
||||
<CoinConfirmSendBtn
|
||||
onClick={() => {
|
||||
setNotification({
|
||||
alertType: "alertInfo",
|
||||
msg: "Sending...",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{openCoinActionModal.type === "send" ? "Send" : "Receive"}
|
||||
</CoinConfirmSendBtn>
|
||||
</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={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>
|
||||
)
|
||||
}
|
@ -34,7 +34,7 @@ export interface IContextProps {
|
||||
isUsingGateway: boolean;
|
||||
selectedCoin: string;
|
||||
setSelectedCoin: (val: any)=> void;
|
||||
getCoinLabel: ()=> void;
|
||||
getCoinLabel: ()=> string | null;
|
||||
}
|
||||
|
||||
const defaultState: IContextProps = {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user