mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-28 11:46:11 -04:00
feat(gui): Button to open modal with QR code of Bitcoin address (#116)
This commit is contained in:
parent
2bffe40a37
commit
15b43bf4a4
@ -216,7 +216,7 @@ export default function SwapStatusAlert({
|
|||||||
<Alert
|
<Alert
|
||||||
key={swap.swap_id}
|
key={swap.swap_id}
|
||||||
severity="warning"
|
severity="warning"
|
||||||
action={<SwapResumeButton swap={swap} />}
|
action={<SwapResumeButton swap={swap}>Resume Swap</SwapResumeButton>}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Box } from "@material-ui/core";
|
import { Box } from "@material-ui/core";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import CopyableMonospaceTextBox from "renderer/components/other/CopyableMonospaceTextBox";
|
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
|
||||||
import BitcoinQrCode from "./BitcoinQrCode";
|
|
||||||
import InfoBox from "./InfoBox";
|
import InfoBox from "./InfoBox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -20,7 +19,7 @@ export default function DepositAddressInfoBox({
|
|||||||
return (
|
return (
|
||||||
<InfoBox
|
<InfoBox
|
||||||
title={title}
|
title={title}
|
||||||
mainContent={<CopyableMonospaceTextBox address={address} />}
|
mainContent={<ActionableMonospaceTextBox content={address} displayCopyIcon={true} enableQrCode={true} />}
|
||||||
additionalContent={
|
additionalContent={
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@ -31,7 +30,6 @@ export default function DepositAddressInfoBox({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>{additionalContent}</Box>
|
<Box>{additionalContent}</Box>
|
||||||
<BitcoinQrCode address={address} />
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
import { Box, Button, IconButton, Tooltip, makeStyles } from "@material-ui/core";
|
||||||
|
import { FileCopyOutlined, CropFree as CropFreeIcon } from "@material-ui/icons";
|
||||||
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
|
import { useState } from "react";
|
||||||
|
import MonospaceTextBox from "./MonospaceTextBox";
|
||||||
|
import { Modal } from "@material-ui/core";
|
||||||
|
import QRCode from "react-qr-code";
|
||||||
|
|
||||||
|
type ModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
content: string;
|
||||||
|
displayCopyIcon?: boolean;
|
||||||
|
enableQrCode?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
container: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
},
|
||||||
|
textBoxWrapper: {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
iconButton: {
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
},
|
||||||
|
modalContent: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
qrCode: {
|
||||||
|
maxWidth: "90%",
|
||||||
|
maxHeight: "90%",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
function QRCodeModal({ open, onClose, content }: ModalProps) {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box className={classes.modalContent}>
|
||||||
|
<QRCode
|
||||||
|
value={content}
|
||||||
|
size={500}
|
||||||
|
className={classes.qrCode}
|
||||||
|
viewBox="0 0 500 500"
|
||||||
|
/>
|
||||||
|
<Button onClick={onClose} size="large" variant="contained" color="primary">
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ActionableMonospaceTextBox({
|
||||||
|
content,
|
||||||
|
displayCopyIcon = true,
|
||||||
|
enableQrCode = true,
|
||||||
|
}: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const [qrCodeOpen, setQrCodeOpen] = useState(false);
|
||||||
|
const [isQrCodeButtonHovered, setIsQrCodeButtonHovered] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
await writeText(content);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title={isQrCodeButtonHovered ? "" : (copied ? "Copied to clipboard" : "Click to copy")} arrow>
|
||||||
|
<Box className={classes.container}>
|
||||||
|
<Box className={classes.textBoxWrapper} onClick={handleCopy}>
|
||||||
|
<MonospaceTextBox>
|
||||||
|
{content}
|
||||||
|
{displayCopyIcon && (
|
||||||
|
<IconButton onClick={handleCopy} size="small" className={classes.iconButton}>
|
||||||
|
<FileCopyOutlined />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
{enableQrCode && (
|
||||||
|
<Tooltip title="Show QR Code" arrow>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setQrCodeOpen(true)}
|
||||||
|
onMouseEnter={() => setIsQrCodeButtonHovered(true)}
|
||||||
|
onMouseLeave={() => setIsQrCodeButtonHovered(false)}
|
||||||
|
size="small"
|
||||||
|
className={classes.iconButton}
|
||||||
|
>
|
||||||
|
<CropFreeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</MonospaceTextBox>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
{enableQrCode && (
|
||||||
|
<QRCodeModal
|
||||||
|
open={qrCodeOpen}
|
||||||
|
onClose={() => setQrCodeOpen(false)}
|
||||||
|
content={content}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
import { Box, Tooltip } from "@material-ui/core";
|
|
||||||
import { FileCopyOutlined } from "@material-ui/icons";
|
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
|
||||||
import { useState } from "react";
|
|
||||||
import MonospaceTextBox from "./MonospaceTextBox";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
address: string;
|
|
||||||
noIcon?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Display addresses monospaced and clickable such that a click copies the address to the clipboard. */
|
|
||||||
export default function CopyableMonospaceTextBox({
|
|
||||||
address,
|
|
||||||
noIcon = false,
|
|
||||||
}: Props) {
|
|
||||||
// Signal that the address was copied
|
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
const tooltip = copied ? "Copied to clipboard" : "Click to copy";
|
|
||||||
|
|
||||||
// Copy address to clipboard on-click
|
|
||||||
const handleClick = async () => {
|
|
||||||
// Copy to clipboard
|
|
||||||
await writeText(address);
|
|
||||||
// Change tooltip to show that we copied the address
|
|
||||||
setCopied(true);
|
|
||||||
// After a delay, show default tooltip again (2sec)
|
|
||||||
setTimeout(() => setCopied(false), 2_000);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply icon unless specified otherwise
|
|
||||||
const icon = noIcon ? null : <FileCopyOutlined />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip title={tooltip} arrow>
|
|
||||||
{/* Div is necessary to make the tooltip work */}
|
|
||||||
<Box style={{ cursor: "pointer" }}>
|
|
||||||
<MonospaceTextBox
|
|
||||||
content={address}
|
|
||||||
endIcon={icon}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,10 +1,7 @@
|
|||||||
import { Box, Typography, makeStyles } from "@material-ui/core";
|
import { Box, Typography, makeStyles } from "@material-ui/core";
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content: string;
|
children: React.ReactNode;
|
||||||
onClick?: (content: string) => void;
|
|
||||||
endIcon?: ReactNode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
@ -14,31 +11,25 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
backgroundColor: theme.palette.grey[900],
|
backgroundColor: theme.palette.grey[900],
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
gap: theme.spacing(1),
|
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
whiteSpace: "pre-wrap",
|
whiteSpace: "pre-wrap",
|
||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
lineHeight: "1.5em",
|
lineHeight: 1.5,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function MonospaceTextBox({ content, endIcon, onClick }: Props) {
|
export default function MonospaceTextBox({ children }: Props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const handleClick = () => onClick?.(content);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.root} onClick={handleClick}>
|
<Box className={classes.root}>
|
||||||
<Typography
|
<Typography component="span" variant="overline" className={classes.content}>
|
||||||
component="span"
|
{children}
|
||||||
variant="overline"
|
|
||||||
className={classes.content}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
{endIcon}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { OpenInNew } from "@material-ui/icons";
|
import { OpenInNew } from "@material-ui/icons";
|
||||||
import { GetSwapInfoResponse } from "models/tauriModel";
|
import { GetSwapInfoResponse } from "models/tauriModel";
|
||||||
import CopyableMonospaceTextBox from "renderer/components/other/CopyableMonospaceTextBox";
|
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
|
||||||
import MonospaceTextBox from "renderer/components/other/MonospaceTextBox";
|
import MonospaceTextBox from "renderer/components/other/MonospaceTextBox";
|
||||||
import {
|
import {
|
||||||
MoneroBitcoinExchangeRate,
|
MoneroBitcoinExchangeRate,
|
||||||
@ -90,7 +90,7 @@ export default function HistoryRowExpanded({
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<Box>
|
<Box>
|
||||||
{swap.seller.addresses.map((addr) => (
|
{swap.seller.addresses.map((addr) => (
|
||||||
<CopyableMonospaceTextBox key={addr} address={addr} />
|
<ActionableMonospaceTextBox key={addr} content={addr} displayCopyIcon={false} enableQrCode={false} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user