feat: move to nextjs

This commit is contained in:
apoorvlathey
2024-05-07 00:18:18 +10:00
parent 602ae4389e
commit 032785a316
49 changed files with 9867 additions and 8425 deletions

View File

@@ -0,0 +1,29 @@
import { FormLabel, Tooltip, Text, Box } from "@chakra-ui/react";
import { InfoIcon } from "@chakra-ui/icons";
function AppUrlLabel() {
return (
<>
<FormLabel>dapp URL</FormLabel>
<Tooltip
label={
<>
<Text>Paste the URL of dapp you want to connect to</Text>
<Text>
Note: Some dapps might not support it, so use WalletConnect in
that case
</Text>
</>
}
hasArrow
placement="top"
>
<Box pb="0.8rem">
<InfoIcon />
</Box>
</Tooltip>
</>
);
}
export default AppUrlLabel;

View File

@@ -0,0 +1,59 @@
import {
HStack,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Text,
Input,
} from "@chakra-ui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faShareAlt } from "@fortawesome/free-solid-svg-icons";
import CopyToClipboard from "../CopyToClipboard";
interface ShareModalParams {
isOpen: boolean;
onClose: () => void;
appUrl: string;
showAddress: string;
}
function ShareModal({
isOpen,
onClose,
appUrl,
showAddress,
}: ShareModalParams) {
const urlToShare = `https://impersonator.xyz/?address=${showAddress}&url=${encodeURI(
appUrl
)}`;
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay bg="none" backdropFilter="auto" backdropBlur="5px" />
<ModalContent>
<ModalHeader>
<HStack>
<FontAwesomeIcon icon={faShareAlt} />
<Text>Share</Text>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text>
Share this link so that anyone can auto-connect to this dapp with
your provided address!
</Text>
<HStack my="3">
<Input value={urlToShare} isReadOnly />
<CopyToClipboard txt={urlToShare} />
</HStack>
</ModalBody>
</ModalContent>
</Modal>
);
}
export default ShareModal;

View File

@@ -0,0 +1,45 @@
import { GridItem, Center, Image, Text } from "@chakra-ui/react";
import { SafeDappInfo } from "../../../../types";
interface DappTileParams {
initIFrame: (_inputAppUrl?: string | undefined) => Promise<void>;
setInputAppUrl: (value: string | undefined) => void;
closeSafeApps: () => void;
dapp: SafeDappInfo;
}
function DappTile({
initIFrame,
setInputAppUrl,
closeSafeApps,
dapp,
}: DappTileParams) {
return (
<GridItem
border="2px solid"
borderColor={"gray.500"}
bg={"white"}
color={"black"}
_hover={{
cursor: "pointer",
bgColor: "gray.600",
color: "white",
}}
rounded="lg"
onClick={() => {
initIFrame(dapp.url);
setInputAppUrl(dapp.url);
closeSafeApps();
}}
>
<Center flexDir={"column"} h="100%" p="1rem">
<Image bg="white" w="2rem" src={dapp.iconUrl} borderRadius="full" />
<Text mt="0.5rem" textAlign={"center"}>
{dapp.name}
</Text>
</Center>
</GridItem>
);
}
export default DappTile;

View File

@@ -0,0 +1,40 @@
import {
Center,
InputGroup,
Input,
InputRightElement,
Button,
} from "@chakra-ui/react";
import { CloseIcon } from "@chakra-ui/icons";
interface DappsSearchParams {
searchSafeDapp: string | undefined;
setSearchSafeDapp: (value: string) => void;
}
function DappsSearch({ searchSafeDapp, setSearchSafeDapp }: DappsSearchParams) {
return (
<Center pb="0.5rem">
<InputGroup maxW="30rem">
<Input
placeholder="search 🔎"
value={searchSafeDapp}
onChange={(e) => setSearchSafeDapp(e.target.value)}
/>
{searchSafeDapp && (
<InputRightElement width="3rem">
<Button
size="xs"
variant={"ghost"}
onClick={() => setSearchSafeDapp("")}
>
<CloseIcon />
</Button>
</InputRightElement>
)}
</InputGroup>
</Center>
);
}
export default DappsSearch;

View File

@@ -0,0 +1,147 @@
import { useState, useEffect } from "react";
import {
Box,
Button,
Center,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
SimpleGrid,
Spinner,
useDisclosure,
} from "@chakra-ui/react";
import axios from "axios";
import DappsSearch from "./DappsSearch";
import DappTile from "./DappTile";
import { SafeDappInfo } from "../../../../types";
interface SupportedDappsParams {
networkId: number;
initIFrame: (_inputAppUrl?: string | undefined) => Promise<void>;
setInputAppUrl: (value: string | undefined) => void;
}
function SupportedDapps({
networkId,
initIFrame,
setInputAppUrl,
}: SupportedDappsParams) {
const {
isOpen: isSafeAppsOpen,
onOpen: openSafeAapps,
onClose: closeSafeApps,
} = useDisclosure();
const [safeDapps, setSafeDapps] = useState<{
[networkId: number]: SafeDappInfo[];
}>({});
const [searchSafeDapp, setSearchSafeDapp] = useState<string>();
const [filteredSafeDapps, setFilteredSafeDapps] = useState<SafeDappInfo[]>();
useEffect(() => {
const fetchSafeDapps = async (networkId: number) => {
const response = await axios.get<SafeDappInfo[]>(
`https://safe-client.safe.global/v1/chains/${networkId}/safe-apps`
);
setSafeDapps((dapps) => ({
...dapps,
[networkId]: response.data.filter((d) => ![29, 11].includes(d.id)), // Filter out Transaction Builder and WalletConnect
}));
};
if (isSafeAppsOpen && !safeDapps[networkId]) {
fetchSafeDapps(networkId);
}
}, [isSafeAppsOpen, safeDapps, networkId]);
useEffect(() => {
if (safeDapps[networkId]) {
setFilteredSafeDapps(
safeDapps[networkId].filter((dapp) => {
if (!searchSafeDapp) return true;
return (
dapp.name
.toLowerCase()
.indexOf(searchSafeDapp.toLocaleLowerCase()) !== -1 ||
dapp.url
.toLowerCase()
.indexOf(searchSafeDapp.toLocaleLowerCase()) !== -1
);
})
);
} else {
setFilteredSafeDapps(undefined);
}
}, [safeDapps, networkId, searchSafeDapp]);
return (
<>
<Box pb="0.5rem">
<Button size="sm" onClick={openSafeAapps}>
Supported dapps
</Button>
</Box>
<Modal isOpen={isSafeAppsOpen} onClose={closeSafeApps} isCentered>
<ModalOverlay bg="none" backdropFilter="auto" backdropBlur="3px" />
<ModalContent
minW={{
base: 0,
sm: "30rem",
md: "40rem",
lg: "60rem",
}}
bg={"brand.lightBlack"}
>
<ModalHeader>Select a dapp</ModalHeader>
<ModalCloseButton />
<ModalBody>
{(!safeDapps || !safeDapps[networkId]) && (
<Center py="3rem" w="100%">
<Spinner />
</Center>
)}
<Box pb="2rem" px={{ base: 0, md: "2rem" }}>
{safeDapps && safeDapps[networkId] && (
<DappsSearch
searchSafeDapp={searchSafeDapp}
setSearchSafeDapp={setSearchSafeDapp}
/>
)}
<Box
minH="30rem"
maxH="30rem"
overflow="scroll"
overflowX="auto"
overflowY="auto"
>
<SimpleGrid
pt="1rem"
columns={{ base: 2, md: 3, lg: 4 }}
gap={6}
>
{filteredSafeDapps &&
filteredSafeDapps.map((dapp, i) => (
<DappTile
key={i}
initIFrame={initIFrame}
setInputAppUrl={setInputAppUrl}
closeSafeApps={closeSafeApps}
dapp={dapp}
/>
))}
</SimpleGrid>
</Box>
</Box>
</ModalBody>
</ModalContent>
</Modal>
</>
);
}
export default SupportedDapps;

View File

@@ -0,0 +1,140 @@
import {
Button,
Box,
Center,
Spacer,
HStack,
FormControl,
Input,
Text,
useDisclosure,
InputGroup,
InputRightElement,
} from "@chakra-ui/react";
import { DeleteIcon } from "@chakra-ui/icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faShareAlt } from "@fortawesome/free-solid-svg-icons";
import SupportedDapps from "./SupportedDapps";
import AppUrlLabel from "./AppUrlLabel";
import ShareModal from "./ShareModal";
interface IFrameConnectTabParams {
networkId: number;
initIFrame: (_inputAppUrl?: string | undefined) => Promise<void>;
inputAppUrl: string | undefined;
setInputAppUrl: (value: string | undefined) => void;
appUrl: string | undefined;
isIFrameLoading: boolean;
setIsIFrameLoading: (value: boolean) => void;
iframeKey: number;
iframeRef: React.RefObject<HTMLIFrameElement> | null;
showAddress: string;
}
function IFrameConnectTab({
networkId,
initIFrame,
setInputAppUrl,
inputAppUrl,
isIFrameLoading,
appUrl,
iframeKey,
iframeRef,
setIsIFrameLoading,
showAddress,
}: IFrameConnectTabParams) {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<FormControl my={4}>
<HStack>
<AppUrlLabel />
<Spacer />
<SupportedDapps
networkId={networkId}
initIFrame={initIFrame}
setInputAppUrl={setInputAppUrl}
/>
</HStack>
<HStack mt="2">
<InputGroup>
<Input
pr="3.5rem"
bg={"brand.lightBlack"}
placeholder="https://app.uniswap.org/"
aria-label="dapp-url"
autoComplete="off"
value={inputAppUrl}
onChange={(e) => setInputAppUrl(e.target.value)}
/>
{inputAppUrl && (
<InputRightElement px="1rem" mr="0.5rem">
<Button
h="1.75rem"
size="sm"
onClick={() => {
setInputAppUrl("");
}}
>
<DeleteIcon />
</Button>
</InputRightElement>
)}
</InputGroup>
{appUrl && (
<>
<Button onClick={onOpen}>
<HStack>
<FontAwesomeIcon icon={faShareAlt} />
<Text>Share</Text>
</HStack>
</Button>
<ShareModal
isOpen={isOpen}
onClose={onClose}
appUrl={appUrl}
showAddress={showAddress}
/>
</>
)}
</HStack>
</FormControl>
<Center>
<Button onClick={() => initIFrame()} isLoading={isIFrameLoading}>
Connect
</Button>
</Center>
<Center
mt="1rem"
ml={{ base: "-385", sm: "-315", md: "-240", lg: "-60" }}
px={{ base: "10rem", lg: 0 }}
w="70rem"
>
{appUrl && (
<Box
as="iframe"
w={{
base: "22rem",
sm: "45rem",
md: "55rem",
lg: "1500rem",
}}
h={{ base: "33rem", md: "35rem", lg: "38rem" }}
title="app"
src={appUrl}
key={iframeKey}
borderWidth="1px"
borderStyle={"solid"}
borderColor="white"
bg="white"
ref={iframeRef}
onLoad={() => setIsIFrameLoading(false)}
/>
)}
</Center>
</>
);
}
export default IFrameConnectTab;