diff --git a/packages/checkout/widgets-lib/src/locales/en.json b/packages/checkout/widgets-lib/src/locales/en.json index 562bbf67d4..e2030d4a80 100644 --- a/packages/checkout/widgets-lib/src/locales/en.json +++ b/packages/checkout/widgets-lib/src/locales/en.json @@ -638,6 +638,27 @@ "actionText": "Try again" } } + }, + "ADD_FUNDS": { + "drawer": { + "options": { + "swap": { + "heading": "Swap", + "caption": "Swap tokens on this network.", + "disabledCaption": "Currently not available." + }, + "debit": { + "heading": "Debit Card", + "caption": "The recommended way to pay with card.", + "disabledCaption": "Unavailable for your selection. We recommend adding tokens." + }, + "credit": { + "heading": "Credit Card", + "caption": "Not recommended since transactions may be blocked by your bank.", + "disabledCaption": "Unavailable for your selection. We recommend adding tokens." + } + } + } } }, "footers": { diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/components/Option.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/components/Option.tsx new file mode 100644 index 0000000000..e1f80078e3 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/components/Option.tsx @@ -0,0 +1,73 @@ +import { IconProps, MenuItem, MenuItemSize } from '@biom3/react'; +import { ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; + +export enum OptionTypes { + SWAP = 'swap', + DEBIT = 'debit', + CREDIT = 'credit', +} + +export interface OptionProps { + rc?: RC; + type: OptionTypes; + onClick: (type: OptionTypes) => void; + disabled?: boolean; + caption?: string; + size?: MenuItemSize; +} + +export function Option({ + type, + onClick, + disabled = false, + caption, + size, + rc = , +}: OptionProps) { + const { t } = useTranslation(); + + const icon: Record = { + [OptionTypes.SWAP]: 'Coins', + [OptionTypes.DEBIT]: 'BankCard', + [OptionTypes.CREDIT]: 'BankCard', + }; + + const handleClick = () => onClick(type); + + const menuItemProps = { + disabled, + emphasized: true, + onClick: disabled ? undefined : handleClick, + }; + + return ( + + + + {t(`views.ADD_FUNDS.drawer.options.${type}.heading`)} + + {!disabled && } + + {caption + || t( + `views.ADD_FUNDS.drawer.options.${type}.${ + disabled ? 'disabledCaption' : 'caption' + }`, + )} + + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/components/Options.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/components/Options.tsx new file mode 100644 index 0000000000..87f4a7e179 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/components/Options.tsx @@ -0,0 +1,75 @@ +import { Box, MenuItemSize } from '@biom3/react'; + +import { motion } from 'framer-motion'; +import { useEffect, useMemo } from 'react'; +import { + listItemVariants, + listVariants, +} from '../../../lib/animation/listAnimation'; +import { Option, OptionTypes } from './Option'; + +const defaultOptions: OptionTypes[] = [ + OptionTypes.SWAP, + OptionTypes.DEBIT, + OptionTypes.CREDIT, +]; + +export interface OptionsProps { + onClick: (type: OptionTypes) => void; + disabledOptions?: OptionTypes[]; + options?: OptionTypes[]; + captions?: Partial>; + size?: MenuItemSize; + hideDisabledOptions?: boolean; +} + +export function Options(props: OptionsProps) { + const { + disabledOptions = [], + options, + onClick, + captions, + size, + hideDisabledOptions, + } = props; + const filteredOptions = useMemo( + () => (options || defaultOptions).filter( + (option) => !hideDisabledOptions || !disabledOptions.includes(option), + ), + [options, disabledOptions, hideDisabledOptions], + ); + + useEffect(() => { + if (filteredOptions.length === 1) { + onClick(filteredOptions[0]); + } + }, [options, onClick]); + + return ( + + } + > + {filteredOptions.map((type, idx: number) => ( + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/components/OptionsDrawer.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/components/OptionsDrawer.tsx new file mode 100644 index 0000000000..3e766703d4 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/components/OptionsDrawer.tsx @@ -0,0 +1,60 @@ +import { Box, Drawer } from '@biom3/react'; +import { motion } from 'framer-motion'; +import { listVariants } from '../../../lib/animation/listAnimation'; +import { Options } from './Options'; +import { OptionTypes } from './Option'; + +type OptionsDrawerProps = { + visible: boolean; + onClose: () => void; + onPayWithCard?: (paymentType: OptionTypes) => void; +}; + +export function OptionsDrawer({ + visible, + onClose, + onPayWithCard, +}: OptionsDrawerProps) { + return ( + + + } + > + + {})} + size="medium" + hideDisabledOptions + options={[ + OptionTypes.SWAP, + OptionTypes.DEBIT, + OptionTypes.CREDIT, + ]} + disabledOptions={[]} + captions={{ + [OptionTypes.SWAP]: 'Swap', + [OptionTypes.DEBIT]: 'Debit', + [OptionTypes.CREDIT]: 'Credit', + }} + /> + + + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/context/AddFundsContext.ts b/packages/checkout/widgets-lib/src/widgets/add-funds/context/AddFundsContext.ts index cc77bc2cee..a9e99a74d9 100644 --- a/packages/checkout/widgets-lib/src/widgets/add-funds/context/AddFundsContext.ts +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/context/AddFundsContext.ts @@ -1,15 +1,17 @@ import { Web3Provider } from '@ethersproject/providers'; import { createContext } from 'react'; -import { Checkout } from '@imtbl/checkout-sdk'; +import { Checkout, TokenInfo } from '@imtbl/checkout-sdk'; export interface AddFundsState { checkout: Checkout | null; provider: Web3Provider | null; + allowedTokens: TokenInfo[] | null; } export const initialAddFundsState: AddFundsState = { checkout: null, provider: null, + allowedTokens: null, }; export interface AddFundsContextState { @@ -22,12 +24,14 @@ export interface AddFundsAction { } type ActionPayload = - | SetCheckoutPayload - | SetProviderPayload; + | SetCheckoutPayload + | SetProviderPayload + | SetAllowedTokensPayload; export enum AddFundsActions { SET_CHECKOUT = 'SET_CHECKOUT', SET_PROVIDER = 'SET_PROVIDER', + SET_ALLOWED_TOKENS = 'SET_ALLOWED_TOKENS', } export interface SetCheckoutPayload { @@ -40,11 +44,15 @@ export interface SetProviderPayload { provider: Web3Provider; } +export interface SetAllowedTokensPayload { + type: AddFundsActions.SET_ALLOWED_TOKENS; + allowedTokens: TokenInfo[]; +} + // eslint-disable-next-line @typescript-eslint/naming-convention export const AddFundsContext = createContext({ addFundsState: initialAddFundsState, - addFundsDispatch: () => { - }, + addFundsDispatch: () => {}, }); AddFundsContext.displayName = 'AddFundsContext'; @@ -66,6 +74,11 @@ export const addFundsReducer: Reducer = ( ...state, provider: action.payload.provider, }; + case AddFundsActions.SET_ALLOWED_TOKENS: + return { + ...state, + allowedTokens: action.payload.allowedTokens, + }; default: return state; } diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/views/AddFunds.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/views/AddFunds.tsx index 1e58dd5f98..c4ad4cf0f8 100644 --- a/packages/checkout/widgets-lib/src/widgets/add-funds/views/AddFunds.tsx +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/views/AddFunds.tsx @@ -1,15 +1,24 @@ /* eslint-disable no-console */ import { Web3Provider } from '@ethersproject/providers'; -import { Checkout } from '@imtbl/checkout-sdk'; import { - Body, - Box, Button, MenuItem, OverflowPopoverMenu, + Checkout, + IMTBLWidgetEvents, + TokenFilterTypes, + TokenInfo, +} from '@imtbl/checkout-sdk'; +import { + Body, Box, MenuItem, OverflowPopoverMenu, } from '@biom3/react'; -import { useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { SimpleLayout } from '../../../components/SimpleLayout/SimpleLayout'; import { HeaderNavigation } from '../../../components/Header/HeaderNavigation'; import { amountInputValidation } from '../../../lib/validations/amountInputValidations'; import { TextInputForm } from '../../../components/FormComponents/TextInputForm/TextInputForm'; +import { OptionsDrawer } from '../components/OptionsDrawer'; +import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext'; +import { orchestrationEvents } from '../../../lib/orchestrationEvents'; +import { OptionTypes } from '../components/Option'; +import { AddFundsActions, AddFundsContext } from '../context/AddFundsContext'; interface AddFundsProps { checkout?: Checkout; @@ -24,10 +33,15 @@ interface AddFundsProps { } export function AddFunds({ - checkout, provider, amount, tokenAddress, - showOnrampOption = true, showSwapOption = true, showBridgeOption = true, - onBackButtonClick, onCloseButtonClick, - + checkout, + provider, + amount, + tokenAddress, + showOnrampOption = true, + showSwapOption = true, + showBridgeOption = true, + onBackButtonClick, + onCloseButtonClick, }: AddFundsProps) { console.log('checkout', checkout); console.log('provider', provider); @@ -36,43 +50,83 @@ export function AddFunds({ console.log('showBridgeOption', showBridgeOption); console.log('onCloseButtonClick', onCloseButtonClick); + const { addFundsDispatch } = useContext(AddFundsContext); + + const { + eventTargetState: { eventTarget }, + } = useContext(EventTargetContext); + + const [showOptionsDrawer, setShowOptionsDrawer] = useState(false); + const [allowedTokens, setAllowedTokens] = useState([]); const [toAmount, setToAmount] = useState(amount || ''); - // eslint-disable-next-line max-len - const [toTokenAddress, setToTokenAddress] = useState(tokenAddress || '0x6B175474E89094C44Da98b954EedeAC495271'); - - // TODO: get the tokens from the new method - const tokens = [ - { - name: 'USDC', - address: '0x6B175474E89094C44Da98b954EedeAC495271', - }, - { - name: 'DAI', - address: '0x6B175474E89094C44Da98b954EedeAC495272', - }, - { - name: 'USDT', - address: '0x6B175474E89094C44Da98b954EedeedeACasd', - }, - ]; + const [toTokenAddress, setToTokenAddress] = useState(); + + useEffect(() => { + if (!checkout) return; + const fetchTokens = async () => { + const tokenResponse = await checkout.getTokenAllowList({ type: TokenFilterTypes.SWAP, chainId: 1 }); + + if (tokenResponse?.tokens.length > 0) { + setAllowedTokens(tokenResponse.tokens); + + if (tokenAddress) { + const token = allowedTokens.find((t) => t.address === tokenAddress); + setToTokenAddress(token); + } else { + setToTokenAddress(tokenResponse.tokens[0]); + } + + addFundsDispatch({ + payload: { + type: AddFundsActions.SET_ALLOWED_TOKENS, + allowedTokens: tokenResponse.tokens, + }, + }); + } + }; + fetchTokens(); + }, [checkout]); + + const openDrawer = () => { + setShowOptionsDrawer(true); + }; const updateAmount = (value: string) => { setToAmount(value); }; - const isSelected = (token: any) => token.address === toTokenAddress; + const isSelected = (token: TokenInfo) => token.address === toTokenAddress; - const handleTokenChange = (token) => { - setToTokenAddress(token.address); + const handleTokenChange = (token: TokenInfo) => { + setToTokenAddress(token); }; - const fromAddressToTokenName = (address: string) => { - const token = tokens.find((t) => t.address === address); - return token?.name || ''; - }; + // const handleReviewClick = () => { + // console.log('handle review click'); + // }; + + const onPayWithCard = (paymentType: OptionTypes) => { + console.log('paymentType', paymentType); + console.log('=== toTokenAddress', toTokenAddress); + console.log('=== toAmount', toAmount); - const handleReviewClick = () => { - console.log('handle review click'); + if (paymentType === OptionTypes.SWAP) { + orchestrationEvents.sendRequestSwapEvent( + eventTarget, + IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT, + { toTokenAddress: toTokenAddress?.address ?? '', amount: toAmount ?? '', fromTokenAddress: '' }, + ); + } else { + const data = { + tokenAddress: tokenAddress ?? '', + amount: amount ?? '', + }; + orchestrationEvents.sendRequestOnrampEvent( + eventTarget, + IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT, + data, + ); + } }; return ( @@ -86,11 +140,21 @@ export function AddFunds({ /> )} > - - + - - {fromAddressToTokenName(toTokenAddress)} + + {toTokenAddress ? toTokenAddress.name : ''} + - {tokens.map((token: any) => ( - handleTokenChange(token)} selected={isSelected(token)}> + {allowedTokens.map((token: any) => ( + handleTokenChange(token)} + selected={isSelected(token)} + > {token.name} ))} - - + */} - ); } diff --git a/packages/checkout/widgets-sample-app/src/components/ui/add-funds/addFunds.tsx b/packages/checkout/widgets-sample-app/src/components/ui/add-funds/addFunds.tsx index 6bb32b471e..c4930c866e 100644 --- a/packages/checkout/widgets-sample-app/src/components/ui/add-funds/addFunds.tsx +++ b/packages/checkout/widgets-sample-app/src/components/ui/add-funds/addFunds.tsx @@ -21,7 +21,7 @@ function AddFundsUI() { ); const addFunds = useMemo( () => factory.create(WidgetType.ADD_FUNDS, { - config: { theme: WidgetTheme.LIGHT }, + config: { theme: WidgetTheme.DARK }, }), [factory] ); @@ -47,7 +47,10 @@ function AddFundsUI() { console.log("CLOSE_WIDGET", data); onRamp.unmount(); }); - onRamp.mount(ADD_FUNDS_TARGET_ID, {}); + onRamp.mount(ADD_FUNDS_TARGET_ID, { + amount: data.amount, + tokenAddress: data.tokenAddress, + }); }); addFunds.addListener(AddFundsEventType.REQUEST_SWAP, (data: any) => { console.log("REQUEST_SWAP", data); @@ -56,7 +59,10 @@ function AddFundsUI() { console.log("CLOSE_WIDGET", data); swap.unmount(); }); - swap.mount(ADD_FUNDS_TARGET_ID, {}); + swap.mount(ADD_FUNDS_TARGET_ID, { + amount: data.amount, + toTokenAddress: data.toTokenAddress, + }); }); addFunds.addListener(AddFundsEventType.REQUEST_BRIDGE, (data: any) => { console.log("REQUEST_BRIDGE", data);