From ed1ce19c3fa15404ad086dea91ded843b5da7339 Mon Sep 17 00:00:00 2001 From: Manuel Wedler Date: Thu, 8 Aug 2024 18:04:37 +0200 Subject: [PATCH] Persist form data when switching tabs --- .../src/app/AppContext.tsx | 1 + .../src/app/VerifyFormContext.tsx | 46 ++++++++++++++++++ apps/contract-verification/src/app/app.tsx | 16 ++++++- .../src/app/components/ConfigInput.tsx | 1 - .../app/components/ConstructorArguments.tsx | 47 ++++++++++++------- .../src/app/components/ContractDropdown.tsx | 32 +++++++------ .../components/SearchableChainDropdown.tsx | 2 +- .../src/app/views/LookupView.tsx | 3 +- .../src/app/views/SettingsView.tsx | 3 +- .../src/app/views/VerifyView.tsx | 19 +++----- 10 files changed, 121 insertions(+), 49 deletions(-) create mode 100644 apps/contract-verification/src/app/VerifyFormContext.tsx diff --git a/apps/contract-verification/src/app/AppContext.tsx b/apps/contract-verification/src/app/AppContext.tsx index e40e14cc57..e4ffda0cee 100644 --- a/apps/contract-verification/src/app/AppContext.tsx +++ b/apps/contract-verification/src/app/AppContext.tsx @@ -2,6 +2,7 @@ import React from 'react' import type { ThemeType, Chain, SubmittedContracts, ContractVerificationSettings } from './types' import { CompilerAbstract } from '@remix-project/remix-solidity' import { ContractVerificationPluginClient } from './ContractVerificationPluginClient' +import { ContractDropdownSelection } from './components/ContractDropdown' // Define the type for the context type AppContextType = { diff --git a/apps/contract-verification/src/app/VerifyFormContext.tsx b/apps/contract-verification/src/app/VerifyFormContext.tsx new file mode 100644 index 0000000000..14d146b0c1 --- /dev/null +++ b/apps/contract-verification/src/app/VerifyFormContext.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import type { Chain } from './types' +import { ContractDropdownSelection } from './components/ContractDropdown' + +// Define the type for the context +type VerifyFormContextType = { + selectedChain: Chain | undefined + setSelectedChain: React.Dispatch> + contractAddress: string + setContractAddress: React.Dispatch> + contractAddressError: string + setContractAddressError: React.Dispatch> + selectedContract: ContractDropdownSelection | undefined + setSelectedContract: React.Dispatch> + proxyAddress: string + setProxyAddress: React.Dispatch> + proxyAddressError: string + setProxyAddressError: React.Dispatch> + abiEncodedConstructorArgs: string + setAbiEncodedConstructorArgs: React.Dispatch> + abiEncodingError: string + setAbiEncodingError: React.Dispatch> +} + +// Provide a default value with the appropriate types +const defaultContextValue: VerifyFormContextType = { + selectedChain: undefined, + setSelectedChain: (selectedChain: Chain) => {}, + contractAddress: '', + setContractAddress: (contractAddress: string) => {}, + contractAddressError: '', + setContractAddressError: (contractAddressError: string) => {}, + selectedContract: undefined, + setSelectedContract: (selectedContract: ContractDropdownSelection) => {}, + proxyAddress: '', + setProxyAddress: (proxyAddress: string) => {}, + proxyAddressError: '', + setProxyAddressError: (contractAddressError: string) => {}, + abiEncodedConstructorArgs: '', + setAbiEncodedConstructorArgs: (contractAddproxyAddressress: string) => {}, + abiEncodingError: '', + setAbiEncodingError: (contractAddressError: string) => {}, +} + +// Create the context with the type +export const VerifyFormContext = React.createContext(defaultContextValue) diff --git a/apps/contract-verification/src/app/app.tsx b/apps/contract-verification/src/app/app.tsx index 423204fc70..44d812ae68 100644 --- a/apps/contract-verification/src/app/app.tsx +++ b/apps/contract-verification/src/app/app.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, useRef } from 'react' import { ContractVerificationPluginClient } from './ContractVerificationPluginClient' import { AppContext } from './AppContext' +import { VerifyFormContext } from './VerifyFormContext' import DisplayRoutes from './routes' import type { ContractVerificationSettings, ThemeType, Chain, SubmittedContracts, VerificationReceipt, VerificationResponse } from './types' import { mergeChainSettingsWithDefaults } from './utils' @@ -11,6 +12,7 @@ import './App.css' import { CompilerAbstract } from '@remix-project/remix-solidity' import { useLocalStorage } from './hooks/useLocalStorage' import { getVerifier } from './Verifiers' +import { ContractDropdownSelection } from './components/ContractDropdown' const plugin = new ContractVerificationPluginClient() @@ -20,6 +22,16 @@ const App = () => { const [submittedContracts, setSubmittedContracts] = useLocalStorage('contract-verification:submitted-contracts', {}) const [chains, setChains] = useState([]) // State to hold the chains data const [compilationOutput, setCompilationOutput] = useState<{ [key: string]: CompilerAbstract } | undefined>() + // Form values: + const [selectedChain, setSelectedChain] = useState() + const [contractAddress, setContractAddress] = useState('') + const [contractAddressError, setContractAddressError] = useState('') + const [selectedContract, setSelectedContract] = useState() + const [proxyAddress, setProxyAddress] = useState('') + const [proxyAddressError, setProxyAddressError] = useState('') + const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = useState('') + const [abiEncodingError, setAbiEncodingError] = useState('') + const timer = useRef(null) useEffect(() => { @@ -131,7 +143,9 @@ const App = () => { return ( - + + + ) } diff --git a/apps/contract-verification/src/app/components/ConfigInput.tsx b/apps/contract-verification/src/app/components/ConfigInput.tsx index 6871f0c186..c1e48b2dd0 100644 --- a/apps/contract-verification/src/app/components/ConfigInput.tsx +++ b/apps/contract-verification/src/app/components/ConfigInput.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react' -import { ethers } from 'ethers/' interface ConfigInputProps { label: string diff --git a/apps/contract-verification/src/app/components/ConstructorArguments.tsx b/apps/contract-verification/src/app/components/ConstructorArguments.tsx index 99bdb3d383..34923e0379 100644 --- a/apps/contract-verification/src/app/components/ConstructorArguments.tsx +++ b/apps/contract-verification/src/app/components/ConstructorArguments.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from 'react' +import { useContext, useEffect, useRef, useState } from 'react' import { ethers } from 'ethers' import { AppContext } from '../AppContext' @@ -22,15 +22,34 @@ export const ConstructorArguments: React.FC = ({ abiE const abi = compiledContract?.abi const constructorArgs = abi && abi.find((a) => a.type === 'constructor')?.inputs - const [constructorArgsValues, setConstructorArgsValues] = useState(Array(constructorArgs?.length ?? 0).fill('')) + const decodeConstructorArgs = (value: string) => { + try { + const decodedObj = ethers.utils.defaultAbiCoder.decode( + constructorArgs.map((inp) => inp.type), + value + ) + const decoded = decodedObj.map((val) => JSON.stringify(val)) + return { decoded, errorMessage: '' } + } catch (e) { + console.error(e) + const errorMessage = 'Decoding error: ' + e.message + const decoded = Array(constructorArgs?.length ?? 0).fill('') + return { decoded, errorMessage } + } + } + + const [constructorArgsValues, setConstructorArgsValues] = useState(abiEncodedConstructorArgs ? decodeConstructorArgs(abiEncodedConstructorArgs).decoded : Array(constructorArgs?.length ?? 0).fill('')) + + const constructorArgsInInitialState = useRef(true) useEffect(() => { - setConstructorArgsValues([]) + if (constructorArgsInInitialState.current) { + constructorArgsInInitialState.current = false + return + } setAbiEncodedConstructorArgs('') setAbiEncodingError('') - if (constructorArgs) { - setConstructorArgsValues(Array(constructorArgs.length).fill('')) - } + setConstructorArgsValues(Array(constructorArgs?.length ?? 0).fill('')) }, [constructorArgs]) const handleConstructorArgs = (value: string, index: number) => { @@ -67,17 +86,9 @@ export const ConstructorArguments: React.FC = ({ abiE const handleRawConstructorArgs = (value: string) => { setAbiEncodedConstructorArgs(value) - try { - const decoded = ethers.utils.defaultAbiCoder.decode( - constructorArgs.map((inp) => inp.type), - value - ) - setConstructorArgsValues(decoded.map((val) => JSON.stringify(val))) - setAbiEncodingError('') - } catch (e) { - console.error(e) - setAbiEncodingError('Decoding error: ' + e.message) - } + const { decoded, errorMessage } = decodeConstructorArgs(value) + setConstructorArgsValues(decoded) + setAbiEncodingError(errorMessage) } if (!selectedContract) return null @@ -105,7 +116,7 @@ export const ConstructorArguments: React.FC = ({ abiE {constructorArgs.map((inp, i) => (
{inp.name}
- handleConstructorArgs(e.target.value, i)} /> + handleConstructorArgs(e.target.value, i)} />
))} {abiEncodedConstructorArgs && ( diff --git a/apps/contract-verification/src/app/components/ContractDropdown.tsx b/apps/contract-verification/src/app/components/ContractDropdown.tsx index 73e00904a7..657c1ddacf 100644 --- a/apps/contract-verification/src/app/components/ContractDropdown.tsx +++ b/apps/contract-verification/src/app/components/ContractDropdown.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useContext } from 'react' +import React, { useEffect, useState, useContext, Fragment } from 'react' import './ContractDropdown.css' import { AppContext } from '../AppContext' @@ -11,15 +11,16 @@ export interface ContractDropdownSelection { interface ContractDropdownProps { label: string id: string + selectedContract: ContractDropdownSelection setSelectedContract: (selection: ContractDropdownSelection) => void } // Chooses one contract from the compilation output. -export const ContractDropdown: React.FC = ({ label, id, setSelectedContract }) => { +export const ContractDropdown: React.FC = ({ label, id, selectedContract, setSelectedContract }) => { const { compilationOutput } = useContext(AppContext) useEffect(() => { - if (!compilationOutput) return + if (!compilationOutput || !!selectedContract) return // Otherwise select the first by default const triggerFilePath = Object.keys(compilationOutput)[0] const contracts = compilationOutput[triggerFilePath]?.data?.contracts @@ -42,21 +43,24 @@ export const ContractDropdown: React.FC = ({ label, id, s return (
- {hasContracts ? ( Object.keys(compilationOutput).map((compilationTriggerFileName) => ( - {Object.keys(compilationOutput[compilationTriggerFileName].data.contracts).map((fileName2) => ( - <> - )) diff --git a/apps/contract-verification/src/app/components/SearchableChainDropdown.tsx b/apps/contract-verification/src/app/components/SearchableChainDropdown.tsx index b58a755a29..5cd834d93a 100644 --- a/apps/contract-verification/src/app/components/SearchableChainDropdown.tsx +++ b/apps/contract-verification/src/app/components/SearchableChainDropdown.tsx @@ -34,7 +34,7 @@ export const SearchableChainDropdown: React.FC = ({ label, id, se [chains] ) - const [searchTerm, setSearchTerm] = useState('') + const [searchTerm, setSearchTerm] = useState(selectedChain ? getChainDescriptor(selectedChain) : '') const [isOpen, setIsOpen] = useState(false) const [filteredOptions, setFilteredOptions] = useState(dropdownChains) const dropdownRef = useRef(null) diff --git a/apps/contract-verification/src/app/views/LookupView.tsx b/apps/contract-verification/src/app/views/LookupView.tsx index 53bf009d42..b203e575a8 100644 --- a/apps/contract-verification/src/app/views/LookupView.tsx +++ b/apps/contract-verification/src/app/views/LookupView.tsx @@ -7,10 +7,11 @@ import { AppContext } from '../AppContext' import { CustomTooltip } from '@remix-ui/helper' import { getVerifier } from '../Verifiers' import { useNavigate } from 'react-router-dom' +import { VerifyFormContext } from '../VerifyFormContext' export const LookupView = () => { const { settings, clientInstance } = useContext(AppContext) - const [selectedChain, setSelectedChain] = useState() + const { selectedChain, setSelectedChain } = useContext(VerifyFormContext) const [contractAddress, setContractAddress] = useState('') const [contractAddressError, setContractAddressError] = useState('') const [loadingVerifiers, setLoadingVerifiers] = useState>>({}) diff --git a/apps/contract-verification/src/app/views/SettingsView.tsx b/apps/contract-verification/src/app/views/SettingsView.tsx index 2c4e1c4d6e..df606b31d3 100644 --- a/apps/contract-verification/src/app/views/SettingsView.tsx +++ b/apps/contract-verification/src/app/views/SettingsView.tsx @@ -3,10 +3,11 @@ import { SearchableChainDropdown, ConfigInput } from '../components' import type { VerifierIdentifier, Chain, VerifierSettings, ContractVerificationSettings } from '../types' import { mergeChainSettingsWithDefaults } from '../utils' import { AppContext } from '../AppContext' +import { VerifyFormContext } from '../VerifyFormContext' export const SettingsView = () => { const { settings, setSettings } = useContext(AppContext) - const [selectedChain, setSelectedChain] = useState() + const { selectedChain, setSelectedChain } = useContext(VerifyFormContext) const chainSettings = selectedChain ? mergeChainSettingsWithDefaults(selectedChain.chainId.toString(), settings) : undefined diff --git a/apps/contract-verification/src/app/views/VerifyView.tsx b/apps/contract-verification/src/app/views/VerifyView.tsx index 599713970b..8196287fdb 100644 --- a/apps/contract-verification/src/app/views/VerifyView.tsx +++ b/apps/contract-verification/src/app/views/VerifyView.tsx @@ -2,27 +2,20 @@ import { useContext, useEffect, useState } from 'react' import { AppContext } from '../AppContext' import { SearchableChainDropdown, ContractDropdown, ContractAddressInput } from '../components' -import type { VerifierIdentifier, Chain, SubmittedContract, VerificationReceipt, VerifierInfo, VerificationResponse } from '../types' +import type { VerifierIdentifier, SubmittedContract, VerificationReceipt, VerifierInfo, VerificationResponse } from '../types' import { VERIFIERS } from '../types' import { mergeChainSettingsWithDefaults, validConfiguration } from '../utils' import { useNavigate } from 'react-router-dom' import { ConstructorArguments } from '../components/ConstructorArguments' -import { ContractDropdownSelection } from '../components/ContractDropdown' import { CustomTooltip } from '@remix-ui/helper' import { AbstractVerifier, getVerifier } from '../Verifiers' +import { VerifyFormContext } from '../VerifyFormContext' export const VerifyView = () => { const { compilationOutput, setSubmittedContracts, settings } = useContext(AppContext) - const [selectedChain, setSelectedChain] = useState() - const [contractAddress, setContractAddress] = useState('') - const [contractAddressError, setContractAddressError] = useState('') - const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = useState('') - const [abiEncodingError, setAbiEncodingError] = useState('') - const [selectedContract, setSelectedContract] = useState() + const { selectedChain, setSelectedChain, contractAddress, setContractAddress, contractAddressError, setContractAddressError, selectedContract, setSelectedContract, proxyAddress, setProxyAddress, proxyAddressError, setProxyAddressError, abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, abiEncodingError, setAbiEncodingError } = useContext(VerifyFormContext) const [enabledVerifiers, setEnabledVerifiers] = useState>>({}) - const [hasProxy, setHasProxy] = useState(false) - const [proxyAddress, setProxyAddress] = useState('') - const [proxyAddressError, setProxyAddressError] = useState('') + const [hasProxy, setHasProxy] = useState(!!proxyAddress) const navigate = useNavigate() const chainSettings = selectedChain ? mergeChainSettingsWithDefaults(selectedChain.chainId.toString(), settings) : undefined @@ -116,6 +109,8 @@ export const VerifyView = () => { setSubmittedContracts((prev) => ({ ...prev, [newSubmittedContract.id]: newSubmittedContract })) + setContractAddress('') + // Take user to receipt view navigate('/receipts') @@ -164,7 +159,7 @@ export const VerifyView = () => { - + {selectedContract && }