From 18791db21e41e314a4f8777bedf9d509ddb68054 Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" <330911+jurevans@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:25:38 -0400 Subject: [PATCH] feat: Faucet - Make extension connection optional (#1089) * feat: make extension optional * fix: remove unneeded message --- apps/faucet/src/App/App.tsx | 81 ++------------------- apps/faucet/src/App/Faucet.tsx | 127 +++++++++++++++++++++++++++------ 2 files changed, 110 insertions(+), 98 deletions(-) diff --git a/apps/faucet/src/App/App.tsx b/apps/faucet/src/App/App.tsx index fe513613a..902f17492 100644 --- a/apps/faucet/src/App/App.tsx +++ b/apps/faucet/src/App/App.tsx @@ -1,9 +1,8 @@ -import React, { createContext, useCallback, useEffect, useState } from "react"; +import React, { createContext, useEffect, useState } from "react"; import { GoGear } from "react-icons/go"; import { ThemeProvider } from "styled-components"; -import { ActionButton, Alert, Modal } from "@namada/components"; -import { Namada } from "@namada/integrations"; +import { Alert, Modal } from "@namada/components"; import { ColorMode, getTheme } from "@namada/utils"; import { @@ -20,9 +19,6 @@ import { } from "App/App.components"; import { FaucetForm } from "App/Faucet"; -import { chains } from "@namada/chains"; -import { useUntil } from "@namada/hooks"; -import { Account } from "@namada/types"; import { API, toNam } from "utils"; import dotsBackground from "../../public/bg-dots.svg"; import { @@ -82,21 +78,9 @@ const START_TIME_TEXT = new Date(START_TIME_UTC * 1000).toLocaleString( export const AppContext = createContext(null); -enum ExtensionAttachStatus { - PendingDetection, - NotInstalled, - Installed, -} - export const App: React.FC = () => { const initialColorMode = "dark"; - const chain = chains.namada; - const integration = new Namada(chain); - const [extensionAttachStatus, setExtensionAttachStatus] = useState( - ExtensionAttachStatus.PendingDetection - ); - const [isExtensionConnected, setIsExtensionConnected] = useState(false); - const [accounts, setAccounts] = useState([]); + const [colorMode, _] = useState(initialColorMode); const [isTestnetLive, setIsTestnetLive] = useState(true); const [settings, setSettings] = useState({ @@ -129,20 +113,6 @@ export const App: React.FC = () => { }); }; - useUntil( - { - predFn: async () => Promise.resolve(integration.detect()), - onSuccess: () => { - setExtensionAttachStatus(ExtensionAttachStatus.Installed); - }, - onFail: () => { - setExtensionAttachStatus(ExtensionAttachStatus.NotInstalled); - }, - }, - { tries: 5, ms: 300 }, - [integration] - ); - useEffect(() => { // Sync url to localStorage localStorage.setItem("baseUrl", url); @@ -168,27 +138,6 @@ export const App: React.FC = () => { .catch((e) => setSettingsError(`Failed to load settings! ${e}`)); }, [url]); - const handleConnectExtensionClick = useCallback(async (): Promise => { - if (integration) { - try { - const isIntegrationDetected = integration.detect(); - - if (!isIntegrationDetected) { - throw new Error("Extension not installed!"); - } - - await integration.connect(); - const accounts = await integration.accounts(); - if (accounts) { - setAccounts(accounts.filter((account) => !account.isShielded)); - } - setIsExtensionConnected(true); - } catch (e) { - console.error(e); - } - } - }, [integration]); - return ( { )} - {extensionAttachStatus === - ExtensionAttachStatus.PendingDetection && ( - - Detecting extension... - - )} - {extensionAttachStatus === ExtensionAttachStatus.NotInstalled && ( - - You must download the extension! - - )} - - {isExtensionConnected && ( - - )} - {extensionAttachStatus === ExtensionAttachStatus.Installed && - !isExtensionConnected && ( - - - Connect to Namada Extension - - - )} + {isModalOpen && ( setIsModalOpen(false)}> diff --git a/apps/faucet/src/App/Faucet.tsx b/apps/faucet/src/App/Faucet.tsx index 9af7ea9d0..186ee85a4 100644 --- a/apps/faucet/src/App/Faucet.tsx +++ b/apps/faucet/src/App/Faucet.tsx @@ -7,11 +7,15 @@ import { Alert, AmountInput, Input, + Option, Select, } from "@namada/components"; import { Account } from "@namada/types"; import { bech32mValidation, shortenAddress } from "@namada/utils"; +import { chains } from "@namada/chains"; +import { useUntil } from "@namada/hooks"; +import { Namada } from "@namada/integrations"; import { Data, PowChallenge, TransferResponse } from "../utils"; import { AppContext } from "./App"; import { @@ -33,18 +37,28 @@ enum Status { } type Props = { - accounts: Account[]; isTestnetLive: boolean; }; const bech32mPrefix = "tnam"; -export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { +enum ExtensionAttachStatus { + PendingDetection, + NotInstalled, + Installed, +} + +export const FaucetForm: React.FC = ({ isTestnetLive }) => { const { api, settings: { difficulty, tokens, withdrawLimit }, } = useContext(AppContext)!; + const [extensionAttachStatus, setExtensionAttachStatus] = useState( + ExtensionAttachStatus.PendingDetection + ); + const [isExtensionConnected, setIsExtensionConnected] = useState(false); + const [accounts, setAccounts] = useState([]); const accountLookup = accounts.reduce( (acc, account) => { acc[account.address] = account; @@ -53,7 +67,13 @@ export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { {} as Record ); - const [account, setAccount] = useState(accounts[0]); + const chain = chains.namada; + const integration = new Namada(chain); + const [account, setAccount] = useState(); + const [accountsSelectData, setAccountsSelectData] = useState< + Option[] + >([]); + const [target, setTarget] = useState(); const [tokenAddress, setTokenAddress] = useState(); const [amount, setAmount] = useState(undefined); const [error, setError] = useState(); @@ -61,16 +81,23 @@ export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { const [statusText, setStatusText] = useState(); const [responseDetails, setResponseDetails] = useState(); - const accountsSelectData = accounts.map(({ alias, address }) => ({ - label: `${alias} - ${shortenAddress(address)}`, - value: address, - })); - const powSolver: Worker = useMemo( () => new Worker(new URL("../workers/powWorker.ts", import.meta.url)), [] ); + useEffect(() => { + if (accounts) { + setAccountsSelectData( + accounts.map(({ alias, address }) => ({ + label: `${alias} - ${shortenAddress(address)}`, + value: address, + })) + ); + setAccount(accounts[0]); + } + }, [accounts]); + useEffect(() => { if (tokens?.NAM) { setTokenAddress(tokens.NAM); @@ -81,7 +108,7 @@ export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { Boolean(tokenAddress) && Boolean(amount) && (amount || 0) <= withdrawLimit && - Boolean(account) && + Boolean(target) && status !== Status.PendingPowSolution && status !== Status.PendingTransfer && typeof difficulty !== "undefined" && @@ -127,7 +154,7 @@ export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { async (e: React.MouseEvent) => { e.preventDefault(); if ( - !account || + !target || !amount || !tokenAddress || typeof difficulty === "undefined" @@ -145,9 +172,9 @@ export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { return; } - if (!account) { + if (!target) { setStatus(Status.Error); - setError("No account found!"); + setError("No target specified!"); return; } @@ -175,7 +202,7 @@ export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { tag, challenge, transfer: { - target: account.address, + target, token: sanitizedToken, amount: amount * 1_000_000, }, @@ -190,29 +217,87 @@ export const FaucetForm: React.FC = ({ accounts, isTestnetLive }) => { [account, tokenAddress, amount] ); + useUntil( + { + predFn: async () => Promise.resolve(integration.detect()), + onSuccess: () => { + setExtensionAttachStatus(ExtensionAttachStatus.Installed); + }, + onFail: () => { + setExtensionAttachStatus(ExtensionAttachStatus.NotInstalled); + }, + }, + { tries: 5, ms: 300 }, + [integration] + ); + + const handleConnectExtensionClick = useCallback( + async (e: React.MouseEvent): Promise => { + e.preventDefault(); + if (integration) { + try { + const isIntegrationDetected = integration.detect(); + + if (!isIntegrationDetected) { + throw new Error("Extension not installed!"); + } + + await integration.connect(); + const accounts = await integration.accounts(); + if (accounts) { + setAccounts(accounts.filter((account) => !account.isShielded)); + } + setIsExtensionConnected(true); + } catch (e) { + console.error(e); + } + } + }, + [integration] + ); + + useEffect(() => { + if (account) { + setTarget(account.address); + } + }, [account]); + return ( - {accounts.length > 0 ? + {account && accounts.length && ( setTarget(e.target.value)} + autoFocus={true} + /> + + + {extensionAttachStatus === ExtensionAttachStatus.Installed && + !isExtensionConnected && ( + + + Load Accounts from Extension + + + )} + setTokenAddress(e.target.value)} - autoFocus={true} />