Skip to content

Commit 705e807

Browse files
committed
Improve WalletConnect UI and add button to connect with Safe
1 parent d7fc41e commit 705e807

File tree

11 files changed

+320
-41
lines changed

11 files changed

+320
-41
lines changed

apps/evmcrispr-terminal/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@
2727
"ethereum-blockies-base64": "^1.0.0",
2828
"events": "^3.3.0",
2929
"framer-motion": "^11.2.10",
30+
"is-ipfs": "^6.0.2",
3031
"isomorphic-fetch": "^3.0.0",
3132
"lodash.debounce": "^4.0.8",
3233
"monaco-editor": "^0.49.0",
33-
"is-ipfs": "^6.0.2",
3434
"process": "^0.11.10",
35+
"qrcode.react": "^4.0.1",
3536
"react": "^18.3.1",
3637
"react-dom": "^18.3.1",
3738
"react-is": "^18.3.0",
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Button, Flex, useClipboard } from "@chakra-ui/react";
2+
3+
export default function CopyCode({ code }: { code: string }) {
4+
const { onCopy, hasCopied } = useClipboard(code);
5+
6+
return (
7+
<Flex mb={2}>
8+
<Button
9+
colorScheme="green"
10+
variant="outline-overlay"
11+
size="sm"
12+
onClick={onCopy}
13+
>
14+
{hasCopied ? "Copied!" : "Copy"}
15+
</Button>
16+
</Flex>
17+
);
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Button, HStack, Input, Text } from "@chakra-ui/react";
2+
import { useState } from "react";
3+
4+
import { useTerminalStore } from "../../components/TerminalEditor/use-terminal-store";
5+
6+
export default function SafeConnect({ onConnect }: { onConnect: () => void }) {
7+
const [safeAddress, setSafeAddress] = useState("");
8+
const [attemptedConnect, setAttemptedConnect] = useState(false);
9+
const [chain, address] = safeAddress.split(":");
10+
const isValidSafeAddress =
11+
/\w+/g.test(chain) &&
12+
((address?.startsWith("0x") && address?.length === 42) ||
13+
address?.endsWith(".eth"));
14+
const error =
15+
attemptedConnect && !isValidSafeAddress
16+
? "Invalid Safe address. Example: 'gno:0x1234...5678' or 'eth:my-account.eth'"
17+
: null;
18+
19+
const { title, script } = useTerminalStore();
20+
21+
const url =
22+
`https://app.safe.global/apps/open?safe=${safeAddress}&appUrl=` +
23+
encodeURIComponent(
24+
`https://evmcrispr.com/#/terminal?title=${encodeURIComponent(title)}&script=${encodeURIComponent(script)}`,
25+
);
26+
return (
27+
<>
28+
<Input
29+
border={"1px solid"}
30+
borderColor={"green.300"}
31+
color={"white"}
32+
p={2.5}
33+
borderRadius={"none"}
34+
fontSize={"xl"}
35+
_placeholder={{
36+
color: "white",
37+
opacity: 1,
38+
}}
39+
_hover={{
40+
borderColor: "green.300",
41+
}}
42+
_focusVisible={{
43+
borderColor: "green.300",
44+
boxShadow: "none",
45+
}}
46+
autoFocus
47+
placeholder="Enter your Safe Address"
48+
value={safeAddress}
49+
onChange={(e) => {
50+
setSafeAddress(e.target.value);
51+
setAttemptedConnect(false);
52+
}}
53+
/>
54+
<HStack justify={"center"} mt={4} mb={2}>
55+
<Button
56+
variant="overlay"
57+
size="sm"
58+
colorScheme="green"
59+
onClick={() => {
60+
setAttemptedConnect(true);
61+
if (isValidSafeAddress) {
62+
window.open(url, "_blank");
63+
onConnect();
64+
}
65+
}}
66+
>
67+
Connect Safe
68+
</Button>
69+
</HStack>
70+
{error && (
71+
<Text textAlign={"center"} color="red">
72+
{error}
73+
</Text>
74+
)}
75+
</>
76+
);
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Button } from "@chakra-ui/react";
2+
import type { Connector } from "wagmi";
3+
import { useConnect } from "wagmi";
4+
5+
export default function WalletButton({
6+
name,
7+
leftIcon,
8+
onClick,
9+
}: {
10+
name: string;
11+
connector?: Connector;
12+
leftIcon: React.ReactElement;
13+
onClick: () => void;
14+
}) {
15+
const { isPending } = useConnect();
16+
return (
17+
<Button
18+
isLoading={isPending}
19+
disabled={isPending}
20+
onClick={onClick}
21+
variant="outline-overlay"
22+
size="lg"
23+
leftIcon={leftIcon}
24+
w={"100%"}
25+
>
26+
{name}
27+
</Button>
28+
);
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useTheme } from "@chakra-ui/react";
2+
import { QRCodeSVG } from "qrcode.react";
3+
4+
import type { Connector } from "wagmi";
5+
6+
import CopyCode from "./CopyCode";
7+
import { useWalletConnect } from "../../hooks/useWalletConnect";
8+
9+
export default function WalletConnectCode({
10+
walletConnectConnector,
11+
onConnect,
12+
}: {
13+
walletConnectConnector: Connector;
14+
onConnect: () => void;
15+
}) {
16+
const { green } = useTheme().colors;
17+
const { wcUri } = useWalletConnect({
18+
walletConnectConnector,
19+
onConnect,
20+
});
21+
22+
return wcUri ? (
23+
<>
24+
<QRCodeSVG
25+
value={wcUri}
26+
size={300}
27+
bgColor="black"
28+
fgColor={green[300]}
29+
marginSize={8}
30+
level="H"
31+
imageSettings={{
32+
src: "/walletconnect-logo.svg",
33+
height: 48,
34+
width: 48,
35+
excavate: true,
36+
}}
37+
/>
38+
<CopyCode code={wcUri} />
39+
</>
40+
) : null;
41+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { useState } from "react";
12
import {
2-
Button,
33
Modal,
44
ModalBody,
55
ModalCloseButton,
@@ -8,10 +8,15 @@ import {
88
ModalOverlay,
99
VStack,
1010
} from "@chakra-ui/react";
11+
import type { Connector } from "wagmi";
1112
import { useConnect } from "wagmi";
1213

14+
import WalletButton from "./WalletButton";
15+
import WalletConnectCode from "./WalletConnectCode";
16+
import SafeConnect from "./SafeConnect";
1317
import MetamaskIcon from "../icons/MetamaskIcon";
1418
import WalletIcon from "../icons/WalletIcon";
19+
import SafeIcon from "../icons/SafeIcon";
1520

1621
export default function SelectWalletModal({
1722
isOpen,
@@ -20,59 +25,108 @@ export default function SelectWalletModal({
2025
isOpen: boolean;
2126
onClose: () => void;
2227
}) {
23-
const { connectors, isPending, connect } = useConnect();
28+
const [selectedWallet, setSelectedWallet] = useState<string | null>(null);
29+
const { connectors } = useConnect();
2430
const walletConnectConnector = connectors.find(
2531
(c) => c.id === "walletConnect",
2632
);
2733

34+
const handleModalClose = () => {
35+
onClose();
36+
setSelectedWallet(null);
37+
};
38+
39+
const renderModalContent = () => {
40+
if (selectedWallet === "walletConnect" && walletConnectConnector) {
41+
return (
42+
<WalletConnectCode
43+
walletConnectConnector={walletConnectConnector}
44+
onConnect={handleModalClose}
45+
/>
46+
);
47+
}
48+
49+
if (selectedWallet === "safe") {
50+
return <SafeConnect onConnect={handleModalClose} />;
51+
}
52+
53+
return (
54+
<WalletList
55+
connectors={connectors}
56+
handleModalClose={handleModalClose}
57+
setSelectedWallet={setSelectedWallet}
58+
/>
59+
);
60+
};
61+
62+
const getModalTitle = () => {
63+
switch (selectedWallet) {
64+
case "walletConnect":
65+
return "Scan with WalletConnect";
66+
case "safe":
67+
return "Connect to a Safe";
68+
default:
69+
return "Select Wallet";
70+
}
71+
};
72+
2873
return (
2974
<Modal
3075
isOpen={isOpen}
31-
onClose={onClose}
76+
onClose={handleModalClose}
3277
isCentered
33-
colorScheme={"yellow"}
78+
colorScheme="yellow"
3479
size={"md"}
3580
>
3681
<ModalOverlay />
3782
<ModalContent>
38-
<ModalHeader>Select Wallet</ModalHeader>
83+
<ModalHeader>{getModalTitle()}</ModalHeader>
3984
<ModalCloseButton />
40-
<ModalBody>
41-
<VStack spacing={7} w={"300px"}>
42-
<Button
43-
isLoading={isPending}
44-
disabled={isPending}
45-
variant="outline-overlay"
46-
onClick={() => {
47-
connect(
48-
{ connector: connectors[0] },
49-
{ onSuccess: () => onClose(), onError: () => onClose() },
50-
);
51-
}}
52-
size="lg"
53-
leftIcon={<MetamaskIcon />}
54-
w={"100%"}
55-
>
56-
Metamask
57-
</Button>
58-
{walletConnectConnector && (
59-
<Button
60-
disabled={isPending}
61-
variant="outline-overlay"
62-
onClick={() => {
63-
connect({ connector: walletConnectConnector });
64-
onClose();
65-
}}
66-
size="lg"
67-
leftIcon={<WalletIcon />}
68-
w={"100%"}
69-
>
70-
Wallet Connect
71-
</Button>
72-
)}
73-
</VStack>
74-
</ModalBody>
85+
<ModalBody>{renderModalContent()}</ModalBody>
7586
</ModalContent>
7687
</Modal>
7788
);
7889
}
90+
91+
function WalletList({
92+
connectors,
93+
handleModalClose,
94+
setSelectedWallet,
95+
}: {
96+
connectors: readonly Connector[];
97+
handleModalClose: () => void;
98+
setSelectedWallet: (wallet: string) => void;
99+
}) {
100+
const { connect } = useConnect();
101+
const walletConnectConnector = connectors.find(
102+
(c) => c.id === "walletConnect",
103+
);
104+
105+
return (
106+
<VStack spacing={7} w={"300px"}>
107+
<WalletButton
108+
name="Metamask"
109+
connector={connectors[0]}
110+
leftIcon={<MetamaskIcon />}
111+
onClick={() => {
112+
connect(
113+
{ connector: connectors[0] },
114+
{ onSuccess: handleModalClose },
115+
);
116+
}}
117+
/>
118+
{walletConnectConnector && (
119+
<WalletButton
120+
name="WalletConnect"
121+
leftIcon={<WalletIcon />}
122+
onClick={() => setSelectedWallet("walletConnect")}
123+
/>
124+
)}
125+
<WalletButton
126+
name="Safe"
127+
leftIcon={<SafeIcon />}
128+
onClick={() => setSelectedWallet("safe")}
129+
/>
130+
</VStack>
131+
);
132+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createIcon } from "@chakra-ui/icons";
2+
3+
const SafeIcon = createIcon({
4+
displayName: "SafeIcon",
5+
viewBox: "0 0 664 664",
6+
path: [
7+
<path
8+
fill="#12ff80"
9+
d="M 132.3849,660.96706 C 66.262502,652.46608 12.251489,597.13988 3.4515858,528.89418 c -4.17694774,-32.39339 -4.17694774,-360.77963 0,-393.17302 C 12.468148,65.795222 65.795222,12.468148 135.72116,3.4515858 c 32.39339,-4.17694774 360.77963,-4.17694774 393.17302,0 69.92596,9.0165622 123.25303,62.3436362 132.26961,132.2695742 4.17692,32.39339 4.17692,360.77963 0,393.17302 -9.01658,69.92596 -62.34365,123.25303 -132.26961,132.26961 -29.64162,3.82209 -366.55008,3.65498 -396.50928,-0.1968 z"
10+
/>,
11+
<path
12+
fill="#121312"
13+
d="m 215.83216,552.16779 c -8.2699,-8.26988 -9.06294,-11.94959 -9.06294,-42.05246 0,-53.53337 -8.02676,-50.20505 124.21858,-51.50751 137.05848,-1.34987 126.85834,3.67522 126.85834,-62.49622 0,-60.70611 2.50977,-63.80392 51.6923,-63.80392 49.83194,0 51.69231,2.45834 51.69231,68.30769 0,64.65884 -2.5207,68.30769 -47.18854,68.30769 -39.69559,0 -45.11915,5.48573 -45.11915,45.63659 0,48.44455 4.98077,46.6711 -131.07692,46.6711 -112.95104,0 -112.95104,0 -122.01398,-9.06296 z m 80.42182,-171.99503 c -18.70325,-7.51396 -22.58823,-78.31822 -5.07537,-92.49927 14.38304,-11.64668 71.47345,-10.28017 83.75842,2.00482 13.68731,13.68731 13.68731,71.57143 0,85.25872 -9.74944,9.74947 -59.249,13.04326 -78.68305,5.23573 z M 112.44755,323.24474 c -8.73326,-8.73326 -9.06294,-10.88837 -9.06294,-59.24475 0,-64.76605 2.46613,-68.30769 47.56418,-68.30769 38.84232,0 44.74351,-6.175 44.74351,-46.81962 0,-47.06548 -4.5454,-45.48807 131.07692,-45.48807 137.69464,0 131.07692,-2.6098 131.07692,51.69231 0,54.13149 5.97256,51.6923 -126.57316,51.6923 -134.32122,0 -124.50376,-4.90874 -124.50376,62.25196 0,60.11584 -2.58979,63.2865 -51.6923,63.2865 -30.71536,0 -34.33623,-0.76979 -42.62937,-9.06294 z"
14+
/>,
15+
],
16+
});
17+
18+
export default SafeIcon;

0 commit comments

Comments
 (0)