Create new WarningBanner component (#242)

- Create new WarningBanner component
- Add chainWalletWhitelists config
- Add warning in form when chains require certain wallets
pull/246/head
J M Rossy 1 month ago committed by GitHub
parent eb281570c1
commit 295dc2a949
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 13
      src/components/banner/FormWarningBanner.tsx
  2. 37
      src/components/banner/WarningBanner.tsx
  3. 5
      src/consts/config.ts
  4. 48
      src/features/chains/ChainWalletWarning.tsx
  5. 8
      src/features/transfer/TransferTokenForm.tsx
  6. 4
      src/features/wallet/SideBarMenu.tsx
  7. 3
      src/images/icons/warning.svg

@ -0,0 +1,13 @@
import { ComponentProps } from 'react';
import { WarningBanner } from '../../components/banner/WarningBanner';
export function FormWarningBanner({ className, ...props }: ComponentProps<typeof WarningBanner>) {
return (
<WarningBanner
// The margins here should be the inverse of those in Card.tsx
className={`z-20 -m-1.5 mb-0 sm:-m-3 sm:mb-0 md:-m-3.5 md:mb-0 ${className}`}
{...props}
/>
);
}

@ -0,0 +1,37 @@
import Image from 'next/image';
import { PropsWithChildren, ReactNode } from 'react';
import WarningIcon from '../../images/icons/warning.svg';
export function WarningBanner({
isVisible,
cta,
onClick,
className,
children,
}: PropsWithChildren<{
isVisible: boolean;
cta: ReactNode;
onClick: () => void;
className?: string;
}>) {
return (
<div
className={`flex items-center justify-between gap-2 px-4 bg-amber-400 text-sm ${
isVisible ? 'max-h-28 py-2 mb-2' : 'max-h-0 mb-0'
} overflow-hidden transition-all duration-500 ${className}`}
>
<div className="flex items-center gap-2">
<Image src={WarningIcon} width={20} height={20} alt="Warning:" />
{children}
</div>
<button
type="button"
onClick={onClick}
className="bg-white/30 rounded-full px-2.5 py-1 text-center hover:bg-white/50 active:bg-white/60"
>
{cta}
</button>
</div>
);
}

@ -1,3 +1,5 @@
import { ChainMap } from '@hyperlane-xyz/sdk';
import { ADDRESS_BLACKLIST } from './blacklist';
const isDevMode = process?.env?.NODE_ENV === 'development';
@ -7,6 +9,7 @@ const explorerApiKeys = JSON.parse(process?.env?.EXPLORER_API_KEYS || '{}');
const walletConnectProjectId = process?.env?.NEXT_PUBLIC_WALLET_CONNECT_ID || '';
const withdrawalWhitelist = process?.env?.NEXT_PUBLIC_BLOCK_WITHDRAWAL_WHITELIST || '';
const transferBlacklist = process?.env?.NEXT_PUBLIC_TRANSFER_BLACKLIST || '';
const chainWalletWhitelists = JSON.parse(process?.env?.NEXT_PUBLIC_CHAIN_WALLET_WHITELISTS || '{}');
interface Config {
isDevMode: boolean; // Enables some debug features in the app
@ -20,6 +23,7 @@ interface Config {
transferBlacklist: string; // comma-separated list of routes between which transfers are disabled. Expects Caip2Id-Caip2Id (e.g. ethereum:1-sealevel:1399811149)
enableExplorerLink: boolean; // Include a link to the hyperlane explorer in the transfer modal
addressBlacklist: string[]; // A list of addresses that are blacklisted and cannot be used in the app
chainWalletWhitelists: ChainMap<string[]>; // A map of chain names to a list of wallet names that work for it
}
export const config: Config = Object.freeze({
@ -34,4 +38,5 @@ export const config: Config = Object.freeze({
transferBlacklist,
enableExplorerLink: false,
addressBlacklist: ADDRESS_BLACKLIST.map((address) => address.toLowerCase()),
chainWalletWhitelists,
});

@ -0,0 +1,48 @@
import { useMemo } from 'react';
import { toTitleCase } from '@hyperlane-xyz/utils';
import { FormWarningBanner } from '../../components/banner/FormWarningBanner';
import { config } from '../../consts/config';
import { logger } from '../../utils/logger';
import { useConnectFns, useDisconnectFns, useWalletDetails } from '../wallet/hooks/multiProtocol';
import { getChainDisplayName, tryGetChainProtocol } from './utils';
export function ChainWalletWarning({ originChain }: { originChain: ChainName }) {
const wallets = useWalletDetails();
const connectFns = useConnectFns();
const disconnectFns = useDisconnectFns();
const { isVisible, chainDisplayName, walletWhitelist, connectFn, disconnectFn } = useMemo(() => {
const protocol = tryGetChainProtocol(originChain);
const walletWhitelist = config.chainWalletWhitelists[originChain]?.map((w) =>
w.trim().toLowerCase(),
);
if (!protocol || !walletWhitelist?.length)
return { isVisible: false, chainDisplayName: '', walletWhitelist: [] };
const chainDisplayName = getChainDisplayName(originChain, true);
const walletName = wallets[protocol]?.name?.trim()?.toLowerCase();
const connectFn = connectFns[protocol];
const disconnectFn = disconnectFns[protocol];
const isVisible = !!walletName && !walletWhitelist.includes(walletName);
return { isVisible, chainDisplayName, walletWhitelist, connectFn, disconnectFn };
}, [originChain, wallets, connectFns, disconnectFns]);
const onClickChange = () => {
if (!connectFn || !disconnectFn) return;
disconnectFn()
.then(() => connectFn())
.catch((err) => logger.error('Error changing wallet connection', err));
};
return (
<FormWarningBanner isVisible={isVisible} cta="Change" onClick={onClickChange}>
{`${chainDisplayName} requires one of the following wallets: ${walletWhitelist
.map((w) => toTitleCase(w))
.join(', ')}`}
</FormWarningBanner>
);
}

@ -18,6 +18,7 @@ import SwapIcon from '../../images/icons/swap.svg';
import { Color } from '../../styles/Color';
import { logger } from '../../utils/logger';
import { ChainSelectField } from '../chains/ChainSelectField';
import { ChainWalletWarning } from '../chains/ChainWalletWarning';
import { getChainDisplayName } from '../chains/utils';
import { useIsAccountSanctioned } from '../sanctions/hooks/useIsAccountSanctioned';
import { useStore } from '../store';
@ -62,8 +63,9 @@ export function TransferTokenForm() {
validateOnChange={false}
validateOnBlur={false}
>
{({ isValidating }) => (
<Form className="flex flex-col items-stretch w-full mt-2">
{({ isValidating, values }) => (
<Form className="flex flex-col items-stretch w-full">
<ChainWalletWarning originChain={values.origin} />
<ChainSelectSection isReview={isReview} />
<div className="mt-3 flex justify-between items-end space-x-4">
<TokenSection setIsNft={setIsNft} isReview={isReview} />
@ -112,7 +114,7 @@ function ChainSelectSection({ isReview }: { isReview: boolean }) {
const chains = useMemo(() => getWarpCore().getTokenChains(), []);
return (
<div className="flex items-center justify-center space-x-7 sm:space-x-10">
<div className="mt-2 flex items-center justify-center space-x-7 sm:space-x-10">
<ChainSelectField name="origin" label="From" chains={chains} disabled={isReview} />
<div className="flex flex-col items-center">
<div className="flex mb-6 sm:space-x-1.5">

@ -33,7 +33,7 @@ export function SideBarMenu({
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedTransfer, setSelectedTransfer] = useState<TransferContext | null>(null);
const disconnects = useDisconnectFns();
const disconnectFns = useDisconnectFns();
const { readyAccounts } = useAccounts();
const didMountRef = useRef(false);
@ -57,7 +57,7 @@ export function SideBarMenu({
}, [isOpen]);
const onClickDisconnect = async () => {
for (const disconnectFn of Object.values(disconnects)) {
for (const disconnectFn of Object.values(disconnectFns)) {
await disconnectFn();
}
};

@ -0,0 +1,3 @@
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.7385 12.8424L10.3944 1.37207C10.0899 0.896794 9.56445 0.609344 9.00031 0.609344C8.43613 0.609344 7.9106 0.896794 7.60622 1.37207L0.261512 12.8424C-0.0648531 13.3519 -0.0874155 13.999 0.203235 14.5299C0.493345 15.0604 1.05035 15.3907 1.65552 15.3907H16.3444C16.9496 15.3907 17.5066 15.0607 17.7967 14.5299C18.0875 13.999 18.0649 13.3519 17.7385 12.8424ZM8.07826 5.27148C8.07826 4.76708 8.48727 4.35837 8.99136 4.35837C9.49553 4.35837 9.9045 4.76708 9.9045 5.27148V9.18939C9.9045 9.69379 9.49553 10.1025 8.99136 10.1025C8.48727 10.1025 8.07826 9.69379 8.07826 9.18939V5.27148ZM8.97416 13.4096C8.34403 13.4096 7.83277 12.8986 7.83277 12.2682C7.83277 11.6377 8.34403 11.1268 8.97416 11.1268C9.60433 11.1268 10.1156 11.6377 10.1156 12.2682C10.1155 12.8986 9.60433 13.4096 8.97416 13.4096Z" fill="black" fill-opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 935 B

Loading…
Cancel
Save