diff --git a/src/components/Body/index.tsx b/src/components/Body/index.tsx index 3301003..8b32cfa 100644 --- a/src/components/Body/index.tsx +++ b/src/components/Body/index.tsx @@ -35,6 +35,16 @@ import { Td, Heading, Collapse, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + Grid, + GridItem, + Image, + Spinner, } from "@chakra-ui/react"; import { SettingsIcon, @@ -43,6 +53,7 @@ import { ChevronUpIcon, CopyIcon, DeleteIcon, + CloseIcon, } from "@chakra-ui/icons"; import WalletConnect from "@walletconnect/client"; import { IClientMeta } from "@walletconnect/types"; @@ -52,6 +63,13 @@ import { useSafeInject } from "../../contexts/SafeInjectContext"; import Tab from "./Tab"; import networkInfo from "./networkInfo"; +interface SafeDappInfo { + id: number; + url: string; + name: string; + iconUrl: string; +} + const slicedText = (txt: string) => { return txt.length > 6 ? `${txt.slice(0, 4)}...${txt.slice(txt.length - 2, txt.length)}` @@ -89,6 +107,11 @@ function Body() { const toast = useToast(); const { onOpen, onClose, isOpen } = useDisclosure(); const { isOpen: tableIsOpen, onToggle: tableOnToggle } = useDisclosure(); + const { + isOpen: isSafeAppsOpen, + onOpen: openSafeAapps, + onClose: closeSafeApps, + } = useDisclosure(); const { setAddress: setIFrameAddress, @@ -112,6 +135,11 @@ function Body() { const [selectedTabIndex, setSelectedTabIndex] = useState(0); const [isIFrameLoading, setIsIFrameLoading] = useState(false); + const [safeDapps, setSafeDapps] = useState<{ + [networkIndex: number]: SafeDappInfo[]; + }>({}); + const [searchSafeDapp, setSearchSafeDapp] = useState(); + const [filteredSafeDapps, setFilteredSafeDapps] = useState(); const [inputAppUrl, setInputAppUrl] = useState(); const [iframeKey, setIframeKey] = useState(0); // hacky way to reload iframe when key changes @@ -227,6 +255,43 @@ function Body() { } }, [latestTransaction, tenderlyForkId]); + useEffect(() => { + const fetchSafeDapps = async (networkIndex: number) => { + const response = await axios.get( + `https://safe-client.gnosis.io/v1/chains/${networkInfo[networkIndex].chainID}/safe-apps` + ); + setSafeDapps((dapps) => ({ + ...dapps, + [networkIndex]: response.data.filter((d) => ![29, 11].includes(d.id)), // Filter out Transaction Builder and WalletConnect + })); + }; + + if (isSafeAppsOpen && !safeDapps[networkIndex]) { + fetchSafeDapps(networkIndex); + } + }, [isSafeAppsOpen, safeDapps, networkIndex]); + + useEffect(() => { + if (safeDapps[networkIndex]) { + setFilteredSafeDapps( + safeDapps[networkIndex].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, networkIndex, searchSafeDapp]); + const resolveAndValidateAddress = async () => { let isValid; let _address = address; @@ -311,9 +376,9 @@ function Body() { } }; - const initIFrame = async () => { + const initIFrame = async (_inputAppUrl = inputAppUrl) => { setIsIFrameLoading(true); - if (inputAppUrl === appUrl) { + if (_inputAppUrl === appUrl) { setIsIFrameLoading(false); return; } @@ -324,7 +389,7 @@ function Body() { return; } - setAppUrl(inputAppUrl); + setAppUrl(_inputAppUrl); }; const subscribeToEvents = () => { @@ -600,7 +665,7 @@ function Body() { isInvalid={!isAddressValid} /> {((selectedTabIndex === 0 && isConnected) || - (selectedTabIndex === 1 && appUrl)) && ( + (selectedTabIndex === 1 && appUrl && !isIFrameLoading)) && ( + + + + )} + + + {filteredSafeDapps && + filteredSafeDapps.map((dapp, i) => ( + { + initIFrame(dapp.url); + setInputAppUrl(dapp.url); + closeSafeApps(); + }} + > +
+ + + {dapp.name} + +
+
+ ))} +
+ + + + +