From 9d9c10375e0f50dfc68e5d8c4511b19553613c89 Mon Sep 17 00:00:00 2001 From: Mimi Tran <80493680+mimi-imtbl@users.noreply.github.com> Date: Fri, 17 May 2024 11:35:38 +1000 Subject: [PATCH] feat: [CM-652][Sale Widget] Non-passport wallet gas warning drawer (#1785) --- .../src/components/Hero/WalletWarningHero.tsx | 174 +++++++++++++ .../checkout/widgets-lib/src/locales/en.json | 6 + .../checkout/widgets-lib/src/locales/ja.json | 8 +- .../checkout/widgets-lib/src/locales/ko.json | 6 + .../checkout/widgets-lib/src/locales/zh.json | 6 + .../components/NonPassportWarningDrawer.tsx | 101 ++++++++ .../widgets/connect/components/WalletList.tsx | 232 +++++++++++------- 7 files changed, 450 insertions(+), 83 deletions(-) create mode 100644 packages/checkout/widgets-lib/src/components/Hero/WalletWarningHero.tsx create mode 100644 packages/checkout/widgets-lib/src/widgets/connect/components/NonPassportWarningDrawer.tsx diff --git a/packages/checkout/widgets-lib/src/components/Hero/WalletWarningHero.tsx b/packages/checkout/widgets-lib/src/components/Hero/WalletWarningHero.tsx new file mode 100644 index 0000000000..9402cedec5 --- /dev/null +++ b/packages/checkout/widgets-lib/src/components/Hero/WalletWarningHero.tsx @@ -0,0 +1,174 @@ +/* eslint-disable max-len */ +import { Box } from '@biom3/react'; + +export function WalletWarningHero() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/checkout/widgets-lib/src/locales/en.json b/packages/checkout/widgets-lib/src/locales/en.json index 9ce6eaa2a8..12ebc00836 100644 --- a/packages/checkout/widgets-lib/src/locales/en.json +++ b/packages/checkout/widgets-lib/src/locales/en.json @@ -7,6 +7,12 @@ }, "walletSelection": { "heading": "Choose a wallet" + }, + "nonPassportDrawer": { + "heading": "Non-Passport users will need IMX for gas.", + "body1": "We recommend using Layerswap to bridge funds onto Immutable zkEVM, with refuel switched on so you receive some IMX.", + "body2": "Alternatively, pay and play easily with a gas free Immutable Passport.", + "buttonText": "Proceed anyway" } }, "CONNECT_SUCCESS": { diff --git a/packages/checkout/widgets-lib/src/locales/ja.json b/packages/checkout/widgets-lib/src/locales/ja.json index 4edebbaa72..c1e8258e12 100644 --- a/packages/checkout/widgets-lib/src/locales/ja.json +++ b/packages/checkout/widgets-lib/src/locales/ja.json @@ -7,7 +7,13 @@ }, "walletSelection": { "heading": "ウォレットを選択" - } + }, + "nonPassportDrawer": { + "heading": "パスポート未使用者はガス代としてIMXが必要です。", + "body1": "資金をImmutable zkEVMにブリッジするには、Layerswapの使用をお勧めします。リファエルをオンにしてIMXを受け取ります。", + "body2": "または、ガス無料のImmutable Passportで簡単に支払いとプレイができます。", + "buttonText": "それでも進む" + } }, "CONNECT_SUCCESS": { "status": "接続が安全です", diff --git a/packages/checkout/widgets-lib/src/locales/ko.json b/packages/checkout/widgets-lib/src/locales/ko.json index 662e01acf0..b6a61389c3 100644 --- a/packages/checkout/widgets-lib/src/locales/ko.json +++ b/packages/checkout/widgets-lib/src/locales/ko.json @@ -7,6 +7,12 @@ }, "walletSelection": { "heading": "지갑 선택" + }, + "nonPassportDrawer": { + "heading": "비 패스포트 사용자는 가스 비용으로 IMX가 필요합니다.", + "body1": "자금을 Immutable zkEVM으로 브리징하기 위해 Layerswap을 사용하는 것을 권장합니다. 리퓨엘이 켜져 있으면 IMX를 받을 수 있습니다.", + "body2": "또는 가스 비용이 없는 Immutable Passport로 쉽게 결제하고 플레이하세요.", + "buttonText": "어쨌든 진행" } }, "CONNECT_SUCCESS": { diff --git a/packages/checkout/widgets-lib/src/locales/zh.json b/packages/checkout/widgets-lib/src/locales/zh.json index 01633ba3bc..49ff1a9511 100644 --- a/packages/checkout/widgets-lib/src/locales/zh.json +++ b/packages/checkout/widgets-lib/src/locales/zh.json @@ -7,6 +7,12 @@ }, "walletSelection": { "heading": "选择一个钱包" + }, + "nonPassportDrawer": { + "heading": "非Passport用户需要IMX来支付燃气费。", + "body1": "我们建议使用Layerswap将资金桥接到Immutable zkEVM,并打开加油功能,以便您能收到一些IMX。", + "body2": "或者,可以使用免燃气费的Immutable Passport轻松支付和玩游戏。", + "buttonText": "继续操作" } }, "CONNECT_SUCCESS": { diff --git a/packages/checkout/widgets-lib/src/widgets/connect/components/NonPassportWarningDrawer.tsx b/packages/checkout/widgets-lib/src/widgets/connect/components/NonPassportWarningDrawer.tsx new file mode 100644 index 0000000000..568d005446 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/connect/components/NonPassportWarningDrawer.tsx @@ -0,0 +1,101 @@ +import { + Body, + Box, + ButtCon, + Button, + Drawer, + Heading, + Link, +} from '@biom3/react'; +import { Trans, useTranslation } from 'react-i18next'; +import { WalletWarningHero } from 'components/Hero/WalletWarningHero'; + +export function NonPassportWarningDrawer({ + visible, + onCloseDrawer, + handleCtaButtonClick, +}: { + visible: boolean; + onCloseDrawer: () => void; + handleCtaButtonClick: () => void; +}) { + const { t } = useTranslation(); + + return ( + + + + + + + {t('views.CONNECT_WALLET.nonPassportDrawer.heading')} + + + + )} + /> + ), + }} + /> +
+
+ {t('views.CONNECT_WALLET.nonPassportDrawer.body2')} + +
+ + + +
+
+ ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx index f28a2a17ec..34c8eb3839 100644 --- a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx +++ b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx @@ -1,11 +1,18 @@ import { Box } from '@biom3/react'; import { ChainId, - CheckoutErrorType, EIP6963ProviderDetail, - WalletProviderName, WalletProviderRdns, + CheckoutErrorType, + EIP6963ProviderDetail, + WalletProviderName, + WalletProviderRdns, } from '@imtbl/checkout-sdk'; import { - useCallback, useContext, useEffect, useMemo, useRef, useState, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, } from 'react'; import { motion } from 'framer-motion'; import { useTranslation } from 'react-i18next'; @@ -31,12 +38,16 @@ import { isPassportProvider, } from '../../../lib/provider'; import { addProviderListenersForWidgetRoot, getL1ChainId } from '../../../lib'; -import { listItemVariants, listVariants } from '../../../lib/animation/listAnimation'; +import { + listItemVariants, + listVariants, +} from '../../../lib/animation/listAnimation'; import { WalletDrawer } from '../../../components/WalletDrawer/WalletDrawer'; import { WalletChangeEvent } from '../../../components/WalletDrawer/WalletDrawerEvents'; import { WalletConnectItem } from './WalletConnectItem'; import { BrowserWalletItem } from './BrowserWalletItem'; import { identifyUser } from '../../../lib/analytics/identifyUser'; +import { NonPassportWarningDrawer } from './NonPassportWarningDrawer'; export interface WalletListProps { targetWalletRdns?: string; @@ -61,43 +72,59 @@ export function WalletList(props: WalletListProps) { const walletConnectItemRef = useRef(null); const [showChangedYourMindDrawer, setShowChangedYourMindDrawer] = useState(false); const [showUnableToConnectDrawer, setShowUnableToConnectDrawer] = useState(false); + const [showNonPassportWarning, setShowNonPassportWarning] = useState(false); const [chosenProviderDetail, setChosenProviderDetail] = useState(); - const filteredProviders = useMemo(() => ( - providers - .filter((provider) => (!(provider.info.rdns === WalletProviderRdns.PASSPORT))) - .filter((provider) => (!blocklistWalletRdns.includes(provider.info.rdns))) - ), [providers]); + const filteredProviders = useMemo( + () => providers + .filter( + (provider) => !(provider.info.rdns === WalletProviderRdns.PASSPORT), + ) + .filter( + (provider) => !blocklistWalletRdns.includes(provider.info.rdns), + ), + [providers], + ); // Don't allow Passport if targetChainId is L1 - const passportProviderDetail = useMemo(() => ( - targetChainId !== getL1ChainId(checkout!.config) - && providers - .filter((provider) => (!blocklistWalletRdns.includes(provider.info.rdns))) - .find((provider) => provider.info.rdns === WalletProviderRdns.PASSPORT) - ), [providers, checkout]); - - const selectWeb3Provider = useCallback((web3Provider: Web3Provider, providerName: string) => { - connectDispatch({ - payload: { - type: ConnectActions.SET_PROVIDER, - provider: web3Provider, - }, - }); - connectDispatch({ - payload: { - type: ConnectActions.SET_WALLET_PROVIDER_NAME, - walletProviderName: providerName as WalletProviderName, - }, - }); - }, []); + const passportProviderDetail = useMemo( + () => targetChainId !== getL1ChainId(checkout!.config) + && providers + .filter((provider) => !blocklistWalletRdns.includes(provider.info.rdns)) + .find((provider) => provider.info.rdns === WalletProviderRdns.PASSPORT), + [providers, checkout], + ); + + const selectWeb3Provider = useCallback( + (web3Provider: Web3Provider, providerName: string) => { + connectDispatch({ + payload: { + type: ConnectActions.SET_PROVIDER, + provider: web3Provider, + }, + }); + connectDispatch({ + payload: { + type: ConnectActions.SET_WALLET_PROVIDER_NAME, + walletProviderName: providerName as WalletProviderName, + }, + }); + }, + [], + ); const handleConnectViewUpdate = async (provider: Web3Provider) => { const isPassport = isPassportProvider(provider); - const chainId = await provider.provider.request!({ method: 'eth_chainId', params: [] }); + const chainId = await provider.provider.request!({ + method: 'eth_chainId', + params: [], + }); // eslint-disable-next-line radix const parsedChainId = parseInt(chainId.toString()); - if (parsedChainId !== targetChainId && !allowedChains?.includes(parsedChainId)) { + if ( + parsedChainId !== targetChainId + && !allowedChains?.includes(parsedChainId) + ) { // TODO: What do we do with Passport here as it can't connect to L1 if (isPassport) { viewDispatch({ @@ -125,50 +152,53 @@ export function WalletList(props: WalletListProps) { }); }; - const selectProviderDetail = useCallback(async (providerDetail: EIP6963ProviderDetail) => { - if (!checkout) return; - - try { - const isMetaMask = providerDetail.info.rdns === WalletProviderRdns.METAMASK; - const web3Provider = new Web3Provider(providerDetail.provider as any); + const selectProviderDetail = useCallback( + async (providerDetail: EIP6963ProviderDetail) => { + if (!checkout) return; try { - // TODO: Find a nice way to detect if the wallet supports switching accounts via requestPermissions - const changeAccount = isMetaMask; - const connectResult = await checkout.connect({ - provider: web3Provider, - requestWalletPermissions: changeAccount, - }); + const isMetaMask = providerDetail.info.rdns === WalletProviderRdns.METAMASK; + const web3Provider = new Web3Provider(providerDetail.provider as any); - // Set up EIP-1193 provider event listeners for widget root instances - addProviderListenersForWidgetRoot(connectResult.provider); - await identifyUser(identify, connectResult.provider); + try { + // TODO: Find a nice way to detect if the wallet supports switching accounts via requestPermissions + const changeAccount = isMetaMask; + const connectResult = await checkout.connect({ + provider: web3Provider, + requestWalletPermissions: changeAccount, + }); - selectWeb3Provider( - web3Provider, - getProviderSlugFromRdns(providerDetail.info.rdns), - ); - await handleConnectViewUpdate(web3Provider); - } catch (err: CheckoutErrorType | any) { - if (err.type === CheckoutErrorType.USER_REJECTED_REQUEST_ERROR) { - // eslint-disable-next-line no-console - console.error('Connect rejected', err); + // Set up EIP-1193 provider event listeners for widget root instances + addProviderListenersForWidgetRoot(connectResult.provider); + await identifyUser(identify, connectResult.provider); + + selectWeb3Provider( + web3Provider, + getProviderSlugFromRdns(providerDetail.info.rdns), + ); + await handleConnectViewUpdate(web3Provider); + } catch (err: CheckoutErrorType | any) { + if (err.type === CheckoutErrorType.USER_REJECTED_REQUEST_ERROR) { + // eslint-disable-next-line no-console + console.error('Connect rejected', err); - setShowChangedYourMindDrawer(true); - } else { - // eslint-disable-next-line no-console - console.error('Connect error', err); + setShowChangedYourMindDrawer(true); + } else { + // eslint-disable-next-line no-console + console.error('Connect error', err); - setShowUnableToConnectDrawer(true); + setShowUnableToConnectDrawer(true); + } } - } - } catch (err: any) { - // eslint-disable-next-line no-console - console.error('Connect unknown error', err); + } catch (err: any) { + // eslint-disable-next-line no-console + console.error('Connect unknown error', err); - setShowUnableToConnectDrawer(true); - } - }, [checkout]); + setShowUnableToConnectDrawer(true); + } + }, + [checkout], + ); const connectCallback = async (ethereumProvider) => { if (ethereumProvider.connected && ethereumProvider.session) { @@ -228,6 +258,17 @@ export function WalletList(props: WalletListProps) { const handleWalletItemClick = useCallback( async (providerDetail: EIP6963ProviderDetail) => { + const isPassport = providerDetail.info.rdns === WalletProviderRdns.PASSPORT; + const hasSeenNonPassportWarning = localStorage.getItem( + 'hasSeenNonPassportWarning', + ); + + if (!isPassport && !hasSeenNonPassportWarning) { + setChosenProviderDetail(providerDetail); + setShowNonPassportWarning(true); + return; + } + setShowChangedYourMindDrawer(false); setShowUnableToConnectDrawer(false); setChosenProviderDetail(providerDetail); @@ -265,13 +306,20 @@ export function WalletList(props: WalletListProps) { useEffect(() => { // Auto-trigger wallet connection via rdns if (targetWalletRdns && targetWalletRdns?.length > 0) { - if (targetWalletRdns === WalletProviderRdns.PASSPORT && passportProviderDetail) { + if ( + targetWalletRdns === WalletProviderRdns.PASSPORT + && passportProviderDetail + ) { handleWalletItemClick(passportProviderDetail); - } else if (targetWalletRdns === WalletProviderRdns.WALLETCONNECT && walletConnectItemRef.current) { + } else if ( + targetWalletRdns === WalletProviderRdns.WALLETCONNECT + && walletConnectItemRef.current + ) { (walletConnectItemRef.current as any).connect(); } else { - const targetProviderDetail = filteredProviders - .find((providerDetail) => providerDetail.info.rdns === targetWalletRdns); + const targetProviderDetail = filteredProviders.find( + (providerDetail) => providerDetail.info.rdns === targetWalletRdns, + ); if (targetProviderDetail) { handleWalletItemClick(targetProviderDetail); } @@ -279,16 +327,20 @@ export function WalletList(props: WalletListProps) { } }, [filteredProviders, targetWalletRdns]); + const handleNonPassportWarningDrawerButtonClick = async () => { + if (chosenProviderDetail) { + localStorage.setItem('hasSeenNonPassportWarning', 'true'); + setShowNonPassportWarning(false); + await selectProviderDetail(chosenProviderDetail); + } + }; + return ( - )} + rc={ + + } sx={walletListStyle} > {passportProviderDetail && ( @@ -327,17 +379,27 @@ export function WalletList(props: WalletListProps) { key="browserwallet" style={{ width: '100%' }} > - + )} {isWalletConnectEnabled && ( 0 ? 1 : 0)} + custom={ + 0 + + (passportProviderDetail ? 1 : 0) + + (filteredProviders.length > 0 ? 1 : 0) + } key="walletconnect" style={{ width: '100%' }} > - + )} @@ -369,6 +431,12 @@ export function WalletList(props: WalletListProps) { onCloseDrawer={() => setShowUnableToConnectDrawer(false)} onTryAgain={() => setShowUnableToConnectDrawer(false)} /> + + setShowNonPassportWarning(false)} + handleCtaButtonClick={handleNonPassportWarningDrawerButtonClick} + /> ); }