From 72ccf2294f45618a7cd0a57e1d83292eead60b2c Mon Sep 17 00:00:00 2001 From: therealemjy Date: Thu, 20 Feb 2025 09:41:35 +0100 Subject: [PATCH 01/13] feat: update behavior to switch chains --- .../src/containers/ConnectWallet/index.tsx | 37 +---- .../containers/Form/RhfSubmitButton/index.tsx | 2 +- apps/evm/src/containers/SwitchChain/index.tsx | 41 +++++ .../ConnectKitWrapper/AuthHandler/index.tsx | 56 +------ .../Page/OperationForm/BorrowForm/index.tsx | 4 +- .../Page/OperationForm/RepayForm/index.tsx | 4 +- .../SupplyForm/SubmitSection/index.tsx | 36 +++-- .../Page/OperationForm/SupplyForm/index.tsx | 150 +++++++++--------- .../Page/OperationForm/WithdrawForm/index.tsx | 4 +- 9 files changed, 149 insertions(+), 185 deletions(-) create mode 100644 apps/evm/src/containers/SwitchChain/index.tsx diff --git a/apps/evm/src/containers/ConnectWallet/index.tsx b/apps/evm/src/containers/ConnectWallet/index.tsx index 62f94d25bd..c6d69c55f5 100644 --- a/apps/evm/src/containers/ConnectWallet/index.tsx +++ b/apps/evm/src/containers/ConnectWallet/index.tsx @@ -1,16 +1,10 @@ -import { chainMetadata } from '@venusprotocol/chains'; -import { useMemo } from 'react'; - -import { Button, type Variant } from 'components/Button'; +import { Button } from 'components/Button'; import { NoticeInfo } from 'components/Notice'; import { useTranslation } from 'libs/translations'; -import { useAccountAddress, useAuthModal, useChainId, useSwitchChain } from 'libs/wallet'; -import type { ChainId } from 'types'; +import { useAccountAddress, useAuthModal } from 'libs/wallet'; import { Container } from './Container'; export interface ConnectWalletProps extends React.HTMLAttributes { - chainId?: ChainId; - buttonVariant?: Variant; message?: string; className?: string; children?: React.ReactNode; @@ -18,26 +12,13 @@ export interface ConnectWalletProps extends React.HTMLAttributes export const ConnectWallet: React.FC = ({ children, - chainId, message, - buttonVariant, ...otherProps }) => { const { accountAddress } = useAccountAddress(); const isUserConnected = !!accountAddress; - const { chainId: currentChainId } = useChainId(); - - const isOnWrongChain = useMemo( - () => chainId && currentChainId !== chainId, - [currentChainId, chainId], - ); - - const chain = chainId && chainMetadata[chainId]; - const { openAuthModal } = useAuthModal(); - const { switchChain } = useSwitchChain(); - const handleSwitchChain = () => chainId && switchChain({ chainId }); const { t } = useTranslation(); @@ -46,24 +27,12 @@ export const ConnectWallet: React.FC = ({ {!!message && } - ); } - if (isOnWrongChain) { - return ( - - - - ); - } - return {children}; }; diff --git a/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx b/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx index 73573fe217..1f9046d7c4 100644 --- a/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx +++ b/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx @@ -50,7 +50,7 @@ export const RhfSubmitButton: React.FC = ({ } if (requiresConnectedWallet || spendingApproval) { - dom = {dom}; + dom = {dom}; } return
{dom}
; diff --git a/apps/evm/src/containers/SwitchChain/index.tsx b/apps/evm/src/containers/SwitchChain/index.tsx new file mode 100644 index 0000000000..a7bb2dd69a --- /dev/null +++ b/apps/evm/src/containers/SwitchChain/index.tsx @@ -0,0 +1,41 @@ +import { chainMetadata } from '@venusprotocol/chains'; + +import { Button } from 'components/Button'; +import { useTranslation } from 'libs/translations'; +import { useChainId, useSwitchChain } from 'libs/wallet'; +import type { ChainId } from 'types'; +import { useAccount } from 'wagmi'; + +// TODO: add tests + +export interface SwitchChainProps extends React.HTMLAttributes { + chainId?: ChainId; +} + +export const SwitchChain: React.FC = ({ children, chainId, ...otherProps }) => { + const { chainId: currentChainId } = useChainId(); + const targetChainId = chainId || currentChainId; + + const { chainId: walletChainId } = useAccount(); + const isOnWrongChain = walletChainId !== targetChainId; + const targetChain = chainMetadata[targetChainId]; + + const { switchChain } = useSwitchChain(); + const { t } = useTranslation(); + + const handleSwitchChain = () => switchChain({ chainId: targetChainId }); + + return ( +
+ {isOnWrongChain ? ( + + ) : ( + children + )} +
+ ); +}; diff --git a/apps/evm/src/libs/wallet/Web3Wrapper/ConnectKitWrapper/AuthHandler/index.tsx b/apps/evm/src/libs/wallet/Web3Wrapper/ConnectKitWrapper/AuthHandler/index.tsx index 17c00a64e5..86a0f7165f 100644 --- a/apps/evm/src/libs/wallet/Web3Wrapper/ConnectKitWrapper/AuthHandler/index.tsx +++ b/apps/evm/src/libs/wallet/Web3Wrapper/ConnectKitWrapper/AuthHandler/index.tsx @@ -1,20 +1,17 @@ -import { getAccount, watchAccount } from '@wagmi/core'; +import { getAccount } from '@wagmi/core'; import { useEffect, useRef } from 'react'; import { useLocation } from 'react-router'; import { useSearchParams } from 'react-router-dom'; -import { useDisconnect } from 'wagmi'; import { routes } from 'constants/routing'; import { useNavigate } from 'hooks/useNavigate'; import { getUnsafeChainIdFromSearchParams } from 'libs/wallet'; +import config from 'libs/wallet/Web3Wrapper/config'; import { chains, defaultChain } from 'libs/wallet/chains'; import { CHAIN_ID_SEARCH_PARAM } from 'libs/wallet/constants'; import { useUpdateUrlChainId } from 'libs/wallet/hooks/useUpdateUrlChainId'; -import { getChainId } from 'libs/wallet/utilities/getChainId'; -import config from '../../config'; export const AuthHandler: React.FC = () => { - const { disconnect } = useDisconnect(); const { updateUrlChainId } = useUpdateUrlChainId(); const { navigate } = useNavigate(); @@ -22,7 +19,6 @@ export const AuthHandler: React.FC = () => { const initialLocationRef = useRef(location); const navigateRef = useRef(navigate); const [searchParams] = useSearchParams(); - const isInitializedRef = useRef(false); // Initialize wallet connection on mount useEffect(() => { @@ -45,38 +41,6 @@ export const AuthHandler: React.FC = () => { } }, []); - // Detect change of chain ID triggered from wallet - useEffect(() => { - const stopWatchingNetwork = watchAccount(config, { - onChange: async ({ chain: walletChain }) => { - if (!walletChain) { - disconnect(); - return; - } - - const { chainId } = getChainId(); - - // Disconnect wallet if it is connected to a different chain than the one set in the URL. - // This check is only performed on initial mount, in order to support links without - // redirecting users - if (walletChain.id !== chainId && !isInitializedRef.current) { - disconnect(); - } - - // Update URL when wallet connects to a different chain - if (walletChain.id !== chainId && isInitializedRef.current) { - updateUrlChainId({ chainId: walletChain.id }); - } - - if (!isInitializedRef.current) { - isInitializedRef.current = true; - } - }, - }); - - return stopWatchingNetwork; - }, [updateUrlChainId, disconnect]); - // Detect change of chain ID triggered from URL useEffect(() => { const fn = async () => { @@ -84,26 +48,18 @@ export const AuthHandler: React.FC = () => { searchParams, }); - const { chain: walletChain } = getAccount(config); - - // Update URL again if it was updated with an unsupported chain ID or does not contain any - // chain ID search param + // Update URL if it was updated with an unsupported chain ID or does not contain any chain ID + // search param if ( unsafeSearchParamChainId === undefined || !chains.some(chain => chain.id === unsafeSearchParamChainId) ) { - updateUrlChainId({ chainId: walletChain?.id ?? defaultChain.id }); - return; - } - - // Disconnect wallet if chain ID changed in URL but wallet is connected to a different network - if (walletChain && walletChain.id !== unsafeSearchParamChainId) { - disconnect(); + updateUrlChainId({ chainId: defaultChain.id }); } }; fn(); - }, [searchParams, updateUrlChainId, disconnect]); + }, [searchParams, updateUrlChainId]); return null; }; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx index c7128e6e0a..948a3c8524 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx @@ -241,9 +241,7 @@ export const BorrowFormUi: React.FC = ({
- - {t('operationForm.connectWalletButtonLabel')} - + {t('operationForm.connectWalletButtonLabel')}
)} diff --git a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx index 1e498fd8a3..c1bb4c1d52 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx @@ -378,9 +378,7 @@ export const RepayFormUi: React.FC = ({
- - {t('operationForm.connectWalletButtonLabel')} - + {t('operationForm.connectWalletButtonLabel')}
)} diff --git a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/SubmitSection/index.tsx index 4bf9bac66d..3ba344f3bd 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/SubmitSection/index.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'libs/translations'; import type { Swap, Token } from 'types'; import { cn } from 'utilities'; +import { SwitchChain } from 'containers/SwitchChain'; import SwapSummary from '../../SwapSummary'; import type { FormError } from '../../types'; import type { FormErrorCode } from '../useForm/types'; @@ -57,17 +58,8 @@ export const SubmitSection: React.FC = ({ return t('operationForm.submitButtonLabel.supply'); }, [isFormValid, t]); - return ( - + const dom = ( + <> = ({ {isFormValid && !isSwapLoading && !isFromTokenWalletSpendingLimitLoading && ( )} - + ); + + if (isFormValid) { + return ( + + + {dom} + + + ); + } + + return dom; }; export default SubmitSection; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx index ca2cdeeee9..f98ebb2cd4 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx @@ -321,92 +321,84 @@ export const SupplyFormUi: React.FC = ({ /> )} - {isUserConnected ? ( - <> - {!isSubmitting && !isSwapLoading && !formError && } - -
- - {isUsingSwap - ? readableFromTokenUserWalletBalanceTokens - : readableSuppliableFromTokenAmountTokens} - - - -
+ {!isUserConnected && } + + + {!isSubmitting && !isSwapLoading && !formError && } + +
+ + {isUsingSwap + ? readableFromTokenUserWalletBalanceTokens + : readableSuppliableFromTokenAmountTokens} + + + +
+ + - - - {isUsingSwap && swap && ( - <> - - - - - )} - -
-
- - - - - -
- - + + + + + )} + +
+
+ + + + +
- - ) : ( -
- - - {t('operationForm.connectWalletButtonLabel')} - +
- )} + ); }; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx index 5fa39614bb..7f51f78185 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx @@ -237,9 +237,7 @@ export const WithdrawFormUi: React.FC = ({
- - {t('operationForm.connectWalletButtonLabel')} - + {t('operationForm.connectWalletButtonLabel')}
)} From ea1f8931475110534bfc7b9614f4cda0bc6c07f5 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Thu, 20 Feb 2025 14:31:57 +0100 Subject: [PATCH 02/13] feat: create useAccountChainId hook + update proposal commands section --- .../useGetPools/__tests__/index.spec.ts | 8 ++-- .../ConnectWallet/__tests__/index.spec.tsx | 36 +------------- .../src/containers/ConnectWallet/index.tsx | 7 ++- apps/evm/src/containers/SwitchChain/index.tsx | 12 +++-- .../__tests__/index.spec.tsx | 7 --- .../evm/src/libs/wallet/Web3Wrapper/config.ts | 4 +- apps/evm/src/libs/wallet/__mocks__/index.ts | 1 + .../useAccountChainId/__mocks__/index.ts | 3 ++ .../wallet/hooks/useAccountChainId/index.tsx | 6 +++ apps/evm/src/libs/wallet/index.ts | 1 + .../BscCommand/ActionButton/index.tsx | 7 ++- .../NonBscCommand/ExecuteButton/index.tsx | 42 +++++++++++++++++ .../Proposal/Commands/NonBscCommand/index.tsx | 47 +++++-------------- .../pages/Proposal/__tests__/index.spec.tsx | 1 + apps/evm/src/testUtils/render.tsx | 24 +++++++--- 15 files changed, 104 insertions(+), 102 deletions(-) create mode 100644 apps/evm/src/libs/wallet/hooks/useAccountChainId/__mocks__/index.ts create mode 100644 apps/evm/src/libs/wallet/hooks/useAccountChainId/index.tsx create mode 100644 apps/evm/src/pages/Proposal/Commands/NonBscCommand/ExecuteButton/index.tsx diff --git a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts index b09b942e54..e6e1f19d13 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts @@ -13,7 +13,7 @@ import { useGetVaiControllerContractAddress, useGetVenusLensContractAddress, } from 'libs/contracts'; -import { useChainId, usePublicClient } from 'libs/wallet'; +import { usePublicClient } from 'libs/wallet'; import { renderHook } from 'testUtils/render'; import { restService } from 'utilities/restService'; import { useGetPools } from '..'; @@ -87,11 +87,9 @@ describe('useGetPools', () => { }); it('returns pools with time based reward rates in the correct format', async () => { - (useChainId as Mock).mockImplementation(() => ({ + const { result } = renderHook(() => useGetPools(), { chainId: ChainId.ARBITRUM_SEPOLIA, - })); - - const { result } = renderHook(() => useGetPools()); + }); await waitFor(() => expect(result.current.data).toBeDefined()); expect(result.current.data).toMatchSnapshot(); diff --git a/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx b/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx index 5bb1b03157..f3c1110bae 100644 --- a/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx +++ b/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx @@ -5,7 +5,6 @@ import fakeAccountAddress from '__mocks__/models/address'; import { en } from 'libs/translations'; import { useAuthModal, useSwitchChain } from 'libs/wallet'; import { renderComponent } from 'testUtils/render'; -import { ChainId } from 'types'; import { ConnectWallet } from '..'; describe('ConnectWallet', () => { @@ -38,40 +37,9 @@ describe('ConnectWallet', () => { }); }); - it('displays chain switching button when current chain is different from chainId parameter', async () => { - const { getByText } = renderComponent(, { - accountAddress: fakeAccountAddress, - }); - - const switchChainButton = getByText( - en.connectWallet.switchChain.replace('{{chainName}}', 'opBNB testnet'), - ); - expect(switchChainButton).toBeInTheDocument(); - }); - - it('calls switchChain when switch chain button is clicked', async () => { - const mockSwitchChain = vi.fn(); - (useSwitchChain as Mock).mockReturnValue({ switchChain: mockSwitchChain }); - - const { getByText } = renderComponent(, { - accountAddress: fakeAccountAddress, - }); - - const switchChainButton = getByText( - en.connectWallet.switchChain.replace('{{chainName}}', 'opBNB testnet'), - ); - - fireEvent.click(switchChainButton); - - await waitFor(() => { - expect(mockSwitchChain).toHaveBeenCalledTimes(1); - expect(mockSwitchChain).toHaveBeenCalledWith({ chainId: ChainId.OPBNB_TESTNET }); - }); - }); - - it('renders children when user is connected and on the correct chain', () => { + it('renders children when user is connected', () => { const { getByText } = renderComponent( - +
Child Component
, { diff --git a/apps/evm/src/containers/ConnectWallet/index.tsx b/apps/evm/src/containers/ConnectWallet/index.tsx index c6d69c55f5..6e54908289 100644 --- a/apps/evm/src/containers/ConnectWallet/index.tsx +++ b/apps/evm/src/containers/ConnectWallet/index.tsx @@ -2,7 +2,6 @@ import { Button } from 'components/Button'; import { NoticeInfo } from 'components/Notice'; import { useTranslation } from 'libs/translations'; import { useAccountAddress, useAuthModal } from 'libs/wallet'; -import { Container } from './Container'; export interface ConnectWalletProps extends React.HTMLAttributes { message?: string; @@ -24,15 +23,15 @@ export const ConnectWallet: React.FC = ({ if (!isUserConnected) { return ( - +
{!!message && } - +
); } - return {children}; + return
{children}
; }; diff --git a/apps/evm/src/containers/SwitchChain/index.tsx b/apps/evm/src/containers/SwitchChain/index.tsx index a7bb2dd69a..6ca979bf5f 100644 --- a/apps/evm/src/containers/SwitchChain/index.tsx +++ b/apps/evm/src/containers/SwitchChain/index.tsx @@ -2,9 +2,8 @@ import { chainMetadata } from '@venusprotocol/chains'; import { Button } from 'components/Button'; import { useTranslation } from 'libs/translations'; -import { useChainId, useSwitchChain } from 'libs/wallet'; +import { useAccountAddress, useAccountChainId, useChainId, useSwitchChain } from 'libs/wallet'; import type { ChainId } from 'types'; -import { useAccount } from 'wagmi'; // TODO: add tests @@ -13,11 +12,14 @@ export interface SwitchChainProps extends React.HTMLAttributes { } export const SwitchChain: React.FC = ({ children, chainId, ...otherProps }) => { + const { accountAddress } = useAccountAddress(); + const isUserConnected = !!accountAddress; + const { chainId: currentChainId } = useChainId(); const targetChainId = chainId || currentChainId; - const { chainId: walletChainId } = useAccount(); - const isOnWrongChain = walletChainId !== targetChainId; + const { chainId: accountChainId } = useAccountChainId(); + const isOnWrongChain = accountChainId !== targetChainId; const targetChain = chainMetadata[targetChainId]; const { switchChain } = useSwitchChain(); @@ -27,7 +29,7 @@ export const SwitchChain: React.FC = ({ children, chainId, ... return (
- {isOnWrongChain ? ( + {isUserConnected && isOnWrongChain ? ( + + + ); +}; diff --git a/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx b/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx index f8a04b46ac..5eb732200c 100644 --- a/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx +++ b/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx @@ -1,9 +1,5 @@ import { chainMetadata } from '@venusprotocol/chains'; -import { useExecuteProposal } from 'clients/api'; -import { Button } from 'components'; -import { ConnectWallet } from 'containers/ConnectWallet'; import { useIsProposalExecutable } from 'hooks/useIsProposalExecutable'; -import { VError, handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import { governanceChain, useChainId } from 'libs/wallet'; import { useMemo } from 'react'; @@ -11,6 +7,7 @@ import { type RemoteProposal, RemoteProposalState } from 'types'; import { Command } from '../Command'; import { Description } from '../Description'; import { CurrentStep } from './CurrentStep'; +import { ExecuteButton } from './ExecuteButton'; const governanceChainMetadata = chainMetadata[governanceChain.id]; @@ -35,24 +32,6 @@ export const NonBscCommand: React.FC = ({ executionEtaDate: remoteProposal.executionEtaDate, }); - const { mutateAsync: executeProposal, isPending: isExecuteProposalLoading } = - useExecuteProposal(); - - const execute = async () => { - if (!remoteProposal.remoteProposalId) { - throw new VError({ type: 'unexpected', code: 'somethingWentWrong' }); - } - - try { - await executeProposal({ - proposalId: remoteProposal.remoteProposalId, - chainId: remoteProposal.chainId, - }); - } catch (error) { - handleError({ error }); - } - }; - const description = useMemo(() => { switch (remoteProposal.state) { case RemoteProposalState.Pending: @@ -91,12 +70,12 @@ export const NonBscCommand: React.FC = ({ } proposalActions={remoteProposal.proposalActions} contentRightItem={ - isExecutable ? ( - - - + isExecutable && remoteProposal.remoteProposalId ? ( + ) : ( = ({ ) } contentBottomItem={ - isExecutable ? ( - - - + isExecutable && remoteProposal.remoteProposalId ? ( + ) : undefined } {...otherProps} diff --git a/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx b/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx index a46512b3aa..4bcdf3d490 100644 --- a/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx @@ -74,6 +74,7 @@ const checkVoteButtonsAreHidden = async ( waitFor(() => expect(queryByText(en.vote.abstain, { selector: 'button' })).toBeNull()); }; +// TODO: rename to "Proposal" describe('ProposalComp page', () => { beforeEach(() => { vi.useFakeTimers().setSystemTime(fakeNow); diff --git a/apps/evm/src/testUtils/render.tsx b/apps/evm/src/testUtils/render.tsx index cce1db66c4..49498651eb 100644 --- a/apps/evm/src/testUtils/render.tsx +++ b/apps/evm/src/testUtils/render.tsx @@ -7,9 +7,15 @@ import type { Mock } from 'vitest'; import fakeSigner from '__mocks__/models/signer'; -import { Web3Wrapper, useAccountAddress, useChainId, useSigner } from 'libs/wallet'; +import { + Web3Wrapper, + useAccountAddress, + useAccountChainId, + useChainId, + useSigner, +} from 'libs/wallet'; import { MuiThemeProvider } from 'theme/MuiThemeProvider'; -import type { ChainId } from 'types'; +import { ChainId } from 'types'; const createQueryClient = () => new QueryClient({ @@ -35,6 +41,8 @@ interface WrapperProps { } const Wrapper: React.FC = ({ children, options }) => { + const chainId = options?.chainId || ChainId.BSC_TESTNET; + if (options?.accountAddress) { const accountAddress = options?.accountAddress; @@ -42,6 +50,10 @@ const Wrapper: React.FC = ({ children, options }) => { accountAddress, })); + (useAccountChainId as Mock).mockImplementation(() => ({ + chainId, + })); + (useSigner as Mock).mockImplementation(() => ({ signer: { ...fakeSigner, @@ -50,11 +62,9 @@ const Wrapper: React.FC = ({ children, options }) => { })); } - if (options?.chainId) { - (useChainId as Mock).mockImplementation(() => ({ - chainId: options?.chainId, - })); - } + (useChainId as Mock).mockImplementation(() => ({ + chainId, + })); return ( From e53898fc3dd6fe4359876525ca7bbc264f7cf04b Mon Sep 17 00:00:00 2001 From: therealemjy Date: Fri, 21 Feb 2025 09:49:45 +0100 Subject: [PATCH 03/13] feat: update operation forms to use SwitchChain container --- .../BorrowForm/SubmitSection/index.tsx | 44 ++++-- .../Page/OperationForm/BorrowForm/index.tsx | 132 ++++++++-------- .../RepayForm/SubmitSection/index.tsx | 36 +++-- .../Page/OperationForm/RepayForm/index.tsx | 146 +++++++++--------- .../WithdrawForm/SubmitSection/index.tsx | 44 ++++-- .../Page/OperationForm/WithdrawForm/index.tsx | 112 +++++++------- 6 files changed, 264 insertions(+), 250 deletions(-) diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx index ab47c26417..97b4eeab81 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js'; import { useMemo } from 'react'; import { PrimaryButton } from 'components'; +import { SwitchChain } from 'containers/SwitchChain'; import { useTranslation } from 'libs/translations'; import { cn } from 'utilities'; import { ApproveDelegateSteps, type ApproveDelegateStepsProps } from '../../ApproveDelegateSteps'; @@ -42,25 +43,34 @@ export const SubmitSection: React.FC = ({ return t('operationForm.submitButtonLabel.borrow'); }, [isFormValid, t]); - return ( - - - {submitButtonLabel} - - + {submitButtonLabel} + ); + + if (isFormValid) { + return ( + + + {dom} + + + ); + } + + return dom; }; export default SubmitSection; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx index 948a3c8524..741cb47eaf 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx @@ -168,82 +168,76 @@ export const BorrowFormUi: React.FC = ({ } /> - {isUserConnected ? ( - <> - {!isSubmitting && !formError && ( - - )} - - - {readableLimit} - - - - - {canUnwrapToNativeToken && ( - <> - - - - - - - )} - -
-
- } + + + {!isSubmitting && !formError && ( + + )} + + + {readableLimit} + + + + + {canUnwrapToNativeToken && ( + <> + + + - + + + )} - -
- - +
+ + + + +
- - ) : ( -
- - {t('operationForm.connectWalletButtonLabel')} +
- )} + ); }; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/SubmitSection/index.tsx index efcf0d9738..4c1c145053 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/SubmitSection/index.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'libs/translations'; import type { Swap, Token } from 'types'; import { cn } from 'utilities'; +import { SwitchChain } from 'containers/SwitchChain'; import SwapSummary from '../../SwapSummary'; export interface SubmitSectionProps { @@ -53,17 +54,8 @@ export const SubmitSection: React.FC = ({ return t('operationForm.submitButtonLabel.repay'); }, [isFormValid, t]); - return ( - + const dom = ( + <> = ({ {isFormValid && !isSwapLoading && !isFromTokenWalletSpendingLimitLoading && ( )} - + ); + + if (isFormValid) { + return ( + + + {dom} + + + ); + } + + return dom; }; export default SubmitSection; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx index c1bb4c1d52..ead4c7d5b8 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/index.tsx @@ -297,90 +297,84 @@ export const RepayFormUi: React.FC = ({ ))}
- {isUserConnected ? ( - <> - {!isSubmitting && !isSwapLoading && !formError && ( - - )} - -
- - {isUsingSwap - ? readableFromTokenUserWalletBalanceTokens - : readableRepayableFromTokenAmountTokens} - - - } + + + {!isSubmitting && !isSwapLoading && !formError && ( + + )} + +
+ + {isUsingSwap + ? readableFromTokenUserWalletBalanceTokens + : readableRepayableFromTokenAmountTokens} + + + +
+ + + + {isUsingSwap && swap && ( + <> + + + + + )} + +
+
+ -
- - - {isUsingSwap && swap && ( - <> - - - - - )} - -
-
- - - - - -
- - + +
- - ) : ( -
- - {t('operationForm.connectWalletButtonLabel')} +
- )} + ); }; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/SubmitSection/index.tsx index 89d2320f48..642e95f0b0 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/SubmitSection/index.tsx @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { PrimaryButton } from 'components'; +import { SwitchChain } from 'containers/SwitchChain'; import { useTranslation } from 'libs/translations'; import { ApproveDelegateSteps, type ApproveDelegateStepsProps } from '../../ApproveDelegateSteps'; @@ -31,25 +32,34 @@ export const SubmitSection: React.FC = ({ return t('operationForm.submitButtonLabel.withdraw'); }, [isFormValid, t]); - return ( - - - {submitButtonLabel} - - + {submitButtonLabel} + ); + + if (isFormValid) { + return ( + + + {dom} + + + ); + } + + return dom; }; export default SubmitSection; diff --git a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx index 7f51f78185..2fd6c59744 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx @@ -174,72 +174,66 @@ export const WithdrawFormUi: React.FC = ({ } /> - {isUserConnected ? ( - <> - - {readableWithdrawableAmountTokens} - - - - - {canUnwrapToNativeToken && ( - <> - - - - - - - )} - -
-
- } + + + + {readableWithdrawableAmountTokens} + + + + + {canUnwrapToNativeToken && ( + <> + + + - + + + )} - -
- - +
+ + + + +
- - ) : ( -
- - {t('operationForm.connectWalletButtonLabel')} +
- )} + ); }; From 720b2cb4fdd0f78e973bfcac0c72fb19ed8fce24 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Fri, 21 Feb 2025 10:00:45 +0100 Subject: [PATCH 04/13] chore: add tests to SwitchChain container --- .../ConnectWallet/__tests__/index.spec.tsx | 3 +- .../SwitchChain/__tests__/index.spec.tsx | 70 +++++++++++++++++++ apps/evm/src/containers/SwitchChain/index.tsx | 2 +- .../libs/translations/translations/en.json | 14 ++-- 4 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 apps/evm/src/containers/SwitchChain/__tests__/index.spec.tsx diff --git a/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx b/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx index f3c1110bae..1363a2837d 100644 --- a/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx +++ b/apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx @@ -3,14 +3,13 @@ import type { Mock } from 'vitest'; import fakeAccountAddress from '__mocks__/models/address'; import { en } from 'libs/translations'; -import { useAuthModal, useSwitchChain } from 'libs/wallet'; +import { useAuthModal } from 'libs/wallet'; import { renderComponent } from 'testUtils/render'; import { ConnectWallet } from '..'; describe('ConnectWallet', () => { beforeEach(() => { (useAuthModal as Mock).mockReturnValue({ openAuthModal: vi.fn() }); - (useSwitchChain as Mock).mockReturnValue({ switchChain: vi.fn() }); }); it('renders without crashing', () => { diff --git a/apps/evm/src/containers/SwitchChain/__tests__/index.spec.tsx b/apps/evm/src/containers/SwitchChain/__tests__/index.spec.tsx new file mode 100644 index 0000000000..0569fa40c8 --- /dev/null +++ b/apps/evm/src/containers/SwitchChain/__tests__/index.spec.tsx @@ -0,0 +1,70 @@ +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import type { Mock } from 'vitest'; + +import { ChainId } from '@venusprotocol/chains'; +import { chainMetadata } from '@venusprotocol/chains'; +import fakeAddress from '__mocks__/models/address'; +import { en } from 'libs/translations'; +import { useSwitchChain } from 'libs/wallet'; +import { renderComponent } from 'testUtils/render'; +import { SwitchChain } from '..'; + +const fakeContent = 'Fake content'; + +describe('SwitchChain', () => { + beforeEach(() => { + (useSwitchChain as Mock).mockReturnValue({ switchChain: vi.fn() }); + }); + + it('renders without SwitchChain', () => { + renderComponent(); + }); + + it('displays children when user is not connected', async () => { + renderComponent({fakeContent}); + + expect(screen.queryByText(fakeContent)).toBeInTheDocument(); + }); + + it('displays switch button when user is connected to the wrong chain', async () => { + renderComponent({fakeContent}, { + accountAddress: fakeAddress, + chainId: ChainId.ETHEREUM, + }); + + expect(screen.queryByText(fakeContent)).not.toBeInTheDocument(); + + expect( + screen.queryByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(); + }); + + it('calls switchChain when switch button is clicked', async () => { + const mockSwitchChain = vi.fn(); + (useSwitchChain as Mock).mockReturnValue({ switchChain: mockSwitchChain }); + + renderComponent({fakeContent}, { + accountAddress: fakeAddress, + chainId: ChainId.ETHEREUM, + }); + + fireEvent.click( + screen.getByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ); + + await waitFor(() => expect(mockSwitchChain).toHaveBeenCalledTimes(1)); + expect(mockSwitchChain).toHaveBeenCalledWith({ + chainId: ChainId.BSC_TESTNET, + }); + }); +}); diff --git a/apps/evm/src/containers/SwitchChain/index.tsx b/apps/evm/src/containers/SwitchChain/index.tsx index 6ca979bf5f..0580b32848 100644 --- a/apps/evm/src/containers/SwitchChain/index.tsx +++ b/apps/evm/src/containers/SwitchChain/index.tsx @@ -31,7 +31,7 @@ export const SwitchChain: React.FC = ({ children, chainId, ...
{isUserConnected && isOnWrongChain ? ( diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index f746af3dd6..3ae9837a52 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -106,9 +106,6 @@ "pointDistribution": { "learnMore": "Learn more" }, - "pointDistributions": { - "title": "Point distributions" - }, "primeDistribution": { "description": "Venus Prime rewards dedicated users with boosted rewards. The Venus protocol does not guarantee these rewards and accepts no liability.", "name": "Prime APY" @@ -263,8 +260,7 @@ "disconnect": "Disconnect" }, "connectWallet": { - "connectButton": "Connect wallet", - "switchChain": "Switch to {{chainName}}" + "connectButton": "Connect wallet" }, "convertVrt": { "connectWalletToWithdrawXvs": "Please connect your wallet to withdraw XVS", @@ -402,6 +398,7 @@ }, "price": "Price", "supply": "Supply", + "unichainBackgroundIllustrationAlt": "Background illustration", "utilizationRate": "Utilization rate" }, "menu": { @@ -534,7 +531,6 @@ "operationForm": { "borrowableAmount": "Borrowable Amount", "borrowTabTitle": "Borrow", - "connectWalletButtonLabel": "Connect wallet", "error": { "borrowCapReached": "The borrow cap of {{assetBorrowCap}} has been reached for this pool. You can not borrow from this market anymore until loans are repaid or its borrow cap is increased.", "higherThanAvailableLiquidity": "Insufficient asset liquidity", @@ -810,6 +806,9 @@ }, "walletBalance": "Wallet balance" }, + "switchChain": { + "switchButton": "Switch to {{chainName}}" + }, "table": { "cardsSelect": { "label": "Sort by" @@ -1155,9 +1154,6 @@ "execute": "Execute", "queue": "Queue" }, - "cta": { - "execute": "Execute" - }, "dates": { "activeAt": "Pending until {{date, }}", "executableAt": "Executable at {{date, }}", From 2be3e7adf57db41d769deb1b564a907a720eb901 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Fri, 21 Feb 2025 10:12:57 +0100 Subject: [PATCH 05/13] chore: add tests to operation forms --- apps/evm/src/containers/SwitchChain/index.tsx | 2 -- .../BorrowForm/__tests__/index.spec.tsx | 33 +++++++++++++++++-- .../__tests__/indexWrapUnwrapNative.spec.tsx | 12 ------- .../RepayForm/__tests__/index.spec.tsx | 32 +++++++++++++++++- .../__tests__/indexIntegratedSwap.spec.tsx | 18 ---------- .../SupplyForm/__tests__/index.spec.tsx | 33 +++++++++++++++++-- .../__tests__/indexIntegratedSwap.spec.tsx | 21 ------------ .../WithdrawForm/__tests__/index.spec.tsx | 33 +++++++++++++++++-- .../__tests__/indexWrapUnwrapNative.spec.tsx | 12 ------- .../pages/Proposal/__tests__/index.spec.tsx | 2 +- apps/evm/src/testUtils/render.tsx | 3 +- 11 files changed, 127 insertions(+), 74 deletions(-) diff --git a/apps/evm/src/containers/SwitchChain/index.tsx b/apps/evm/src/containers/SwitchChain/index.tsx index 0580b32848..02054fc23a 100644 --- a/apps/evm/src/containers/SwitchChain/index.tsx +++ b/apps/evm/src/containers/SwitchChain/index.tsx @@ -5,8 +5,6 @@ import { useTranslation } from 'libs/translations'; import { useAccountAddress, useAccountChainId, useChainId, useSwitchChain } from 'libs/wallet'; import type { ChainId } from 'types'; -// TODO: add tests - export interface SwitchChainProps extends React.HTMLAttributes { chainId?: ChainId; } diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/index.spec.tsx index 78477e3146..3c66e6e7e8 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/index.spec.tsx @@ -10,8 +10,9 @@ import { renderComponent } from 'testUtils/render'; import { borrow } from 'clients/api'; import { SAFE_BORROW_LIMIT_PERCENTAGE } from 'constants/safeBorrowLimitPercentage'; import { en } from 'libs/translations'; -import type { Asset, Pool } from 'types'; +import { type Asset, ChainId, type Pool } from 'types'; +import { chainMetadata } from '@venusprotocol/chains'; import BorrowForm from '..'; import { fakeAsset, fakePool } from '../__testUtils__/fakeData'; import TEST_IDS from '../testIds'; @@ -35,7 +36,7 @@ describe('BorrowForm', () => { ); // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); + expect(getByText(en.connectWallet.connectButton)).toBeInTheDocument(); // Check input is disabled expect(getByTestId(TEST_IDS.tokenTextField).closest('input')).toBeDisabled(); @@ -245,6 +246,34 @@ describe('BorrowForm', () => { await checkSubmitButtonIsDisabled(); }); + it('prompts user to switch chain if they are connected to the wrong one', async () => { + const { getByText, getByTestId } = renderComponent( + , + { + accountAddress: fakeAccountAddress, + accountChainId: ChainId.ARBITRUM_ONE, + chainId: ChainId.BSC_TESTNET, + }, + ); + + const correctAmountTokens = 1; + + const tokenTextInput = await waitFor(() => getByTestId(TEST_IDS.tokenTextField)); + fireEvent.change(tokenTextInput, { target: { value: correctAmountTokens } }); + + // Check "Switch chain" button is displayed + await waitFor(() => + expect( + getByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(), + ); + }); + it('displays warning notice if amount to borrow requested would bring user borrow balance at safe borrow limit', async () => { const { getByText, getByTestId } = renderComponent( , diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx index 2534994209..c5ba5bdcf5 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx @@ -30,18 +30,6 @@ describe('BorrowForm - Feature flag enabled: wrapUnwrapNativeToken', () => { }); }); - it('prompts user to connect their wallet if they are not connected', async () => { - const { getByText, getByTestId } = renderComponent( - , - ); - - // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); - - // Check input is disabled - expect(getByTestId(TEST_IDS.tokenTextField)).toBeDisabled(); - }); - it('does not display the receive native token toggle if the underlying token does not wrap the chain native token', async () => { const { queryByTestId } = renderComponent( , diff --git a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/index.spec.tsx index f8f3d6b65d..5603d7d084 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/index.spec.tsx @@ -14,6 +14,8 @@ import { repay } from 'clients/api'; import useTokenApproval from 'hooks/useTokenApproval'; import { en } from 'libs/translations'; +import { chainMetadata } from '@venusprotocol/chains'; +import { ChainId } from 'types'; import Repay, { PRESET_PERCENTAGES } from '..'; import { fakeAsset, fakePool } from '../__testUtils__/fakeData'; import TEST_IDS from '../testIds'; @@ -47,7 +49,7 @@ describe('RepayForm', () => { ); // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); + expect(getByText(en.connectWallet.connectButton)).toBeInTheDocument(); // Check input is disabled expect(getByTestId(TEST_IDS.tokenTextField).closest('input')).toBeDisabled(); @@ -175,6 +177,34 @@ describe('RepayForm', () => { await checkSubmitButtonIsDisabled(); }); + it('prompts user to switch chain if they are connected to the wrong one', async () => { + const { getByText, getByTestId } = renderComponent( + , + { + accountAddress: fakeAccountAddress, + accountChainId: ChainId.ARBITRUM_ONE, + chainId: ChainId.BSC_TESTNET, + }, + ); + + const correctAmountTokens = 1; + + const tokenTextInput = await waitFor(() => getByTestId(TEST_IDS.tokenTextField)); + fireEvent.change(tokenTextInput, { target: { value: correctAmountTokens } }); + + // Check "Switch chain" button is displayed + await waitFor(() => + expect( + getByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(), + ); + }); + it('displays the wallet spending limit correctly and lets user revoke it', async () => { const originalTokenApprovalOutput = useTokenApproval({ token: xvs, diff --git a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/indexIntegratedSwap.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/indexIntegratedSwap.spec.tsx index 9fa910a6a0..e16f435aef 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/indexIntegratedSwap.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/RepayForm/__tests__/indexIntegratedSwap.spec.tsx @@ -106,24 +106,6 @@ describe('RepayForm - Feature flag enabled: integratedSwap', () => { renderComponent(); }); - it('prompts user to connect their wallet if they are not connected', async () => { - const { getByText, getByTestId } = renderComponent( - , - ); - - // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); - - // Check input is disabled - expect( - getByTestId( - getTokenTextFieldTestId({ - parentTestId: TEST_IDS.selectTokenTextField, - }), - ), - ).toBeDisabled(); - }); - it('displays correct wallet balance', async () => { const { getByText, container } = renderComponent( , diff --git a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/index.spec.tsx index 32fd330055..4c009048d6 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/index.spec.tsx @@ -13,8 +13,9 @@ import { supply } from 'clients/api'; import useCollateral from 'hooks/useCollateral'; import useTokenApproval from 'hooks/useTokenApproval'; import { en } from 'libs/translations'; -import type { Asset } from 'types'; +import { type Asset, ChainId } from 'types'; +import { chainMetadata } from '@venusprotocol/chains'; import MAX_UINT256 from 'constants/maxUint256'; import SupplyForm from '..'; import { fakeAsset, fakePool } from '../__testUtils__/fakeData'; @@ -46,7 +47,7 @@ describe('SupplyForm', () => { ); // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); + expect(getByText(en.connectWallet.connectButton)).toBeInTheDocument(); // Check collateral switch is disabled expect(getByRole('checkbox')).toBeDisabled(); @@ -225,6 +226,34 @@ describe('SupplyForm', () => { await checkSubmitButtonIsDisabled(); }); + it('prompts user to switch chain if they are connected to the wrong one', async () => { + const { getByText, getByTestId } = renderComponent( + , + { + accountAddress: fakeAccountAddress, + accountChainId: ChainId.ARBITRUM_ONE, + chainId: ChainId.BSC_TESTNET, + }, + ); + + const correctAmountTokens = 1; + + const tokenTextInput = await waitFor(() => getByTestId(TEST_IDS.tokenTextField)); + fireEvent.change(tokenTextInput, { target: { value: correctAmountTokens } }); + + // Check "Switch chain" button is displayed + await waitFor(() => + expect( + getByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(), + ); + }); + it('displays the wallet spending limit correctly and lets user revoke it', async () => { const originalTokenApprovalOutput = useTokenApproval({ token: xvs, diff --git a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx index c55633b171..75e2505821 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx @@ -94,27 +94,6 @@ describe('SupplyForm - Feature flag enabled: integratedSwap', () => { renderComponent(); }); - it('prompts user to connect their wallet if they are not connected', async () => { - const { getByText, getByTestId, getByRole } = renderComponent( - , - ); - - // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); - - // Check collateral switch is disabled - expect(getByRole('checkbox')).toBeDisabled(); - - // Check input is disabled - expect( - getByTestId( - getTokenTextFieldTestId({ - parentTestId: TEST_IDS.selectTokenTextField, - }), - ), - ).toBeDisabled(); - }); - it('disables swap feature when swapAndSupply action of asset is disabled', async () => { const customFakeAsset: Asset = { ...fakeAsset, diff --git a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/index.spec.tsx index f470e79cbb..d90f53a97a 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/index.spec.tsx @@ -9,8 +9,9 @@ import { renderComponent } from 'testUtils/render'; import { getVTokenBalanceOf, withdraw } from 'clients/api'; import { en } from 'libs/translations'; -import type { Asset, Pool } from 'types'; +import { type Asset, ChainId, type Pool } from 'types'; +import { chainMetadata } from '@venusprotocol/chains'; import Withdraw from '..'; import { fakeAsset, fakePool, fakeVTokenBalanceMantissa } from '../__testUtils__/fakeData'; import TEST_IDS from '../testIds'; @@ -22,7 +23,7 @@ describe('WithdrawForm', () => { ); // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); + expect(getByText(en.connectWallet.connectButton)).toBeInTheDocument(); // Check input is disabled expect(getByTestId(TEST_IDS.valueInput).closest('input')).toBeDisabled(); @@ -120,6 +121,34 @@ describe('WithdrawForm', () => { expect(submitButton).toBeDisabled(); }); + it('prompts user to switch chain if they are connected to the wrong one', async () => { + const { getByText, getByTestId } = renderComponent( + , + { + accountAddress: fakeAccountAddress, + accountChainId: ChainId.ARBITRUM_ONE, + chainId: ChainId.BSC_TESTNET, + }, + ); + + const correctAmountTokens = 1; + + const tokenTextInput = await waitFor(() => getByTestId(TEST_IDS.valueInput)); + fireEvent.change(tokenTextInput, { target: { value: correctAmountTokens } }); + + // Check "Switch chain" button is displayed + await waitFor(() => + expect( + getByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(), + ); + }); + it('displays correct token withdrawable amount', async () => { const { getByText } = renderComponent( , diff --git a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx index 8e9a0e0c2a..fead4e95f7 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx @@ -31,18 +31,6 @@ describe('WithdrawForm - Feature flag enabled: wrapUnwrapNativeToken', () => { }); }); - it('prompts user to connect their wallet if they are not connected', async () => { - const { getByText, getByTestId } = renderComponent( - , - ); - - // Check "Connect wallet" button is displayed - expect(getByText(en.operationForm.connectWalletButtonLabel)).toBeInTheDocument(); - - // Check input is disabled - expect(getByTestId(TEST_IDS.valueInput).closest('input')).toBeDisabled(); - }); - it('does not display the receive native token toggle if the underlying token does not wrap the chain native token', async () => { const { queryByTestId } = renderComponent( , diff --git a/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx b/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx index 4bcdf3d490..7bb47d0150 100644 --- a/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx @@ -702,7 +702,7 @@ describe('ProposalComp page', () => { }); const executeButton = screen - .getAllByText(en.voteProposalUi.command.cta.execute)[0] + .getAllByText(en.voteProposalUi.command.actionButton.execute)[0] .closest('button'); expect(executeButton).toBeInTheDocument(); diff --git a/apps/evm/src/testUtils/render.tsx b/apps/evm/src/testUtils/render.tsx index 49498651eb..5271ba059c 100644 --- a/apps/evm/src/testUtils/render.tsx +++ b/apps/evm/src/testUtils/render.tsx @@ -29,6 +29,7 @@ const createQueryClient = () => interface Options { accountAddress?: string; + accountChainId?: ChainId; chainId?: ChainId; routerInitialEntries?: string[]; routePath?: string; @@ -51,7 +52,7 @@ const Wrapper: React.FC = ({ children, options }) => { })); (useAccountChainId as Mock).mockImplementation(() => ({ - chainId, + chainId: options?.accountChainId || chainId, })); (useSigner as Mock).mockImplementation(() => ({ From 6fa65fa57753d93a76ae1442ff115380c5acc1a5 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Fri, 21 Feb 2025 13:52:43 +0100 Subject: [PATCH 06/13] feat: take account chain ID in consideration to display warning on Proposal page --- .../libs/translations/translations/en.json | 4 +- .../pages/Proposal/__tests__/index.spec.tsx | 11 +++++ apps/evm/src/pages/Proposal/index.tsx | 43 +++++++++++-------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index 3ae9837a52..6713fa03e2 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -1107,8 +1107,8 @@ "for": "For", "goToXvsSnapshot": "Go to XVS Snapshot", "omnichain": { - "switchToBnb": "Switch to BNB chain", - "votingOnlyEnabledOnBnb": "Voting is only available on the BNB chain" + "switchToBnb": "Switch to BNB Chain", + "votingOnlyEnabledOnBnb": "Voting is only available on BNB Chain" }, "pages": { "actions": "3 of 3 Actions", diff --git a/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx b/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx index 7bb47d0150..354e847362 100644 --- a/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Proposal/__tests__/index.spec.tsx @@ -221,6 +221,17 @@ describe('ProposalComp page', () => { await waitFor(() => expect(screen.getByTestId(TEST_IDS.votingDisabledWarning)).toBeVisible()); }); + it('renders warning about voting being disabled when the feature flag is on, proposal is active and user is connected to another chain than the governance one', async () => { + (useIsFeatureEnabled as Mock).mockImplementation(() => true); + + renderComponent(, { + accountAddress: fakeAccountAddress, + accountChainId: ChainId.ARBITRUM_SEPOLIA, + }); + + await waitFor(() => expect(screen.getByTestId(TEST_IDS.votingDisabledWarning)).toBeVisible()); + }); + it('allows user to vote for', async () => { const vote = vi.fn(); (useVote as Mock).mockImplementation(() => ({ diff --git a/apps/evm/src/pages/Proposal/index.tsx b/apps/evm/src/pages/Proposal/index.tsx index 8bd23d96f1..6d2cbe0a29 100644 --- a/apps/evm/src/pages/Proposal/index.tsx +++ b/apps/evm/src/pages/Proposal/index.tsx @@ -10,7 +10,7 @@ import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import useVote, { type UseVoteParams } from 'hooks/useVote'; import { useGetToken } from 'libs/tokens'; import { useTranslation } from 'libs/translations'; -import { governanceChain, useAccountAddress, useSwitchChain } from 'libs/wallet'; +import { governanceChain, useAccountAddress, useAccountChainId, useSwitchChain } from 'libs/wallet'; import { ProposalState, type Proposal as ProposalType } from 'types'; import { convertMantissaToTokens } from 'utilities'; @@ -40,6 +40,14 @@ export const ProposalUi: React.FC = ({ isVoteLoading, }) => { const { switchChain } = useSwitchChain(); + + const { accountAddress } = useAccountAddress(); + const isUserConnected = !!accountAddress; + + const { chainId: accountChainId } = useAccountChainId(); + const isUserConnectedToNonGovernanceChain = + isUserConnected && accountChainId !== governanceChain.id; + const isVoteProposalFeatureEnabled = useIsFeatureEnabled({ name: 'voteProposal' }); const styles = useStyles(); const { t } = useTranslation(); @@ -58,22 +66,23 @@ export const ProposalUi: React.FC = ({
- {!isVoteProposalFeatureEnabled && proposal.state === ProposalState.Active && ( - switchChain({ chainId: governanceChain.id })} - > - {t('vote.omnichain.switchToBnb')} - - } - /> - )} + {proposal.state === ProposalState.Active && + (!isVoteProposalFeatureEnabled || isUserConnectedToNonGovernanceChain) && ( + switchChain({ chainId: governanceChain.id })} + > + {t('vote.omnichain.switchToBnb')} + + } + /> + )}
Date: Fri, 21 Feb 2025 13:57:17 +0100 Subject: [PATCH 07/13] feat: feat: update Swap page to use SwitchChain container --- .../src/pages/Swap/SubmitSection/index.tsx | 78 ++++++++++++------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/apps/evm/src/pages/Swap/SubmitSection/index.tsx b/apps/evm/src/pages/Swap/SubmitSection/index.tsx index 4f271b4594..c36f1aa68e 100644 --- a/apps/evm/src/pages/Swap/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Swap/SubmitSection/index.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'libs/translations'; import type { Swap, SwapError } from 'types'; import { cn } from 'utilities'; +import { SwitchChain } from 'containers/SwitchChain'; import type { FormError, FormValues } from '../types'; import { useStyles } from './styles'; @@ -93,35 +94,58 @@ const SubmitSection: React.FC = ({ return t('swapPage.submitButton.disabledLabels.processing'); }, [swap, swapError, isSwappingWithHighPriceImpact, formErrors, fromToken.symbol, t]); - return ( - - - {submitButtonLabel} - - + {submitButtonLabel} + ); + + if (isFormValid) { + return ( + + + + {dom} + + + + ); + } + + return dom; }; export default SubmitSection; From da0fb030211d07162ef025f501c91a22ea127e1a Mon Sep 17 00:00:00 2001 From: therealemjy Date: Fri, 21 Feb 2025 14:34:32 +0100 Subject: [PATCH 08/13] feat: feat: update Vault page to use SwitchChain container --- .../BorrowForm/SubmitSection/index.tsx | 4 +- .../RepayForm/SubmitSection/index.tsx | 4 +- .../SupplyForm/SubmitSection/index.tsx | 4 +- .../WithdrawForm/SubmitSection/index.tsx | 4 +- .../src/pages/Swap/SubmitSection/index.tsx | 20 +----- .../TransactionForm/SubmitSection/index.tsx | 72 +++++++++++++++++++ .../Vault/TransactionForm/index.spec.tsx | 26 +++++++ .../src/pages/Vault/TransactionForm/index.tsx | 64 ++++++----------- .../__tests__/index.spec.tsx | 14 ++++ .../Withdraw/index.spec.tsx | 33 +++++++++ .../Withdraw/index.tsx | 21 +++--- 11 files changed, 190 insertions(+), 76 deletions(-) create mode 100644 apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx index 97b4eeab81..542bb38488 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/SubmitSection/index.tsx @@ -43,7 +43,7 @@ export const SubmitSection: React.FC = ({ return t('operationForm.submitButtonLabel.borrow'); }, [isFormValid, t]); - const dom = ( + let dom = ( = ({ ); if (isFormValid) { - return ( + dom = ( = ({ return t('operationForm.submitButtonLabel.repay'); }, [isFormValid, t]); - const dom = ( + let dom = ( <> = ({ ); if (isFormValid) { - return ( + dom = ( = ({ return t('operationForm.submitButtonLabel.supply'); }, [isFormValid, t]); - const dom = ( + let dom = ( <> = ({ ); if (isFormValid) { - return ( + dom = ( = ({ return t('operationForm.submitButtonLabel.withdraw'); }, [isFormValid, t]); - const dom = ( + let dom = ( = ({ ); if (isFormValid) { - return ( + dom = ( = ({ return t('swapPage.submitButton.disabledLabels.processing'); }, [swap, swapError, isSwappingWithHighPriceImpact, formErrors, fromToken.symbol, t]); - const dom = ( + let dom = ( = ({ ); if (isFormValid) { - return ( + dom = ( = ({ secondStepButtonLabel={submitButtonLabel} css={styles.container} > - - {dom} - + {dom} ); diff --git a/apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx b/apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx new file mode 100644 index 0000000000..ab20d683da --- /dev/null +++ b/apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx @@ -0,0 +1,72 @@ +import { ApproveTokenSteps } from 'components'; +import { FormikSubmitButton } from 'containers/Form'; +import { SwitchChain } from 'containers/SwitchChain'; +import type { Token } from 'types'; +import { cn } from 'utilities'; + +export interface SubmitSectionProps { + token: Token; + approveToken: () => Promise; + tokenNeedsToBeApproved: boolean; + isFormValid: boolean; + isSubmitting: boolean; + isTokenApproved: boolean; + isApproveTokenLoading: boolean; + isWalletSpendingLimitLoading: boolean; + isRevokeWalletSpendingLimitLoading: boolean; + submitButtonEnabledLabel: string; + submitButtonDisabledLabel: string; + isDangerousAction: boolean; +} + +export const SubmitSection: React.FC = ({ + token, + tokenNeedsToBeApproved, + approveToken, + isFormValid, + isSubmitting, + isTokenApproved, + isApproveTokenLoading, + isWalletSpendingLimitLoading, + isRevokeWalletSpendingLimitLoading, + submitButtonEnabledLabel, + submitButtonDisabledLabel, + isDangerousAction, +}) => { + let dom = ( + + ); + + if (isFormValid) { + dom = {dom}; + } + + if (isFormValid && tokenNeedsToBeApproved) { + dom = ( + + {dom} + + ); + } + + return dom; +}; diff --git a/apps/evm/src/pages/Vault/TransactionForm/index.spec.tsx b/apps/evm/src/pages/Vault/TransactionForm/index.spec.tsx index ba559a3c0b..8e504e3bda 100644 --- a/apps/evm/src/pages/Vault/TransactionForm/index.spec.tsx +++ b/apps/evm/src/pages/Vault/TransactionForm/index.spec.tsx @@ -10,7 +10,9 @@ import { renderComponent } from 'testUtils/render'; import useTokenApproval from 'hooks/useTokenApproval'; +import { ChainId, chainMetadata } from '@venusprotocol/chains'; import { NULL_ADDRESS } from 'constants/address'; +import { en } from 'libs/translations'; import TransactionForm, { type TransactionFormProps } from '.'; import TEST_IDS from './testIds'; @@ -39,6 +41,30 @@ describe('TransactionForm', () => { expect(getByTestId(TEST_IDS.lockingPeriod).textContent).toMatchSnapshot(); }); + it('prompts user to switch chain if they are connected to the wrong one', async () => { + const { queryByText, getByTestId } = renderComponent(, { + accountAddress: fakeAccountAddress, + accountChainId: ChainId.SEPOLIA, + chainId: ChainId.BSC_TESTNET, + }); + + fireEvent.change(getByTestId(TEST_IDS.tokenTextField), { + target: { value: 1 }, + }); + + // Check switch button is present + await waitFor(() => + expect( + queryByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(), + ); + }); + it('displays the wallet spending limit correctly and lets user revoke it', async () => { const originalTokenApprovalOutput = useTokenApproval({ token: vai, diff --git a/apps/evm/src/pages/Vault/TransactionForm/index.tsx b/apps/evm/src/pages/Vault/TransactionForm/index.tsx index 047c00f936..e66297b2bf 100644 --- a/apps/evm/src/pages/Vault/TransactionForm/index.tsx +++ b/apps/evm/src/pages/Vault/TransactionForm/index.tsx @@ -2,7 +2,6 @@ import BigNumber from 'bignumber.js'; import { useCallback, useMemo } from 'react'; import { - ApproveTokenSteps, type ApproveTokenStepsProps, LabeledInlineContent, NoticeWarning, @@ -15,10 +14,11 @@ import { handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; import type { Token } from 'types'; -import { cn, convertMantissaToTokens, convertTokensToMantissa } from 'utilities'; +import { convertMantissaToTokens, convertTokensToMantissa } from 'utilities'; -import { FormikSubmitButton, FormikTokenTextField } from 'containers/Form'; +import { FormikTokenTextField } from 'containers/Form'; import type { Address } from 'viem'; +import { SubmitSection } from './SubmitSection'; import TEST_IDS from './testIds'; export interface TransactionFormUiProps { @@ -101,7 +101,7 @@ export const TransactionFormUi: React.FC = ({ const shouldDisplayWarning = useCallback( (amountTokens: string) => - !!amountTokens && warning?.amountTokens.isLessThanOrEqualTo(amountTokens), + amountTokens && warning ? warning.amountTokens.isLessThanOrEqualTo(amountTokens) : false, [warning], ); @@ -174,44 +174,24 @@ export const TransactionFormUi: React.FC = ({ )}
- {tokenNeedsToBeApproved ? ( - - - - ) : ( - - )} +
)} diff --git a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/RequestWithdrawal/__tests__/index.spec.tsx b/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/RequestWithdrawal/__tests__/index.spec.tsx index 62865d6437..b8a31b9edc 100644 --- a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/RequestWithdrawal/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/RequestWithdrawal/__tests__/index.spec.tsx @@ -54,6 +54,20 @@ describe('RequestWithdrawal', () => { await waitFor(() => getByTestId(TEST_IDS.availableTokens)); }); + it('prompts user to connect their wallet if they are not connected', async () => { + const { queryByText } = renderComponent( + , + ); + + // Check connect button is present + await waitFor(() => expect(queryByText(en.connectWallet.connectButton)).toBeInTheDocument()); + }); + it('fetches staked tokens and locking period and displays them correctly', async () => { const { getByTestId } = renderComponent( { await waitFor(() => getByText(en.withdrawFromVestingVaultModalModal.withdrawTab.submitButton)); }); + it('prompts user to connect their wallet if they are not connected', async () => { + const { queryByText } = renderComponent( + , + ); + + // Check connect button is present + await waitFor(() => expect(queryByText(en.connectWallet.connectButton)).toBeInTheDocument()); + }); + + it('prompts user to switch chain if they are connected to the wrong one', async () => { + const { queryByText } = renderComponent( + , + { + accountAddress: fakeAddress, + accountChainId: ChainId.SEPOLIA, + chainId: ChainId.BSC_TESTNET, + }, + ); + + // Check switch button is present + await waitFor(() => + expect( + queryByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(), + ); + }); + it('fetches available tokens amount and displays it correctly', async () => { const { getByTestId } = renderComponent( , diff --git a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx b/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx index aab07bcef7..eff12c6e81 100644 --- a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx +++ b/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx @@ -12,6 +12,7 @@ import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; import type { Token } from 'types'; +import { SwitchChain } from 'containers/SwitchChain'; import { useStyles } from './styles'; import TEST_IDS from './testIds'; @@ -63,15 +64,17 @@ const WithdrawUi: React.FC = ({ {readableWithdrawableTokens} - - {t('withdrawFromVestingVaultModalModal.withdrawTab.submitButton')} - + + + {t('withdrawFromVestingVaultModalModal.withdrawTab.submitButton')} + + )} From 966ef3c4c8a0621e28da3df0e1376ce1248d5738 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Fri, 21 Feb 2025 14:49:44 +0100 Subject: [PATCH 09/13] feat: feat: update Vai page to use SwitchChain container --- .../containers/Form/RhfSubmitButton/index.tsx | 26 ++++++++++++++++--- apps/evm/src/pages/Vai/Repay/index.tsx | 1 - 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx b/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx index 1f9046d7c4..29b4d78688 100644 --- a/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx +++ b/apps/evm/src/containers/Form/RhfSubmitButton/index.tsx @@ -4,6 +4,8 @@ import { type ButtonProps, PrimaryButton } from 'components'; import { ConnectWallet } from 'containers/ConnectWallet'; import { cn } from 'utilities'; +import type { ChainId } from '@venusprotocol/chains'; +import { SwitchChain } from 'containers/SwitchChain'; import { ApproveTokenSteps, type ApproveTokenStepsProps } from './ApproveTokenSteps'; export interface RhfSubmitButtonProps extends ButtonProps { @@ -11,7 +13,11 @@ export interface RhfSubmitButtonProps extends ButtonProps { enabledLabel: string; disabledLabel: string; isDangerousSubmission?: boolean; - requiresConnectedWallet?: boolean; + requiresConnectedWallet?: + | boolean + | { + chainId: ChainId; + }; spendingApproval?: Omit; } @@ -41,7 +47,7 @@ export const RhfSubmitButton: React.FC = ({ ); - if (spendingApproval) { + if (formState.isValid && spendingApproval) { dom = ( {dom} @@ -49,8 +55,20 @@ export const RhfSubmitButton: React.FC = ({ ); } - if (requiresConnectedWallet || spendingApproval) { - dom = {dom}; + if (formState.isValid && (requiresConnectedWallet || spendingApproval)) { + dom = ( + + + {dom} + + + ); } return
{dom}
; diff --git a/apps/evm/src/pages/Vai/Repay/index.tsx b/apps/evm/src/pages/Vai/Repay/index.tsx index a2d34b9cc4..fb9da391fc 100644 --- a/apps/evm/src/pages/Vai/Repay/index.tsx +++ b/apps/evm/src/pages/Vai/Repay/index.tsx @@ -254,7 +254,6 @@ export const Repay: React.FC = () => { vaiControllerContractAddress && { token: vai, spenderAddress: vaiControllerContractAddress, - hideSpendingApprovalStep: !formState.isValid, } } control={control} From c9c8cd3e4a68f367e4724edb8255cd7b64ce9155 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Mon, 24 Feb 2025 11:24:32 +0100 Subject: [PATCH 10/13] feat: feat: update Bridge page to use SwitchChain container --- .../src/containers/ConnectWallet/index.tsx | 29 +- .../src/pages/Bridge/__tests__/index.spec.tsx | 24 ++ apps/evm/src/pages/Bridge/index.tsx | 255 +++++++------- .../ProposalWizard/index.tsx | 14 +- .../Page/OperationForm/BorrowForm/index.tsx | 76 +++-- .../Page/OperationForm/RepayForm/index.tsx | 281 ++++++++-------- .../Page/OperationForm/SupplyForm/index.tsx | 315 +++++++++--------- .../Page/OperationForm/WithdrawForm/index.tsx | 170 +++++----- apps/evm/src/pages/Proposal/index.tsx | 70 ++-- .../TransactionForm/SubmitSection/index.tsx | 8 +- .../Withdraw/index.tsx | 37 +- .../Withdraw/styles.ts | 12 - 12 files changed, 656 insertions(+), 635 deletions(-) delete mode 100644 apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/styles.ts diff --git a/apps/evm/src/containers/ConnectWallet/index.tsx b/apps/evm/src/containers/ConnectWallet/index.tsx index 6e54908289..5453a74d14 100644 --- a/apps/evm/src/containers/ConnectWallet/index.tsx +++ b/apps/evm/src/containers/ConnectWallet/index.tsx @@ -3,7 +3,8 @@ import { NoticeInfo } from 'components/Notice'; import { useTranslation } from 'libs/translations'; import { useAccountAddress, useAuthModal } from 'libs/wallet'; -export interface ConnectWalletProps extends React.HTMLAttributes { +export interface ConnectWalletProps + extends Omit, 'className'> { message?: string; className?: string; children?: React.ReactNode; @@ -21,17 +22,19 @@ export const ConnectWallet: React.FC = ({ const { t } = useTranslation(); - if (!isUserConnected) { - return ( -
- {!!message && } + return ( +
+ {isUserConnected ? ( + children + ) : ( + <> + {!!message && } - -
- ); - } - - return
{children}
; + + + )} +
+ ); }; diff --git a/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx b/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx index 4226c46d1f..b0c4020aa1 100644 --- a/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx @@ -16,6 +16,7 @@ import { en } from 'libs/translations'; import { useAuthModal, useChainId, useSwitchChain } from 'libs/wallet'; import { ChainId } from 'types'; +import { chainMetadata } from '@venusprotocol/chains'; import { fromUnixTime } from 'date-fns'; import Bridge from '..'; import TEST_IDS from '../testIds'; @@ -96,6 +97,29 @@ describe('Bridge', () => { await waitFor(() => expect(openAuthModalMock).toHaveBeenCalledTimes(1)); }); + it('prompts user to switch chain if they are connected to the wrong one', async () => { + const { getByText, getByTestId } = renderComponent(, { + accountAddress: fakeAccountAddress, + accountChainId: ChainId.ARBITRUM_ONE, + chainId: ChainId.BSC_TESTNET, + }); + + const tokenTextInput = await waitFor(() => getByTestId(TEST_IDS.tokenTextField)); + fireEvent.change(tokenTextInput, { target: { value: 1 } }); + + // Check "Switch chain" button is displayed + await waitFor(() => + expect( + getByText( + en.switchChain.switchButton.replace( + '{{chainName}}', + chainMetadata[ChainId.BSC_TESTNET].name, + ), + ), + ).toBeInTheDocument(), + ); + }); + it('handles changing from chain ID correctly', async () => { const { getByTestId } = renderComponent(, { chainId: ChainId.SEPOLIA, diff --git a/apps/evm/src/pages/Bridge/index.tsx b/apps/evm/src/pages/Bridge/index.tsx index 731f12d85c..8a7e56a42c 100644 --- a/apps/evm/src/pages/Bridge/index.tsx +++ b/apps/evm/src/pages/Bridge/index.tsx @@ -18,7 +18,9 @@ import { TokenTextField, } from 'components'; import { NULL_ADDRESS } from 'constants/address'; +import { ConnectWallet } from 'containers/ConnectWallet'; import { Link } from 'containers/Link'; +import { SwitchChain } from 'containers/SwitchChain'; import { useGetChainMetadata } from 'hooks/useGetChainMetadata'; import useTokenApproval from 'hooks/useTokenApproval'; import { @@ -28,9 +30,9 @@ import { import { handleError } from 'libs/errors'; import { useGetToken } from 'libs/tokens'; import { useTranslation } from 'libs/translations'; -import { useAccountAddress, useAuthModal, useChainId, useSwitchChain } from 'libs/wallet'; +import { useAccountAddress, useChainId, useSwitchChain } from 'libs/wallet'; import { ChainId } from 'types'; -import { convertMantissaToTokens, formatTokensToReadableValue } from 'utilities'; +import { cn, convertMantissaToTokens, formatTokensToReadableValue } from 'utilities'; import { ChainSelect, getOptionsFromChainsList } from './ChainSelect'; import { bridgeChains } from './constants'; import { ReactComponent as LayerZeroLogo } from './layerZeroLogo.svg'; @@ -44,7 +46,6 @@ const BridgePage: React.FC = () => { const { chainId } = useChainId(); const { nativeToken } = useGetChainMetadata(); const { switchChain } = useSwitchChain(); - const { openAuthModal } = useAuthModal(); const { accountAddress } = useAccountAddress(); const isUserConnected = !!accountAddress; @@ -290,148 +291,140 @@ const BridgePage: React.FC = () => {
-
- ( -
- { - handleChainFieldChange({ - newFromChainId: newChainId as ChainId, - }); - }} - /> -
- )} - /> +
+
+ ( +
+ { + handleChainFieldChange({ + newFromChainId: newChainId as ChainId, + }); + }} + /> +
+ )} + /> - - - + + + + + ( +
+ { + handleChainFieldChange({ + newToChainId: newChainId as ChainId, + }); + }} + /> +
+ )} + /> +
( -
- { - handleChainFieldChange({ - newToChainId: newChainId as ChainId, + render={({ field, fieldState }) => ( + { + setValue('amountTokens', walletBalanceTokens.toFixed(), { + shouldValidate: true, + shouldDirty: true, }); - }} - /> -
+ }, + }} + {...field} + disabled={field.disabled || isApproveXvsLoading || !accountAddress} + /> )} />
- ( - +
+ {errorLabel && ( + + )} + + + {readableWalletBalance} + + + { - setValue('amountTokens', walletBalanceTokens.toFixed(), { - shouldValidate: true, - shouldDirty: true, - }); - }, - }} - {...field} - disabled={field.disabled || isApproveXvsLoading || !accountAddress} + walletBalanceTokens={walletBalanceTokens} + walletSpendingLimitTokens={xvsWalletSpendingLimitTokens} + onRevoke={revokeXvsWalletSpendingLimit} + isRevokeLoading={isRevokeXvsWalletSpendingLimitLoading} /> - )} - /> - - {errorLabel && ( - - )} - -
- - {readableWalletBalance} - - - -
- - {readableFee} - - - - {isUserConnected ? ( - + {readableFee} + +
+ + + - {submitButtonLabel} - - ) : ( - - {t('bridgePage.connectWalletButton.label')} - - )} - + + {submitButtonLabel} + + + + diff --git a/apps/evm/src/pages/Governance/ProposalList/CreateProposalModal/ProposalWizard/index.tsx b/apps/evm/src/pages/Governance/ProposalList/CreateProposalModal/ProposalWizard/index.tsx index b7bc5ff236..e4f3db7e7f 100644 --- a/apps/evm/src/pages/Governance/ProposalList/CreateProposalModal/ProposalWizard/index.tsx +++ b/apps/evm/src/pages/Governance/ProposalList/CreateProposalModal/ProposalWizard/index.tsx @@ -9,6 +9,8 @@ import { useNavigate } from 'hooks/useNavigate'; import { useTranslation } from 'libs/translations'; import { FormikSubmitButton } from 'containers/Form'; +import { SwitchChain } from 'containers/SwitchChain'; +import { governanceChain } from 'libs/wallet'; import ActionAccordion from '../ActionAccordion'; import ProposalInfo from '../ProposalInfo'; import ProposalPreview from '../ProposalPreview'; @@ -171,11 +173,13 @@ const ProposalWizard: React.FC = ({ )} {currentStep === 'proposal-preview' && ( - + + + )} ); diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx index 741cb47eaf..07336da820 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx @@ -12,7 +12,7 @@ import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import { useGetNativeTokenGatewayContractAddress } from 'libs/contracts'; import { useTranslation } from 'libs/translations'; import type { Asset, Pool } from 'types'; -import { convertTokensToMantissa } from 'utilities'; +import { cn, convertTokensToMantissa } from 'utilities'; import { NULL_ADDRESS } from 'constants/address'; import { ConnectWallet } from 'containers/ConnectWallet'; @@ -136,41 +136,45 @@ export const BorrowFormUi: React.FC = ({ }, [safeLimitTokens, setFormValues]); return ( -
- - setFormValues(currentFormValues => ({ - ...currentFormValues, - amountTokens, - })) - } - disabled={ - !isUserConnected || - isSubmitting || - formError?.code === 'BORROW_CAP_ALREADY_REACHED' || - formError?.code === 'NO_COLLATERALS' - } - rightMaxButton={{ - label: t('operationForm.limitButtonLabel', { - limitPercentage: SAFE_BORROW_LIMIT_PERCENTAGE, - }), - onClick: handleRightMaxButtonClick, - }} - hasError={isUserConnected && !!formError && Number(formValues.amountTokens) > 0} - description={ - isUserConnected && !isSubmitting && !!formError?.message ? ( -

{formError.message}

- ) : undefined - } - /> - - {!isUserConnected && } - - + +
+ + setFormValues(currentFormValues => ({ + ...currentFormValues, + amountTokens, + })) + } + disabled={ + !isUserConnected || + isSubmitting || + formError?.code === 'BORROW_CAP_ALREADY_REACHED' || + formError?.code === 'NO_COLLATERALS' + } + rightMaxButton={{ + label: t('operationForm.limitButtonLabel', { + limitPercentage: SAFE_BORROW_LIMIT_PERCENTAGE, + }), + onClick: handleRightMaxButtonClick, + }} + hasError={isUserConnected && !!formError && Number(formValues.amountTokens) > 0} + description={ + isUserConnected && !isSubmitting && !!formError?.message ? ( +

{formError.message}

+ ) : undefined + } + /> + + {!isUserConnected && } +
+ + cn('space-y-4', isUserConnected ? 'mt-4' : 'mt-6')} + > {!isSubmitting && !formError && ( = ({ ]); return ( - - {isIntegratedSwapFeatureEnabled || canWrapNativeToken ? ( - 0} - disabled={!isUserConnected || isSubmitting} - onChange={amountTokens => - setFormValues(currentFormValues => ({ - ...currentFormValues, - amountTokens, - // Reset selected fixed percentage - fixedRepayPercentage: undefined, - })) - } - onChangeSelectedToken={fromToken => - setFormValues(currentFormValues => ({ - ...currentFormValues, - fromToken, - })) - } - rightMaxButton={{ - label: t('operationForm.rightMaxButtonLabel'), - onClick: handleRightMaxButtonClick, - }} - tokenBalances={tokenBalances} - description={ - !isSubmitting && !!formError?.message ? ( -

{formError.message}

- ) : undefined - } - /> - ) : ( - - setFormValues(currentFormValues => ({ - ...currentFormValues, - amountTokens, - // Reset selected fixed percentage - fixedRepayPercentage: undefined, - })) - } - disabled={!isUserConnected || isSubmitting} - rightMaxButton={{ - label: t('operationForm.rightMaxButtonLabel'), - onClick: handleRightMaxButtonClick, - }} - data-testid={TEST_IDS.tokenTextField} - hasError={ - isUserConnected && !isSubmitting && !!formError && Number(formValues.amountTokens) > 0 - } - description={ - isUserConnected && !isSubmitting && !!formError?.message ? ( -

{formError.message}

- ) : undefined - } - /> - )} - -
- {PRESET_PERCENTAGES.map(percentage => ( - + +
+ {isIntegratedSwapFeatureEnabled || canWrapNativeToken ? ( + 0} + disabled={!isUserConnected || isSubmitting} + onChange={amountTokens => setFormValues(currentFormValues => ({ ...currentFormValues, - fixedRepayPercentage: percentage, + amountTokens, + // Reset selected fixed percentage + fixedRepayPercentage: undefined, })) } - > - {formatPercentageToReadableValue(percentage)} - - ))} -
- - {!isUserConnected && } - - - {!isSubmitting && !isSwapLoading && !formError && ( - - )} - -
- + setFormValues(currentFormValues => ({ + ...currentFormValues, + fromToken, + })) + } + rightMaxButton={{ + label: t('operationForm.rightMaxButtonLabel'), + onClick: handleRightMaxButtonClick, + }} + tokenBalances={tokenBalances} + description={ + !isSubmitting && !!formError?.message ? ( +

{formError.message}

+ ) : undefined + } + /> + ) : ( + + setFormValues(currentFormValues => ({ + ...currentFormValues, + amountTokens, + // Reset selected fixed percentage + fixedRepayPercentage: undefined, + })) + } + disabled={!isUserConnected || isSubmitting} + rightMaxButton={{ + label: t('operationForm.rightMaxButtonLabel'), + onClick: handleRightMaxButtonClick, + }} + data-testid={TEST_IDS.tokenTextField} + hasError={ + isUserConnected && !isSubmitting && !!formError && Number(formValues.amountTokens) > 0 + } + description={ + isUserConnected && !isSubmitting && !!formError?.message ? ( +

{formError.message}

+ ) : undefined } - > - {isUsingSwap - ? readableFromTokenUserWalletBalanceTokens - : readableRepayableFromTokenAmountTokens} -
- - + )} + +
+ {PRESET_PERCENTAGES.map(percentage => ( + + setFormValues(currentFormValues => ({ + ...currentFormValues, + fixedRepayPercentage: percentage, + })) + } + > + {formatPercentageToReadableValue(percentage)} + + ))}
- + {!isUserConnected && } +
- {isUsingSwap && swap && ( - <> - + +
+ {!isSubmitting && !isSwapLoading && !formError && ( + + )} + +
+ + {isUsingSwap + ? readableFromTokenUserWalletBalanceTokens + : readableRepayableFromTokenAmountTokens} + + + +
- - - )} + -
-
- + {isUsingSwap && swap && ( + <> + - + + + )} - -
+ - + +
+ + ); diff --git a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx index f98ebb2cd4..948acad087 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/SupplyForm/index.tsx @@ -29,6 +29,7 @@ import { useAccountAddress } from 'libs/wallet'; import type { Asset, Pool, Swap, SwapError, TokenBalance } from 'types'; import { areTokensEqual, + cn, convertMantissaToTokens, convertTokensToMantissa, getUniqueTokenBalances, @@ -225,179 +226,177 @@ export const SupplyFormUi: React.FC = ({ ]); return ( -
- {!isWrapUnwrapNativeTokenEnabled && !!asset.vToken.underlyingToken.tokenWrapped && ( - , - }} - values={{ - nativeTokenSymbol: asset.vToken.underlyingToken.tokenWrapped.symbol, - wrappedNativeTokenSymbol: asset.vToken.underlyingToken.symbol, - }} - /> - } - /> - )} - - {(asset.collateralFactor || asset.isCollateralOfUser) && ( - - +
+ {!isWrapUnwrapNativeTokenEnabled && !!asset.vToken.underlyingToken.tokenWrapped && ( + , + }} + values={{ + nativeTokenSymbol: asset.vToken.underlyingToken.tokenWrapped.symbol, + wrappedNativeTokenSymbol: asset.vToken.underlyingToken.symbol, + }} + /> + } /> - - )} - - {isIntegratedSwapFeatureEnabled || canWrapNativeToken ? ( - 0} - disabled={ - !isUserConnected || - isSubmitting || - isApproveFromTokenLoading || - formError?.code === 'SUPPLY_CAP_ALREADY_REACHED' - } - onChange={amountTokens => - setFormValues(currentFormValues => ({ - ...currentFormValues, - amountTokens, - })) - } - onChangeSelectedToken={fromToken => - setFormValues(currentFormValues => ({ - ...currentFormValues, - fromToken, - })) - } - rightMaxButton={{ - label: t('operationForm.rightMaxButtonLabel'), - onClick: handleRightMaxButtonClick, - }} - tokenBalances={tokenBalances} - description={ - !isSubmitting && !!formError?.message ? ( -

{formError.message}

- ) : undefined - } - /> - ) : ( - - setFormValues(currentFormValues => ({ - ...currentFormValues, - amountTokens, - })) - } - disabled={ - !isUserConnected || - isSubmitting || - isApproveFromTokenLoading || - formError?.code === 'SUPPLY_CAP_ALREADY_REACHED' - } - rightMaxButton={{ - label: t('operationForm.rightMaxButtonLabel'), - onClick: handleRightMaxButtonClick, - }} - hasError={ - isUserConnected && !isSubmitting && !!formError && Number(formValues.amountTokens) > 0 - } - description={ - isUserConnected && !isSubmitting && !!formError?.message ? ( -

{formError.message}

- ) : undefined - } - /> - )} - - {!isUserConnected && } - - - {!isSubmitting && !isSwapLoading && !formError && } + )} -
- - {isUsingSwap - ? readableFromTokenUserWalletBalanceTokens - : readableSuppliableFromTokenAmountTokens} + {(asset.collateralFactor || asset.isCollateralOfUser) && ( + + + )} - 0} + disabled={ + !isUserConnected || + isSubmitting || + isApproveFromTokenLoading || + formError?.code === 'SUPPLY_CAP_ALREADY_REACHED' + } + onChange={amountTokens => + setFormValues(currentFormValues => ({ + ...currentFormValues, + amountTokens, + })) + } + onChangeSelectedToken={fromToken => + setFormValues(currentFormValues => ({ + ...currentFormValues, + fromToken, + })) + } + rightMaxButton={{ + label: t('operationForm.rightMaxButtonLabel'), + onClick: handleRightMaxButtonClick, + }} + tokenBalances={tokenBalances} + description={ + !isSubmitting && !!formError?.message ? ( +

{formError.message}

+ ) : undefined + } + /> + ) : ( + + setFormValues(currentFormValues => ({ + ...currentFormValues, + amountTokens, + })) + } + disabled={ + !isUserConnected || + isSubmitting || + isApproveFromTokenLoading || + formError?.code === 'SUPPLY_CAP_ALREADY_REACHED' + } + rightMaxButton={{ + label: t('operationForm.rightMaxButtonLabel'), + onClick: handleRightMaxButtonClick, + }} + hasError={ + isUserConnected && !isSubmitting && !!formError && Number(formValues.amountTokens) > 0 + } + description={ + isUserConnected && !isSubmitting && !!formError?.message ? ( +

{formError.message}

+ ) : undefined + } /> -
- - - - {isUsingSwap && swap && ( - <> - - - - )} -
-
- } +
+ + +
+ {!isSubmitting && !isSwapLoading && !formError && } + +
+ + {isUsingSwap + ? readableFromTokenUserWalletBalanceTokens + : readableSuppliableFromTokenAmountTokens} + + + +
- + - -
+ {isUsingSwap && swap && ( + <> + - + + )} + + + + + +
+ +
); diff --git a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx index 2fd6c59744..06165f8d24 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx @@ -13,7 +13,7 @@ import { VError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; import type { Asset, Pool } from 'types'; -import { convertTokensToMantissa } from 'utilities'; +import { cn, convertTokensToMantissa } from 'utilities'; import { NULL_ADDRESS } from 'constants/address'; import { ConnectWallet } from 'containers/ConnectWallet'; @@ -147,92 +147,92 @@ export const WithdrawFormUi: React.FC = ({ } return ( -
- - setFormValues(currentFormValues => ({ - ...currentFormValues, - amountTokens, - })) - } - disabled={!isUserConnected || isSubmitting} - rightMaxButton={{ - label: t('operationForm.rightMaxButtonLabel'), - onClick: handleRightMaxButtonClick, - }} - hasError={ - isUserConnected && !isSubmitting && !!formError && Number(formValues.amountTokens) > 0 - } - description={ - isUserConnected && !isSubmitting && !!formError?.message ? ( -

{formError.message}

- ) : undefined - } - /> - - {!isUserConnected && } - - - - {readableWithdrawableAmountTokens} - - - - - {canUnwrapToNativeToken && ( - <> - - - - - - - )} - -
-
- - - - - -
- - +
+ + setFormValues(currentFormValues => ({ + ...currentFormValues, + amountTokens, + })) + } + disabled={!isUserConnected || isSubmitting} + rightMaxButton={{ + label: t('operationForm.rightMaxButtonLabel'), + onClick: handleRightMaxButtonClick, + }} + hasError={ + isUserConnected && !isSubmitting && !!formError && Number(formValues.amountTokens) > 0 + } + description={ + isUserConnected && !isSubmitting && !!formError?.message ? ( +

{formError.message}

+ ) : undefined + } + /> + + {!isUserConnected && } +
+ + +
+ + {readableWithdrawableAmountTokens} + + + + + {canUnwrapToNativeToken && ( + <> + + + + + + + )} + + + + + +
+ +
); diff --git a/apps/evm/src/pages/Proposal/index.tsx b/apps/evm/src/pages/Proposal/index.tsx index 6d2cbe0a29..74a2d97ad1 100644 --- a/apps/evm/src/pages/Proposal/index.tsx +++ b/apps/evm/src/pages/Proposal/index.tsx @@ -27,7 +27,8 @@ import TEST_IDS from './testIds'; interface ProposalUiProps { proposal: ProposalType | undefined; vote: (params: UseVoteParams) => Promise; - votingEnabled: boolean; + canUserVoteOnProposal: boolean; + isUserConnectedToGovernanceChain: boolean; readableVoteWeight: string; isVoteLoading: boolean; } @@ -35,25 +36,23 @@ interface ProposalUiProps { export const ProposalUi: React.FC = ({ proposal, vote, - votingEnabled, + canUserVoteOnProposal, + isUserConnectedToGovernanceChain, readableVoteWeight, isVoteLoading, }) => { - const { switchChain } = useSwitchChain(); - - const { accountAddress } = useAccountAddress(); - const isUserConnected = !!accountAddress; - - const { chainId: accountChainId } = useAccountChainId(); - const isUserConnectedToNonGovernanceChain = - isUserConnected && accountChainId !== governanceChain.id; - - const isVoteProposalFeatureEnabled = useIsFeatureEnabled({ name: 'voteProposal' }); const styles = useStyles(); const { t } = useTranslation(); + const { switchChain } = useSwitchChain(); const [voteModalType, setVoteModalType] = useState<0 | 1 | 2 | undefined>(undefined); + const isVoteProposalFeatureEnabled = useIsFeatureEnabled({ name: 'voteProposal' }); + + const shouldEnableVoteButtons = + isVoteProposalFeatureEnabled && canUserVoteOnProposal && isUserConnectedToGovernanceChain; + const shouldShowWarning = !isVoteProposalFeatureEnabled || !isUserConnectedToGovernanceChain; + if (!proposal) { return (
@@ -66,23 +65,22 @@ export const ProposalUi: React.FC = ({
- {proposal.state === ProposalState.Active && - (!isVoteProposalFeatureEnabled || isUserConnectedToNonGovernanceChain) && ( - switchChain({ chainId: governanceChain.id })} - > - {t('vote.omnichain.switchToBnb')} - - } - /> - )} + {shouldShowWarning && ( + switchChain({ chainId: governanceChain.id })} + > + {t('vote.omnichain.switchToBnb')} + + } + /> + )}
= ({ voters={proposal.forVotes} openVoteModal={() => setVoteModalType(1)} progressBarColor={styles.successColor} - votingEnabled={votingEnabled && isVoteProposalFeatureEnabled} + votingEnabled={shouldEnableVoteButtons} data-testid={TEST_IDS.voteSummary.for} /> @@ -103,7 +101,7 @@ export const ProposalUi: React.FC = ({ voters={proposal.againstVotes} openVoteModal={() => setVoteModalType(0)} progressBarColor={styles.againstColor} - votingEnabled={votingEnabled && isVoteProposalFeatureEnabled} + votingEnabled={shouldEnableVoteButtons} data-testid={TEST_IDS.voteSummary.against} /> @@ -114,7 +112,7 @@ export const ProposalUi: React.FC = ({ voters={proposal.abstainVotes} openVoteModal={() => setVoteModalType(2)} progressBarColor={styles.abstainColor} - votingEnabled={votingEnabled && isVoteProposalFeatureEnabled} + votingEnabled={shouldEnableVoteButtons} data-testid={TEST_IDS.voteSummary.abstain} />
@@ -140,6 +138,7 @@ export const ProposalUi: React.FC = ({ const Proposal = () => { const { accountAddress } = useAccountAddress(); + const { chainId: accountChainId } = useAccountChainId(); const { proposalId = '' } = useParams<{ proposalId: string }>(); const { data: proposalData, error: getProposalError } = useGetProposal( { proposalId: Number(proposalId), accountAddress }, @@ -178,7 +177,7 @@ const Proposal = () => { ); // voting should be enabled if: - const votingEnabled = + const canUserVoteOnProposal = // user wallet is connected !!accountAddress && // proposal is still active @@ -188,6 +187,8 @@ const Proposal = () => { // user has some voting weight votingWeightData.votesMantissa.isGreaterThan(0); + const isUserConnectedToGovernanceChain = accountChainId === governanceChain.id; + if (getProposalError) { return ; } @@ -197,7 +198,8 @@ const Proposal = () => { diff --git a/apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx b/apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx index ab20d683da..43105ae064 100644 --- a/apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Vault/TransactionForm/SubmitSection/index.tsx @@ -49,10 +49,6 @@ export const SubmitSection: React.FC = ({ /> ); - if (isFormValid) { - dom = {dom}; - } - if (isFormValid && tokenNeedsToBeApproved) { dom = ( = ({ ); } + if (isFormValid) { + dom = {dom}; + } + return dom; }; diff --git a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx b/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx index eff12c6e81..ba813aa757 100644 --- a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx +++ b/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/index.tsx @@ -13,7 +13,6 @@ import { useAccountAddress } from 'libs/wallet'; import type { Token } from 'types'; import { SwitchChain } from 'containers/SwitchChain'; -import { useStyles } from './styles'; import TEST_IDS from './testIds'; export interface WithdrawUiProps { @@ -34,7 +33,6 @@ const WithdrawUi: React.FC = ({ withdrawableMantissa, }) => { const { t } = useTranslation(); - const styles = useStyles(); const handleSubmit = async () => { await onSubmit(); @@ -47,14 +45,31 @@ const WithdrawUi: React.FC = ({ token: stakedToken, }); + const hasWithdrawableTokens = !!withdrawableMantissa && withdrawableMantissa.isGreaterThan(0); + + let submitDom = ( + + {t('withdrawFromVestingVaultModalModal.withdrawTab.submitButton')} + + ); + + if (hasWithdrawableTokens) { + submitDom = {submitDom}; + } + return ( <> {isInitialLoading || !withdrawableMantissa ? ( ) : ( - <> +
= ({ {readableWithdrawableTokens} - - - {t('withdrawFromVestingVaultModalModal.withdrawTab.submitButton')} - - - + {submitDom} +
)} ); diff --git a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/styles.ts b/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/styles.ts deleted file mode 100644 index dc5f0063cc..0000000000 --- a/apps/evm/src/pages/Vault/modals/WithdrawFromVestingVaultModal/Withdraw/styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { css } from '@emotion/react'; -import { useTheme } from '@mui/material'; - -export const useStyles = () => { - const theme = useTheme(); - - return { - content: css` - margin-bottom: ${theme.spacing(12)}; - `, - }; -}; From ab05017284c87fee8a30e8f6b892faa2d3f0c9e0 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Mon, 24 Feb 2025 14:27:20 +0100 Subject: [PATCH 11/13] feat: feat: update Delegate modal to use SwitchChain container --- .../src/containers/ConnectWallet/index.tsx | 2 + .../DelegateModal/SubmitSection/index.tsx | 37 ++++++ .../VotingWallet/DelegateModal/index.tsx | 25 ++-- .../pages/Governance/VotingWallet/index.tsx | 1 - .../pages/Governance/__tests__/index.spec.tsx | 29 ++-- .../Page/OperationForm/BorrowForm/index.tsx | 124 +++++++++--------- .../Page/OperationForm/WithdrawForm/index.tsx | 2 +- 7 files changed, 122 insertions(+), 98 deletions(-) create mode 100644 apps/evm/src/pages/Governance/VotingWallet/DelegateModal/SubmitSection/index.tsx diff --git a/apps/evm/src/containers/ConnectWallet/index.tsx b/apps/evm/src/containers/ConnectWallet/index.tsx index 5453a74d14..f91235c235 100644 --- a/apps/evm/src/containers/ConnectWallet/index.tsx +++ b/apps/evm/src/containers/ConnectWallet/index.tsx @@ -22,6 +22,8 @@ export const ConnectWallet: React.FC = ({ const { t } = useTranslation(); + console.log('isUserConnected', isUserConnected); + return (
{isUserConnected ? ( diff --git a/apps/evm/src/pages/Governance/VotingWallet/DelegateModal/SubmitSection/index.tsx b/apps/evm/src/pages/Governance/VotingWallet/DelegateModal/SubmitSection/index.tsx new file mode 100644 index 0000000000..1bd7e4532d --- /dev/null +++ b/apps/evm/src/pages/Governance/VotingWallet/DelegateModal/SubmitSection/index.tsx @@ -0,0 +1,37 @@ +import { ConnectWallet } from 'containers/ConnectWallet'; +import { FormikSubmitButton } from 'containers/Form'; +import { SwitchChain } from 'containers/SwitchChain'; +import { useFormikContext } from 'formik'; +import { useTranslation } from 'libs/translations'; +import { governanceChain } from 'libs/wallet'; + +export interface SubmitSectionProps { + previouslyDelegated: boolean; + isVoteDelegationLoading: boolean; +} + +export const SubmitSection = ({ + previouslyDelegated, + isVoteDelegationLoading, +}: SubmitSectionProps) => { + const { t } = useTranslation(); + const { isValid } = useFormikContext(); + + let dom = ( + + ); + + if (isValid) { + dom = ( + + {dom} + + ); + } + + return
{dom}
; +}; diff --git a/apps/evm/src/pages/Governance/VotingWallet/DelegateModal/index.tsx b/apps/evm/src/pages/Governance/VotingWallet/DelegateModal/index.tsx index 10d6436885..372bfb0d1a 100644 --- a/apps/evm/src/pages/Governance/VotingWallet/DelegateModal/index.tsx +++ b/apps/evm/src/pages/Governance/VotingWallet/DelegateModal/index.tsx @@ -2,13 +2,14 @@ import { Typography } from '@mui/material'; import { Form, Formik } from 'formik'; -import { ButtonWrapper, Modal, NoticeInfo, PrimaryButton, TextButton } from 'components'; +import { ButtonWrapper, Modal, NoticeInfo, TextButton } from 'components'; import { routes } from 'constants/routing'; import { Link } from 'containers/Link'; import { handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; -import { FormikSubmitButton, FormikTextField } from 'containers/Form'; +import { FormikTextField } from 'containers/Form'; +import { SubmitSection } from './SubmitSection'; import addressValidationSchema from './addressValidationSchema'; import { useStyles } from './styles'; @@ -19,7 +20,6 @@ interface DelegateModalProps { setVoteDelegation: (input: { delegateAddress: string }) => unknown; previouslyDelegated: boolean; isVoteDelegationLoading: boolean; - openAuthModal: () => void; } const DelegateModal: React.FC = ({ @@ -29,7 +29,6 @@ const DelegateModal: React.FC = ({ setVoteDelegation, previouslyDelegated, isVoteDelegationLoading, - openAuthModal, }) => { const { t } = useTranslation(); const styles = useStyles(); @@ -81,19 +80,11 @@ const DelegateModal: React.FC = ({ maxLength={42} disabled={!currentUserAccountAddress} /> - {currentUserAccountAddress ? ( - - ) : ( - - {t('connectWallet.connectButton')} - - )} + + )} diff --git a/apps/evm/src/pages/Governance/VotingWallet/index.tsx b/apps/evm/src/pages/Governance/VotingWallet/index.tsx index 1cad899010..6f5ccdf2e6 100644 --- a/apps/evm/src/pages/Governance/VotingWallet/index.tsx +++ b/apps/evm/src/pages/Governance/VotingWallet/index.tsx @@ -243,7 +243,6 @@ const VotingWallet: React.FC = ({ className }) => { previouslyDelegated={previouslyDelegated} setVoteDelegation={setVoteDelegation} isVoteDelegationLoading={isVoteDelegationLoading} - openAuthModal={openAuthModal} /> )} diff --git a/apps/evm/src/pages/Governance/__tests__/index.spec.tsx b/apps/evm/src/pages/Governance/__tests__/index.spec.tsx index f334d76cd0..0c62506a51 100644 --- a/apps/evm/src/pages/Governance/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Governance/__tests__/index.spec.tsx @@ -1,4 +1,4 @@ -import { fireEvent, screen, waitFor } from '@testing-library/react'; +import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import BigNumber from 'bignumber.js'; import _cloneDeep from 'lodash/cloneDeep'; import type { Mock } from 'vitest'; @@ -147,16 +147,6 @@ describe('Governance', () => { expect(createProposalButton).toBeDisabled(); }); - it('opens delegate modal when clicking text with connect wallet button when unauthenticated', async () => { - const { getByText, getAllByText, getByTestId } = renderComponent(); - const delegateVoteText = getByTestId(VOTING_WALLET_TEST_IDS.delegateYourVoting); - - fireEvent.click(delegateVoteText); - await waitFor(() => getByText(en.vote.delegateAddress)); - - expect(getAllByText(en.connectWallet.connectButton)).toHaveLength(2); - }); - it('opens delegate modal when clicking text with delegate button when authenticated', async () => { const { getByText, getByTestId } = renderComponent(, { accountAddress: fakeAccountAddress, @@ -180,6 +170,11 @@ describe('Governance', () => { }); it('prompts user to connect Wallet', async () => { + const { getByText } = renderComponent(); + getByText(en.connectWallet.connectButton); + }); + + it('prompts user to switch chain if they are connected to the wrong one', async () => { (getCurrentVotes as Mock).mockImplementationOnce(() => ({ votesMantissa: new BigNumber(0), })); @@ -240,8 +235,10 @@ describe('Governance', () => { const addressInput = getByPlaceholderText(en.vote.enterContactAddress); - fireEvent.change(addressInput, { - target: { value: altAddress }, + await act(async () => { + fireEvent.change(addressInput, { + target: { value: altAddress }, + }); }); const delegateVotesButton = getByText(en.vote.delegateVotes); @@ -270,14 +267,16 @@ describe('Governance', () => { await waitFor(() => getByText(en.vote.delegateAddress)); - fireEvent.click(getByText(en.vote.pasteYourAddress)); + await act(async () => { + fireEvent.click(getByText(en.vote.pasteYourAddress)); + }); const delegateVotesButton = getByText(en.vote.delegateVotes); await waitFor(() => expect(delegateVotesButton).toBeEnabled()); fireEvent.click(delegateVotesButton); await waitFor(() => - expect(setVoteDelegate).toBeCalledWith({ + expect(setVoteDelegate).toHaveBeenCalledWith({ delegateAddress: fakeAccountAddress, }), ); diff --git a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx index 07336da820..bf599842b1 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/BorrowForm/index.tsx @@ -172,75 +172,71 @@ export const BorrowFormUi: React.FC = ({ {!isUserConnected && }
- cn('space-y-4', isUserConnected ? 'mt-4' : 'mt-6')} - > - {!isSubmitting && !formError && ( - - )} - - - {readableLimit} - - - - - {canUnwrapToNativeToken && ( - <> - - - - - - - )} - -
-
- +
+ {!isSubmitting && !formError && ( + + )} + + + {readableLimit} + + + + + {canUnwrapToNativeToken && ( + <> + + + + + + + )} + + - + - -
- -
+ + ); diff --git a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx index 06165f8d24..ac568188bc 100644 --- a/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx +++ b/apps/evm/src/pages/Market/Page/OperationForm/WithdrawForm/index.tsx @@ -178,7 +178,7 @@ export const WithdrawFormUi: React.FC = ({ {!isUserConnected && }
- +
{readableWithdrawableAmountTokens} From 9a3a12df068c5ec9bc8b7cad565611c5cdde194d Mon Sep 17 00:00:00 2001 From: therealemjy Date: Mon, 24 Feb 2025 14:33:16 +0100 Subject: [PATCH 12/13] chore: add changeset --- .changeset/clean-ladybugs-kiss.md | 5 +++++ apps/evm/src/containers/ConnectWallet/index.tsx | 2 -- apps/evm/src/libs/translations/translations/en.json | 3 --- apps/evm/src/pages/Bridge/__tests__/index.spec.tsx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 .changeset/clean-ladybugs-kiss.md diff --git a/.changeset/clean-ladybugs-kiss.md b/.changeset/clean-ladybugs-kiss.md new file mode 100644 index 0000000000..4844d8a07e --- /dev/null +++ b/.changeset/clean-ladybugs-kiss.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": minor +--- + +update behavior to switch chains diff --git a/apps/evm/src/containers/ConnectWallet/index.tsx b/apps/evm/src/containers/ConnectWallet/index.tsx index f91235c235..5453a74d14 100644 --- a/apps/evm/src/containers/ConnectWallet/index.tsx +++ b/apps/evm/src/containers/ConnectWallet/index.tsx @@ -22,8 +22,6 @@ export const ConnectWallet: React.FC = ({ const { t } = useTranslation(); - console.log('isUserConnected', isUserConnected); - return (
{isUserConnected ? ( diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index 6713fa03e2..08b0a7ae80 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -194,9 +194,6 @@ "label": "Bridge gas fee", "tooltip": "Used to cover the gas cost for sending your transfer on the destination chain" }, - "connectWalletButton": { - "label": "Connect wallet" - }, "errors": { "dailyTransactionLimitExceeded": { "message": "You cannot bridge more than {{readableAmountTokens}} ({{readableAmountUsd}}) on the destination chain due to the 24-hour limit. This limit will be reset on {{ date, dd MMM yyyy h:mm a }}", diff --git a/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx b/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx index b0c4020aa1..5fc009f135 100644 --- a/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Bridge/__tests__/index.spec.tsx @@ -89,10 +89,10 @@ describe('Bridge', () => { const { getByText } = renderComponent(); // Check connect button is present - await waitFor(() => getByText(en.bridgePage.connectWalletButton.label)); + await waitFor(() => getByText(en.connectButton.connect)); // Click on connect button - fireEvent.click(getByText(en.bridgePage.connectWalletButton.label).closest('button')!); + fireEvent.click(getByText(en.connectButton.connect).closest('button')!); await waitFor(() => expect(openAuthModalMock).toHaveBeenCalledTimes(1)); }); From 824c5c96f27afd53a53f853a581d90b4b0d7351a Mon Sep 17 00:00:00 2001 From: therealemjy Date: Wed, 26 Feb 2025 09:45:52 +0100 Subject: [PATCH 13/13] refactor: update ClaimRewardButton to use SwitchChain container --- .../Layout/ClaimRewardButton/index.tsx | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/evm/src/containers/Layout/ClaimRewardButton/index.tsx b/apps/evm/src/containers/Layout/ClaimRewardButton/index.tsx index deefb843de..e9fa3de88d 100644 --- a/apps/evm/src/containers/Layout/ClaimRewardButton/index.tsx +++ b/apps/evm/src/containers/Layout/ClaimRewardButton/index.tsx @@ -9,6 +9,8 @@ import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; import { cn, formatCentsToReadableValue } from 'utilities'; +import { ConnectWallet } from 'containers/ConnectWallet'; +import { SwitchChain } from 'containers/SwitchChain'; import TEST_IDS from '../testIds'; import { RewardGroup } from './RewardGroup'; import type { Group } from './types'; @@ -127,17 +129,21 @@ export const ClaimRewardButtonUi: React.FC = ({ ))}
- - {isSubmitDisabled - ? t('claimReward.modal.claimButton.disabledLabel') - : t('claimReward.modal.claimButton.enabledLabel')} - + + + + {isSubmitDisabled + ? t('claimReward.modal.claimButton.disabledLabel') + : t('claimReward.modal.claimButton.enabledLabel')} + + +