From 08b83c51165c83a75ca63a71278f4e48e492db6d Mon Sep 17 00:00:00 2001 From: Jhonatan Gonzalez Date: Fri, 20 Sep 2024 13:05:07 +1000 Subject: [PATCH] [NO CHANGELOG][Checkout widget] Feature: Add Checkout Widget (#2188) --- .../definitions/configurations/checkout.ts | 12 +- .../definitions/configurations/index.ts | 1 + .../definitions/events/orchestration.ts | 6 +- .../definitions/parameters/checkout.ts | 21 +- .../sdk/src/widgets/definitions/types.ts | 12 +- .../ConnectLoaderContext.ts | 8 +- .../EventTargetContext.ts | 8 +- .../CheckoutWidgetViewContextTypes.ts | 83 +++++ .../{ViewContext.ts => ViewContext.tsx} | 55 ++- .../checkout/widgets-lib/src/lib/constants.ts | 9 - .../src/widgets/checkout/CheckoutWidget.tsx | 234 ++++++++++--- .../widgets/checkout/CheckoutWidgetEvents.ts | 10 +- .../widgets/checkout/CheckoutWidgetRoot.tsx | 9 +- .../checkout/context/CheckoutContext.ts | 96 +----- .../context/CheckoutContextProvider.tsx | 73 ++-- .../src/widgets/checkout/functions/.gitkeep | 0 .../functions/getCheckoutWidgetEvent.ts | 320 ++++++++++++++++++ .../functions/getConnectLoaderParams.ts | 58 ++++ .../getViewFromOrchestrationEventType.ts | 25 ++ .../functions/getViewShouldConnect.ts | 19 ++ .../checkout/functions/iframeParams.ts | 174 ---------- .../functions/isOrchestrationEvent.ts | 21 ++ .../hooks/useCheckoutEventsRelayer.ts | 71 ---- .../checkout/hooks/useEip6963Relayer.ts | 45 --- .../checkout/hooks/useProviderRelay.ts | 138 -------- .../widgets/checkout/hooks/useWidgetEvents.ts | 95 ++++++ .../hooks/useWidgetProviderEventRelayer.ts | 23 -- .../src/widgets/checkout/utils/.gitkeep | 0 .../src/widgets/checkout/utils/config.ts | 23 -- .../src/widgets/checkout/utils/encode.ts | 26 -- .../checkout/views/CheckoutAppIframe.tsx | 130 ------- .../src/components/ui/checkout/checkout.tsx | 62 ++-- 32 files changed, 972 insertions(+), 895 deletions(-) create mode 100644 packages/checkout/widgets-lib/src/context/view-context/CheckoutWidgetViewContextTypes.ts rename packages/checkout/widgets-lib/src/context/view-context/{ViewContext.ts => ViewContext.tsx} (79%) create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/functions/.gitkeep create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/functions/getCheckoutWidgetEvent.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/functions/getConnectLoaderParams.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewFromOrchestrationEventType.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewShouldConnect.ts delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/functions/isOrchestrationEvent.ts delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/hooks/useCheckoutEventsRelayer.ts delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/hooks/useEip6963Relayer.ts delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/hooks/useProviderRelay.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetEvents.ts delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetProviderEventRelayer.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/utils/.gitkeep delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/utils/encode.ts delete mode 100644 packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx diff --git a/packages/checkout/sdk/src/widgets/definitions/configurations/checkout.ts b/packages/checkout/sdk/src/widgets/definitions/configurations/checkout.ts index e32b9238e4..c130dc0b32 100644 --- a/packages/checkout/sdk/src/widgets/definitions/configurations/checkout.ts +++ b/packages/checkout/sdk/src/widgets/definitions/configurations/checkout.ts @@ -9,10 +9,10 @@ import { SaleWidgetConfiguration } from './sale'; import { WidgetConfiguration } from './widget'; export type CheckoutWidgetConfiguration = { - connect?: Omit; - wallet?: Omit; - swap?: Omit; - bridge?: Omit; - onRamp?: Omit; - sale?: Omit; + CONNECT?: Omit; + WALLET?: Omit; + SWAP?: Omit; + BRIDGE?: Omit; + ONRAMP?: Omit; + SALE?: Omit; } & Omit; diff --git a/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts b/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts index 1927000b2a..8fa1e8f726 100644 --- a/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts +++ b/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts @@ -6,4 +6,5 @@ export * from './onramp'; export * from './sale'; export * from './theme'; export * from './widget'; +export * from './addFunds'; export * from './checkout'; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts index ff9edd35ef..be2e1edd93 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts @@ -81,10 +81,10 @@ export type RequestGoBackEvent = { }; /* -* Type representing the orchestration events. -*/ + * Type representing the orchestration events. + */ export type OrchestrationEventData = - RequestConnectEvent + | RequestConnectEvent | RequestWalletEvent | RequestSwapEvent | RequestBridgeEvent diff --git a/packages/checkout/sdk/src/widgets/definitions/parameters/checkout.ts b/packages/checkout/sdk/src/widgets/definitions/parameters/checkout.ts index 4d25d17332..a7bae39c0e 100644 --- a/packages/checkout/sdk/src/widgets/definitions/parameters/checkout.ts +++ b/packages/checkout/sdk/src/widgets/definitions/parameters/checkout.ts @@ -6,14 +6,16 @@ import { WalletWidgetParams } from './wallet'; import { SwapWidgetParams } from './swap'; import { OnRampWidgetParams } from './onramp'; import { SaleWidgetParams } from './sale'; +import { AddFundsWidgetParams } from './addFunds'; export enum CheckoutFlowType { - CONNECT = 'connect', - WALLET = 'wallet', - SWAP = 'swap', - BRIDGE = 'bridge', - ONRAMP = 'onramp', - SALE = 'sale', + CONNECT = 'CONNECT', + WALLET = 'WALLET', + SALE = 'SALE', + SWAP = 'SWAP', + BRIDGE = 'BRIDGE', + ONRAMP = 'ONRAMP', + ADD_FUNDS = 'ADD_FUNDS', } export type CheckoutWidgetConnectFlowParams = { @@ -40,13 +42,18 @@ export type CheckouWidgetSaleFlowParams = { flow: CheckoutFlowType.SALE; } & SaleWidgetParams; +export type CheckouWidgetAddFundsFlowParams = { + flow: CheckoutFlowType.ADD_FUNDS; +} & AddFundsWidgetParams; + export type CheckoutWidgetFlowParams = | CheckoutWidgetConnectFlowParams | CheckoutWidgetWalletFlowParams | CheckouWidgetSwapFlowParams | CheckouWidgetBridgeFlowParams | CheckouWidgetOnRampFlowParams - | CheckouWidgetSaleFlowParams; + | CheckouWidgetSaleFlowParams + | CheckouWidgetAddFundsFlowParams; export type CheckoutWidgetParams = { /** The language to use for the checkout widget */ diff --git a/packages/checkout/sdk/src/widgets/definitions/types.ts b/packages/checkout/sdk/src/widgets/definitions/types.ts index 50e519a27d..736c36f8ef 100644 --- a/packages/checkout/sdk/src/widgets/definitions/types.ts +++ b/packages/checkout/sdk/src/widgets/definitions/types.ts @@ -41,6 +41,7 @@ import { CheckoutUserActionEvent, RequestAddFundsEvent, RequestGoBackEvent, + AddFundsEventType, } from './events'; import { BridgeWidgetParams, @@ -49,6 +50,7 @@ import { WalletWidgetParams, OnRampWidgetParams, CheckoutWidgetParams, + AddFundsWidgetParams, } from './parameters'; import { SaleWidgetParams } from './parameters/sale'; import { @@ -59,11 +61,9 @@ import { SwapWidgetConfiguration, WalletWidgetConfiguration, CheckoutWidgetConfiguration, + AddFundsWidgetConfiguration, } from './configurations'; import { WidgetTheme } from './configurations/theme'; -import { AddFundsWidgetConfiguration } from './configurations/addFunds'; -import { AddFundsWidgetParams } from './parameters/addFunds'; -import { AddFundsEventType } from './events/addFunds'; /** * Enum representing the list of widget types. @@ -94,8 +94,9 @@ export type WidgetConfigurations = { [WidgetType.BRIDGE]: BridgeWidgetConfiguration; [WidgetType.ONRAMP]: OnrampWidgetConfiguration; [WidgetType.SALE]: SaleWidgetConfiguration; - [WidgetType.CHECKOUT]: CheckoutWidgetConfiguration; [WidgetType.ADD_FUNDS]: AddFundsWidgetConfiguration; + + [WidgetType.CHECKOUT]: CheckoutWidgetConfiguration; }; // Mapping each widget type to their parameters @@ -106,8 +107,9 @@ export type WidgetParameters = { [WidgetType.BRIDGE]: BridgeWidgetParams; [WidgetType.ONRAMP]: OnRampWidgetParams; [WidgetType.SALE]: SaleWidgetParams; - [WidgetType.CHECKOUT]: CheckoutWidgetParams; [WidgetType.ADD_FUNDS]: AddFundsWidgetParams; + + [WidgetType.CHECKOUT]: CheckoutWidgetParams; }; /** diff --git a/packages/checkout/widgets-lib/src/context/connect-loader-context/ConnectLoaderContext.ts b/packages/checkout/widgets-lib/src/context/connect-loader-context/ConnectLoaderContext.ts index 61f3d21bd8..079fd25348 100644 --- a/packages/checkout/widgets-lib/src/context/connect-loader-context/ConnectLoaderContext.ts +++ b/packages/checkout/widgets-lib/src/context/connect-loader-context/ConnectLoaderContext.ts @@ -1,4 +1,4 @@ -import { createContext } from 'react'; +import { createContext, useReducer } from 'react'; import { Checkout } from '@imtbl/checkout-sdk'; import { Web3Provider } from '@ethersproject/providers'; import { ConnectWidgetViews } from '../view-context/ConnectViewContextTypes'; @@ -91,3 +91,9 @@ ConnectLoaderAction return state; } }; + +export const useConnectLoaderState = () => { + const [connectLoaderState, connectLoaderDispatch] = useReducer(connectLoaderReducer, initialConnectLoaderState); + + return [connectLoaderState, connectLoaderDispatch] as const; +}; diff --git a/packages/checkout/widgets-lib/src/context/event-target-context/EventTargetContext.ts b/packages/checkout/widgets-lib/src/context/event-target-context/EventTargetContext.ts index 40bcf9659d..4b87083c3f 100644 --- a/packages/checkout/widgets-lib/src/context/event-target-context/EventTargetContext.ts +++ b/packages/checkout/widgets-lib/src/context/event-target-context/EventTargetContext.ts @@ -1,4 +1,4 @@ -import { createContext } from 'react'; +import { createContext, useReducer } from 'react'; export interface EventTargetState { eventTarget: Window | EventTarget @@ -50,3 +50,9 @@ EventTargetAction return state; } }; + +export const useEventTargetState = () => { + const [eventTargetState, eventTargetDispatch] = useReducer(eventTargetReducer, initialEventTargetState); + + return [eventTargetState, eventTargetDispatch] as const; +}; diff --git a/packages/checkout/widgets-lib/src/context/view-context/CheckoutWidgetViewContextTypes.ts b/packages/checkout/widgets-lib/src/context/view-context/CheckoutWidgetViewContextTypes.ts new file mode 100644 index 0000000000..4ae474775a --- /dev/null +++ b/packages/checkout/widgets-lib/src/context/view-context/CheckoutWidgetViewContextTypes.ts @@ -0,0 +1,83 @@ +import { + BridgeWidgetConfiguration, + BridgeWidgetParams, + OnrampWidgetConfiguration, + OnRampWidgetParams, + SaleWidgetConfiguration, + SaleWidgetParams, + SwapWidgetConfiguration, + SwapWidgetParams, + AddFundsWidgetParams, + AddFundsWidgetConfiguration, + CheckoutFlowType, + ConnectWidgetParams, + ConnectWidgetConfiguration, + WalletWidgetParams, + WalletWidgetConfiguration, +} from '@imtbl/checkout-sdk'; +import { ViewType } from './ViewType'; + +export type CheckoutWidgetView = + | ConnectView + | WalletView + | AddFundsView + | SaleView + | SwapView + | OnRampView + | BrdigeView; + +interface ConnectView extends ViewType { + type: CheckoutFlowType.CONNECT; + data: { + params: ConnectWidgetParams; + config: ConnectWidgetConfiguration; + }; +} + +interface WalletView extends ViewType { + type: CheckoutFlowType.WALLET; + data: { + params: WalletWidgetParams; + config: WalletWidgetConfiguration; + }; +} + +interface AddFundsView extends ViewType { + type: CheckoutFlowType.ADD_FUNDS; + data: { + params: AddFundsWidgetParams; + config: AddFundsWidgetConfiguration; + }; +} + +interface SaleView extends ViewType { + type: CheckoutFlowType.SALE; + data: { + params: Required; + config: SaleWidgetConfiguration; + }; +} + +interface SwapView extends ViewType { + type: CheckoutFlowType.SWAP; + data: { + params: SwapWidgetParams; + config: SwapWidgetConfiguration; + }; +} + +interface OnRampView extends ViewType { + type: CheckoutFlowType.ONRAMP; + data: { + params: OnRampWidgetParams; + config: OnrampWidgetConfiguration; + }; +} + +interface BrdigeView extends ViewType { + type: CheckoutFlowType.BRIDGE; + data: { + params: BridgeWidgetParams; + config: BridgeWidgetConfiguration; + }; +} diff --git a/packages/checkout/widgets-lib/src/context/view-context/ViewContext.ts b/packages/checkout/widgets-lib/src/context/view-context/ViewContext.tsx similarity index 79% rename from packages/checkout/widgets-lib/src/context/view-context/ViewContext.ts rename to packages/checkout/widgets-lib/src/context/view-context/ViewContext.tsx index dc55fd0ec3..98cc2eb82d 100644 --- a/packages/checkout/widgets-lib/src/context/view-context/ViewContext.ts +++ b/packages/checkout/widgets-lib/src/context/view-context/ViewContext.tsx @@ -1,4 +1,6 @@ -import { createContext } from 'react'; +import { + createContext, ReactNode, useMemo, useReducer, +} from 'react'; import { ConnectWidgetView } from './ConnectViewContextTypes'; import { WalletWidgetView } from './WalletViewContextTypes'; import { PrefilledSwapForm, SwapWidgetView } from './SwapViewContextTypes'; @@ -7,28 +9,30 @@ import { SaleWidgetView } from './SaleViewContextTypes'; import { ViewType } from './ViewType'; import { OnRampWidgetView } from './OnRampViewContextTypes'; import { AddFundsWidgetView } from './AddFundsViewContextTypes'; +import { CheckoutWidgetView } from './CheckoutWidgetViewContextTypes'; export enum SharedViews { LOADING_VIEW = 'LOADING_VIEW', ERROR_VIEW = 'ERROR_VIEW', - SERVICE_UNAVAILABLE_ERROR_VIEW = 'SERVICE_UNAVAILABLE_ERROR_VIEW', + SUCCESS_VIEW = 'SUCCESS_VIEW', TOP_UP_VIEW = 'TOP_UP_VIEW', + SERVICE_UNAVAILABLE_ERROR_VIEW = 'SERVICE_UNAVAILABLE_ERROR_VIEW', } export type SharedView = - LoadingView + | LoadingView | ErrorView | ServiceUnavailableErrorView | TopUpView; interface LoadingView extends ViewType { - type: SharedViews.LOADING_VIEW + type: SharedViews.LOADING_VIEW; } export interface ErrorView extends ViewType { type: SharedViews.ERROR_VIEW; error: Error; - tryAgain?: () => Promise + tryAgain?: () => Promise; } interface ServiceUnavailableErrorView extends ViewType { @@ -37,19 +41,20 @@ interface ServiceUnavailableErrorView extends ViewType { } interface TopUpView extends ViewType { - type: SharedViews.TOP_UP_VIEW, - swapData?: PrefilledSwapForm, + type: SharedViews.TOP_UP_VIEW; + swapData?: PrefilledSwapForm; } export type View = - SharedView + | SharedView | ConnectWidgetView | WalletWidgetView | SwapWidgetView | OnRampWidgetView | SaleWidgetView | BridgeWidgetView - | AddFundsWidgetView; + | AddFundsWidgetView + | CheckoutWidgetView; export interface ViewState { view: View; @@ -98,7 +103,7 @@ export interface GoBackToPayload { // eslint-disable-next-line @typescript-eslint/naming-convention export const ViewContext = createContext({ viewState: initialViewState, - viewDispatch: () => { }, + viewDispatch: () => {}, }); ViewContext.displayName = 'ViewContext'; // help with debugging Context in browser @@ -124,7 +129,10 @@ export const viewReducer: Reducer = ( ) { // currentViewData should only be set on the current view before updating if (currentViewData) { - history[history.length - 1] = { ...history[history.length - 1], data: currentViewData }; + history[history.length - 1] = { + ...history[history.length - 1], + data: currentViewData, + }; } history.push(view); @@ -177,3 +185,28 @@ export const viewReducer: Reducer = ( return state; } }; + +export const useViewState = () => { + const [viewState, viewDispatch] = useReducer(viewReducer, initialViewState); + + return [viewState, viewDispatch] as const; +}; + +type ViewContextProviderProps = { + children: ReactNode; +}; + +export function ViewContextProvider({ children }: ViewContextProviderProps) { + const [viewState, viewDispatch] = useViewState(); + + const viewReducerValues = useMemo( + () => ({ viewState, viewDispatch }), + [viewState, viewDispatch], + ); + + return ( + + {children} + + ); +} diff --git a/packages/checkout/widgets-lib/src/lib/constants.ts b/packages/checkout/widgets-lib/src/lib/constants.ts index a90f7768d6..f1bc8f27e7 100644 --- a/packages/checkout/widgets-lib/src/lib/constants.ts +++ b/packages/checkout/widgets-lib/src/lib/constants.ts @@ -115,12 +115,3 @@ export const IMAGE_RESIZER_URL = { }; export const WITHDRAWAL_CLAIM_GAS_LIMIT = 91000; - -/** - * URL for Checkout App based on the environment. - */ -export const CHECKOUT_APP_URL = { - [ENV_DEVELOPMENT]: 'https://checkout.dev.immutable.com', - [Environment.SANDBOX]: 'https://checkout.sandbox.immutable.com', - [Environment.PRODUCTION]: 'https://checkout.immutable.com', -}; diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx index 216528eb74..3b6ba89b1a 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx @@ -1,77 +1,205 @@ -import { useEffect, useMemo, useReducer } from 'react'; +import { Suspense, useEffect, useMemo } from 'react'; import { - Checkout, - CheckoutWidgetConfiguration, + CheckoutEventType, CheckoutWidgetParams, - PostMessageHandlerEventType, + CheckoutFlowType, + CheckoutWidgetConfiguration, + Checkout, } from '@imtbl/checkout-sdk'; +import { useTranslation } from 'react-i18next'; import { Web3Provider } from '@ethersproject/providers'; +import { CheckoutWidgetContextProvicer } from './context/CheckoutContextProvider'; import { - CheckoutActions, - checkoutReducer, - initialCheckoutState, -} from './context/CheckoutContext'; -import { CheckoutContextProvider } from './context/CheckoutContextProvider'; -import { CheckoutAppIframe } from './views/CheckoutAppIframe'; -import { getIframeURL } from './functions/iframeParams'; -import { useMount } from './hooks/useMount'; -import { useAsyncMemo } from './hooks/useAsyncMemo'; + useViewState, + SharedViews, + ViewContextProvider, + ViewActions, +} from '../../context/view-context/ViewContext'; +import { LoadingView } from '../../views/loading/LoadingView'; +import { sendCheckoutEvent } from './CheckoutWidgetEvents'; +import { useEventTargetState } from '../../context/event-target-context/EventTargetContext'; +import { ErrorView } from '../../views/error/ErrorView'; +import { StrongCheckoutWidgetsConfig } from '../../lib/withDefaultWidgetConfig'; +import SwapWidget from '../swap/SwapWidget'; +import { ConnectLoader } from '../../components/ConnectLoader/ConnectLoader'; +import ConnectWidget from '../connect/ConnectWidget'; +import BridgeWidget from '../bridge/BridgeWidget'; +import OnRampWidget from '../on-ramp/OnRampWidget'; +import WalletWidget from '../wallet/WalletWidget'; +import SaleWidget from '../sale/SaleWidget'; +import AddFundsWidget from '../add-funds/AddFundsWidget'; +import { getViewShouldConnect } from './functions/getViewShouldConnect'; +import { useWidgetEvents } from './hooks/useWidgetEvents'; +import { getConnectLoaderParams } from './functions/getConnectLoaderParams'; export type CheckoutWidgetInputs = { checkout: Checkout; - params: CheckoutWidgetParams; - config: CheckoutWidgetConfiguration; - provider?: Web3Provider; + web3Provider?: Web3Provider; + flowParams: CheckoutWidgetParams; + flowConfig: CheckoutWidgetConfiguration; + widgetsConfig: StrongCheckoutWidgetsConfig; }; export default function CheckoutWidget(props: CheckoutWidgetInputs) { const { - config, checkout, params, provider, + flowParams, flowConfig, widgetsConfig, checkout, web3Provider, } = props; - const iframeURL = useAsyncMemo( - async () => getIframeURL(params, config, checkout.config), - [params, config, checkout.config], - ); + const { t } = useTranslation(); + const viewState = useViewState(); + const [{ view }, viewDispatch] = viewState; + const [{ eventTarget }] = useEventTargetState(); - const [checkoutState, checkoutDispatch] = useReducer( - checkoutReducer, - initialCheckoutState, - ); - const checkoutReducerValues = useMemo( - () => ({ - checkoutState: { ...checkoutState, iframeURL, checkout }, - checkoutDispatch, - }), - [checkoutState, checkoutDispatch, iframeURL, checkout], + const connectLoaderParams = useMemo( + () => getConnectLoaderParams(view, checkout, web3Provider), + [view, checkout, web3Provider], ); - // If the widget was initialized with a provider, - // notify iframe via postMessage - const { postMessageHandler } = checkoutState; - useMount( - () => { - if (!provider) return; + /** + * Subscribe and Handle widget events + */ + useWidgetEvents(eventTarget, viewState); - postMessageHandler?.send(PostMessageHandlerEventType.PROVIDER_UPDATED, { - isMetamask: provider.provider.isMetaMask, - isPassport: (provider.provider as any)?.isPassport, - }); - }, - [postMessageHandler, provider] as const, - ([_postMessageHandler]) => _postMessageHandler !== undefined, - ); - - // keep the provider updated in the state + /** + * Mount the view according to set flow in params + */ useEffect(() => { - checkoutDispatch({ - payload: { type: CheckoutActions.SET_PROVIDER, provider }, + if (!flowParams.flow) return; + + const { flow, ...mountedWidgetParams } = flowParams; + + viewDispatch({ + payload: { + type: ViewActions.UPDATE_VIEW, + view: { + type: flow as any, + data: { + params: mountedWidgetParams, + config: { ...(flowConfig?.[flow] || {}) }, + }, + }, + }, }); - }, [provider]); + }, [flowParams]); + + const showBackButton = !!view.data?.showBackButton; + + const shouldConnectView = useMemo( + () => getViewShouldConnect(view.type), + [view.type], + ); return ( - - - + + + {/* --- Status Views --- */} + {view.type === SharedViews.LOADING_VIEW && ( + + )} + {view.type === SharedViews.SERVICE_UNAVAILABLE_ERROR_VIEW && ( + { + sendCheckoutEvent(eventTarget, { + type: CheckoutEventType.CLOSE, + data: {}, + }); + }} + onActionClick={() => { + // TODO: trigger a retry + }} + actionText={t('views.ERROR_VIEW.actionText')} + /> + )} + {/* --- Widgets without connect --- */} + {view.type === CheckoutFlowType.CONNECT && ( + { + sendCheckoutEvent(eventTarget, { + type: CheckoutEventType.CLOSE, + data: {}, + }); + }} + {...(view.data.params || {})} + /> + )} + {view.type === CheckoutFlowType.BRIDGE && ( + + )} + {/* --- Widgets that require connect --- */} + {shouldConnectView && ( + { + sendCheckoutEvent(eventTarget, { + type: CheckoutEventType.CLOSE, + data: {}, + }); + }} + > + + } + > + {view.type === CheckoutFlowType.WALLET && ( + + )} + {view.type === CheckoutFlowType.SALE && ( + + )} + {view.type === CheckoutFlowType.ADD_FUNDS && ( + + )} + {view.type === CheckoutFlowType.SWAP && ( + + )} + {view.type === CheckoutFlowType.ONRAMP && ( + + )} + + + )} + + ); } diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetEvents.ts b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetEvents.ts index 45a3178005..a1165904e3 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetEvents.ts +++ b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetEvents.ts @@ -6,12 +6,14 @@ import { WidgetEventData, } from '@imtbl/checkout-sdk'; +export type CheckoutEventDetail = { + type: CheckoutEventType; + data: WidgetEventData[WidgetType.CHECKOUT][keyof WidgetEventData[WidgetType.CHECKOUT]]; +}; + export const sendCheckoutEvent = ( eventTarget: Window | EventTarget, - detail: { - type: CheckoutEventType; - data: WidgetEventData[WidgetType.CHECKOUT][keyof WidgetEventData[WidgetType.CHECKOUT]]; - }, + detail: CheckoutEventDetail, ) => { const event = new CustomEvent< WidgetEvent diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx index 3704a70fc2..1ba1ada713 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx @@ -46,8 +46,8 @@ export class CheckoutWidgetRoot extends Base { protected render() { if (!this.reactRoot) return; + const { t } = i18n; - const config = this.properties.config || {}; this.reactRoot.render( @@ -59,10 +59,11 @@ export class CheckoutWidgetRoot extends Base { } > diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContext.ts b/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContext.ts index e3af702eb2..fe5a6c25be 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContext.ts +++ b/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContext.ts @@ -1,34 +1,20 @@ import { Web3Provider } from '@ethersproject/providers'; import { createContext } from 'react'; import { - Checkout, EIP6963ProviderInfo, PostMessageHandler, WalletProviderName, + Checkout, } from '@imtbl/checkout-sdk'; import { Passport } from '@imtbl/passport'; export interface CheckoutState { - checkout: Checkout | null; + checkout: Checkout | undefined; provider: Web3Provider | undefined; passport: Passport | undefined; - iframeURL: string | undefined; - iframeContentWindow: Window | undefined; - postMessageHandler: PostMessageHandler | undefined; - walletProviderName: WalletProviderName | null; - walletProviderInfo: EIP6963ProviderInfo | null; - sendCloseEvent: () => void; - initialised: boolean; } export const initialCheckoutState: CheckoutState = { - checkout: null, + checkout: undefined, provider: undefined, passport: undefined, - iframeURL: undefined, - iframeContentWindow: undefined, - postMessageHandler: undefined, - walletProviderInfo: null, - walletProviderName: null, - sendCloseEvent: () => { }, - initialised: false, }; export interface CheckoutContextState { @@ -43,24 +29,12 @@ export interface CheckoutAction { type ActionPayload = | SetCheckoutPayload | SetProviderPayload - | SetIframeURLPayload - | SetPostMessageHandlerPayload - | SetIframeContentWindowPayload - | SetPassportPayload - | SetProviderNamePayload - | SetSendCloseEventPayload - | SetInitialisedPayload; + | SetPassportPayload; export enum CheckoutActions { SET_CHECKOUT = 'SET_CHECKOUT', SET_PROVIDER = 'SET_PROVIDER', - SET_IFRAME_URL = 'SET_IFRAME_URL', - SET_POST_MESSAGE_HANDLER = 'SET_POST_MESSAGE_HANDLER', - SET_CHECKOUT_APP_IFRAME = 'SET_CHECKOUT_APP_IFRAME', SET_PASSPORT = 'SET_PASSPORT', - SET_WALLET_PROVIDER_NAME = 'SET_WALLET_PROVIDER_NAME', - SET_SEND_CLOSE_EVENT = 'SET_SEND_CLOSE_EVENT', - SET_INITIALISED = 'SET_INITIALISED', } export interface SetCheckoutPayload { @@ -73,39 +47,9 @@ export interface SetProviderPayload { provider: Web3Provider | undefined; } -export interface SetIframeURLPayload { - type: CheckoutActions.SET_IFRAME_URL; - iframeURL: string; -} - -export interface SetIframeContentWindowPayload { - type: CheckoutActions.SET_CHECKOUT_APP_IFRAME; - iframeContentWindow: Window; -} - -export interface SetPostMessageHandlerPayload { - type: CheckoutActions.SET_POST_MESSAGE_HANDLER; - postMessageHandler: PostMessageHandler; -} - export interface SetPassportPayload { type: CheckoutActions.SET_PASSPORT; - passport: Passport; -} - -export interface SetProviderNamePayload { - type: CheckoutActions.SET_WALLET_PROVIDER_NAME; - walletProviderName: WalletProviderName; -} - -export interface SetSendCloseEventPayload { - type: CheckoutActions.SET_SEND_CLOSE_EVENT; - sendCloseEvent: () => void; -} - -export interface SetInitialisedPayload { - type: CheckoutActions.SET_INITIALISED; - initialised: boolean; + passport: Passport | undefined; } // eslint-disable-next-line @typescript-eslint/naming-convention @@ -138,36 +82,6 @@ export const checkoutReducer: Reducer = ( ...state, passport: action.payload.passport, }; - case CheckoutActions.SET_IFRAME_URL: - return { - ...state, - iframeURL: action.payload.iframeURL, - }; - case CheckoutActions.SET_CHECKOUT_APP_IFRAME: - return { - ...state, - iframeContentWindow: action.payload.iframeContentWindow, - }; - case CheckoutActions.SET_POST_MESSAGE_HANDLER: - return { - ...state, - postMessageHandler: action.payload.postMessageHandler, - }; - case CheckoutActions.SET_WALLET_PROVIDER_NAME: - return { - ...state, - walletProviderName: action.payload.walletProviderName, - }; - case CheckoutActions.SET_SEND_CLOSE_EVENT: - return { - ...state, - sendCloseEvent: action.payload.sendCloseEvent, - }; - case CheckoutActions.SET_INITIALISED: - return { - ...state, - initialised: action.payload.initialised, - }; default: return state; } diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx index 56f4ade91d..b65d313b90 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx @@ -1,48 +1,37 @@ -import { PostMessageHandler } from '@imtbl/checkout-sdk'; +import { ReactNode, useMemo, useReducer } from 'react'; import { - Dispatch, ReactNode, useContext, useEffect, -} from 'react'; -import { - CheckoutAction, - CheckoutActions, CheckoutContext, - CheckoutState, + checkoutReducer, + initialCheckoutState, } from './CheckoutContext'; +import { useConnectLoaderState } from '../../../context/connect-loader-context/ConnectLoaderContext'; + +export const useCheckoutWidgetState = () => { + const [viewState, viewDispatch] = useReducer( + checkoutReducer, + initialCheckoutState, + ); + + return [viewState, viewDispatch] as const; +}; type CheckoutContextProviderProps = { - values: { - checkoutState: CheckoutState; - checkoutDispatch: Dispatch; - }; children: ReactNode; }; -export function CheckoutContextProvider({ - values, + +export function CheckoutWidgetContextProvicer({ children, }: CheckoutContextProviderProps) { - const { checkoutState, checkoutDispatch } = values; - const { checkout, iframeContentWindow, iframeURL } = checkoutState; - - useEffect(() => { - if (!iframeContentWindow || !checkout || !iframeURL) return; - - const postMessageHandlerInstance = new PostMessageHandler({ - targetOrigin: new URL(iframeURL).origin, - eventTarget: iframeContentWindow, - }); - - // TODO: remove logger after done with development - postMessageHandlerInstance.setLogger((...args: any[]) => { - console.log("🔔 PARENT – ", ...args); // eslint-disable-line - }); - - checkoutDispatch({ - payload: { - type: CheckoutActions.SET_POST_MESSAGE_HANDLER, - postMessageHandler: postMessageHandlerInstance, - }, - }); - }, [iframeContentWindow, checkout, iframeURL]); + const [{ checkout, provider }] = useConnectLoaderState(); + const [checkoutState, checkoutDispatch] = useCheckoutWidgetState(); + + const values = useMemo( + () => ({ + checkoutState: { ...checkoutState, checkout, provider }, + checkoutDispatch, + }), + [checkoutState, checkoutDispatch, checkout, provider], + ); return ( @@ -50,15 +39,3 @@ export function CheckoutContextProvider({ ); } - -export const useCheckoutContext = () => { - const context = useContext(CheckoutContext); - if (context === undefined) { - const error = new Error( - 'useCheckoutContext must be used within a ', - ); - throw error; - } - - return [context.checkoutState, context.checkoutDispatch] as const; -}; diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/.gitkeep b/packages/checkout/widgets-lib/src/widgets/checkout/functions/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/getCheckoutWidgetEvent.ts b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getCheckoutWidgetEvent.ts new file mode 100644 index 0000000000..a98b9e02cc --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getCheckoutWidgetEvent.ts @@ -0,0 +1,320 @@ +import { + AddFundsEventType, + BridgeEventType, + CheckoutEventType, + CheckoutFailureEventType, + CheckoutSuccessEventType, + CheckoutUserActionEventType, + ConnectEventType, + IMTBLWidgetEvents, + OnRampEventType, + ProviderEventType, + SaleEventType, + SwapEventType, + WalletEventType, +} from '@imtbl/checkout-sdk'; + +import { CheckoutEventDetail } from '../CheckoutWidgetEvents'; + +/** + * Map Connect Widget Events + */ +function mapConnectWidgetEvent( + event: CustomEvent<{ type: ConnectEventType; data: Record }>, +): CheckoutEventDetail { + const { type, data } = event.detail; + + switch (type) { + case ConnectEventType.SUCCESS: + return { + type: CheckoutEventType.SUCCESS, + data: { + type: CheckoutSuccessEventType.CONNECT_SUCCESS, + data, + }, + }; + + case ConnectEventType.FAILURE: + return { + type: CheckoutEventType.FAILURE, + data: { + type: CheckoutFailureEventType.CONNECT_FAILED, + data, + }, + }; + + case ConnectEventType.CLOSE_WIDGET: + return { + type: CheckoutEventType.CLOSE, + data: {}, + }; + + default: + throw new Error(`Unknown connect event type "${event.detail.type}"`); + } +} + +/** + * Map Wallet Widget Events + */ +function mapWalletWidgetEvent( + event: CustomEvent<{ type: WalletEventType; data: Record }>, +): CheckoutEventDetail { + const { type, data } = event.detail; + + switch (type) { + case WalletEventType.NETWORK_SWITCH: + return { + type: CheckoutEventType.USER_ACTION, + data: { + type: CheckoutUserActionEventType.NETWORK_SWITCH, + data, + }, + }; + case WalletEventType.DISCONNECT_WALLET: + return { + type: CheckoutEventType.DISCONNECTED, + data: {}, + }; + case WalletEventType.CLOSE_WIDGET: + return { + type: CheckoutEventType.CLOSE, + data: {}, + }; + default: + throw new Error(`Unknown wallet event type "${event.detail.type}"`); + } +} + +/** + * Map Swap Widget Events + */ +function mapSwapWidgetEvent( + event: CustomEvent<{ type: SwapEventType; data: Record }>, +): CheckoutEventDetail { + const { type, data } = event.detail; + + switch (type) { + case SwapEventType.SUCCESS: + return { + type: CheckoutEventType.SUCCESS, + data: { + type: CheckoutSuccessEventType.SWAP_SUCCESS, + data, + }, + }; + case SwapEventType.FAILURE: + return { + type: CheckoutEventType.FAILURE, + data: { + type: CheckoutFailureEventType.SWAP_FAILED, + data, + }, + }; + case SwapEventType.REJECTED: + return { + type: CheckoutEventType.FAILURE, + data: { + type: CheckoutFailureEventType.SWAP_REJECTED, + data, + }, + }; + case SwapEventType.CLOSE_WIDGET: + return { + type: CheckoutEventType.CLOSE, + data: {}, + }; + + default: + throw new Error(`Unknown swap event type "${event.detail.type}"`); + } +} + +/** + * Map Add Funds Widget Events + */ +function mapAddFundsWidgetEvent( + event: CustomEvent<{ type: AddFundsEventType; data: Record }>, +): CheckoutEventDetail { + const { type } = event.detail; + + switch (type) { + default: + throw new Error(`Unknown add funds event type "${event.detail.type}"`); + } +} + +/** + * Map Bridge Widget Events + */ +function mapBridgeWidgetEvent( + event: CustomEvent<{ type: BridgeEventType; data: Record }>, +): CheckoutEventDetail { + const { type, data } = event.detail; + + switch (type) { + case BridgeEventType.TRANSACTION_SENT: + return { + type: CheckoutEventType.SUCCESS, + data: { + type: CheckoutSuccessEventType.BRIDGE_SUCCESS, + data, + }, + }; + case BridgeEventType.CLAIM_WITHDRAWAL_SUCCESS: + return { + type: CheckoutEventType.SUCCESS, + data: { + type: CheckoutSuccessEventType.BRIDGE_CLAIM_WITHDRAWAL_SUCCESS, + data, + }, + }; + case BridgeEventType.CLAIM_WITHDRAWAL_FAILURE: + return { + type: CheckoutEventType.FAILURE, + data: { + type: CheckoutFailureEventType.BRIDGE_CLAIM_WITHDRAWAL_FAILED, + data, + }, + }; + case BridgeEventType.FAILURE: + return { + type: CheckoutEventType.FAILURE, + data: { + type: CheckoutFailureEventType.BRIDGE_FAILED, + data, + }, + }; + case BridgeEventType.CLOSE_WIDGET: + return { + type: CheckoutEventType.CLOSE, + data: {}, + }; + default: + throw new Error(`Unknown bridge event type "${event.detail.type}"`); + } +} + +/** + * Map Bridge Widget Events + */ +function mapOnrampWidgetEvent( + event: CustomEvent<{ type: OnRampEventType; data: Record }>, +): CheckoutEventDetail { + const { type, data } = event.detail; + + switch (type) { + case OnRampEventType.SUCCESS: + return { + type: CheckoutEventType.SUCCESS, + data: { + type: CheckoutSuccessEventType.ONRAMP_SUCCESS, + data, + }, + }; + case OnRampEventType.FAILURE: + return { + type: CheckoutEventType.FAILURE, + data: { + type: CheckoutFailureEventType.ONRAMP_FAILED, + data, + }, + }; + case OnRampEventType.CLOSE_WIDGET: + return { + type: CheckoutEventType.CLOSE, + data: {}, + }; + default: + throw new Error(`Unknown onRamp event type "${event.detail.type}"`); + } +} + +function mapSaleWidgetEvent( + event: CustomEvent<{ type: SaleEventType; data: Record }>, +): CheckoutEventDetail { + const { type, data } = event.detail; + + switch (type) { + case SaleEventType.SUCCESS: + return { + type: CheckoutEventType.SUCCESS, + data: { + type: CheckoutSuccessEventType.SALE_SUCCESS, + data, + }, + }; + case SaleEventType.FAILURE: + return { + type: CheckoutEventType.FAILURE, + data: { + type: CheckoutFailureEventType.SALE_FAILED, + data, + }, + }; + case SaleEventType.CLOSE_WIDGET: + return { + type: CheckoutEventType.CLOSE, + data: {}, + }; + case SaleEventType.TRANSACTION_SUCCESS: + return { + type: CheckoutEventType.SUCCESS, + data: { + type: CheckoutSuccessEventType.SALE_TRANSACTION_SUCCESS, + data, + }, + }; + case SaleEventType.PAYMENT_METHOD: + return { + type: CheckoutEventType.USER_ACTION, + data: { + type: CheckoutUserActionEventType.PAYMENT_METHOD_SELECTED, + data, + }, + }; + case SaleEventType.PAYMENT_TOKEN: + return { + type: CheckoutEventType.USER_ACTION, + data: { + type: CheckoutUserActionEventType.PAYMENT_TOKEN_SELECTED, + data, + }, + }; + default: + throw new Error(`Unknown sale event type "${event.detail.type}"`); + } +} + +/** + * Map widget events to checkout widget event detail + */ +export function getCheckoutWidgetEvent( + event: CustomEvent, +): CheckoutEventDetail { + if (event.detail.type === ProviderEventType.PROVIDER_UPDATED) { + return { + type: CheckoutEventType.PROVIDER_UPDATED, + data: event.detail.data, + }; + } + + switch (event.type) { + case IMTBLWidgetEvents.IMTBL_CONNECT_WIDGET_EVENT: + return mapConnectWidgetEvent(event); + case IMTBLWidgetEvents.IMTBL_WALLET_WIDGET_EVENT: + return mapWalletWidgetEvent(event); + case IMTBLWidgetEvents.IMTBL_SWAP_WIDGET_EVENT: + return mapSwapWidgetEvent(event); + case IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT: + return mapAddFundsWidgetEvent(event); + case IMTBLWidgetEvents.IMTBL_BRIDGE_WIDGET_EVENT: + return mapBridgeWidgetEvent(event); + case IMTBLWidgetEvents.IMTBL_ONRAMP_WIDGET_EVENT: + return mapOnrampWidgetEvent(event); + case IMTBLWidgetEvents.IMTBL_SALE_WIDGET_EVENT: + return mapSaleWidgetEvent(event); + default: + throw new Error(`Unknown widget event type "${event.type}"`); + } +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/getConnectLoaderParams.ts b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getConnectLoaderParams.ts new file mode 100644 index 0000000000..8a7de70803 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getConnectLoaderParams.ts @@ -0,0 +1,58 @@ +import { ChainId, Checkout, CheckoutFlowType } from '@imtbl/checkout-sdk'; +import { Web3Provider } from '@ethersproject/providers'; +import { ConnectLoaderParams } from '../../../components/ConnectLoader/ConnectLoader'; +import { getL1ChainId, getL2ChainId } from '../../../lib/networkUtils'; +import { View } from '../../../context/view-context/ViewContext'; + +/** + * Get the chain id for the checkout + */ +const getChainId = (checkout: Checkout) => (checkout.config.isProduction + ? ChainId.IMTBL_ZKEVM_MAINNET + : ChainId.IMTBL_ZKEVM_TESTNET); + +/** + * Get the connect loader params for the widget + */ +export function getConnectLoaderParams( + view: View, + checkout: Checkout, + web3Provider: Web3Provider | undefined, +): ConnectLoaderParams { + const { type, data } = view; + + switch (type) { + case CheckoutFlowType.WALLET: + return { + checkout, + web3Provider, + targetChainId: getChainId(checkout), + walletProviderName: data.params.walletProviderName, + allowedChains: [ + getL1ChainId(checkout.config), + getL2ChainId(checkout.config), + ], + }; + case CheckoutFlowType.ONRAMP: + case CheckoutFlowType.ADD_FUNDS: + return { + checkout, + web3Provider, + targetChainId: getChainId(checkout), + allowedChains: [ + getL1ChainId(checkout.config), + getL2ChainId(checkout.config), + ], + }; + case CheckoutFlowType.SALE: + case CheckoutFlowType.SWAP: + return { + checkout, + web3Provider, + targetChainId: getChainId(checkout), + allowedChains: [getL2ChainId(checkout.config)], + }; + default: + return {} as ConnectLoaderParams; + } +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewFromOrchestrationEventType.ts b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewFromOrchestrationEventType.ts new file mode 100644 index 0000000000..151b608dd8 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewFromOrchestrationEventType.ts @@ -0,0 +1,25 @@ +import { CheckoutFlowType, OrchestrationEventType } from '@imtbl/checkout-sdk'; + +/** + * Get view from orchestration event type + */ +export function getViewFromOrchestrationEventType( + type: OrchestrationEventType, +): CheckoutFlowType | null { + switch (type) { + case OrchestrationEventType.REQUEST_SWAP: + return CheckoutFlowType.SWAP; + case OrchestrationEventType.REQUEST_CONNECT: + return CheckoutFlowType.CONNECT; + case OrchestrationEventType.REQUEST_WALLET: + return CheckoutFlowType.WALLET; + case OrchestrationEventType.REQUEST_BRIDGE: + return CheckoutFlowType.BRIDGE; + case OrchestrationEventType.REQUEST_ONRAMP: + return CheckoutFlowType.ONRAMP; + case OrchestrationEventType.REQUEST_ADD_FUNDS: + return CheckoutFlowType.ADD_FUNDS; + default: + return null; + } +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewShouldConnect.ts b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewShouldConnect.ts new file mode 100644 index 0000000000..01ed572450 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/functions/getViewShouldConnect.ts @@ -0,0 +1,19 @@ +import { CheckoutFlowType } from '@imtbl/checkout-sdk'; + +/** + * List of views that require a connected wallet + */ +const connectFirstViewList = [ + CheckoutFlowType.SALE, + CheckoutFlowType.SWAP, + CheckoutFlowType.WALLET, + CheckoutFlowType.ONRAMP, + CheckoutFlowType.ADD_FUNDS, +] as unknown[]; + +/** + * Check if the given view requires a connected wallet + */ +export function getViewShouldConnect(view: unknown) { + return connectFirstViewList.includes(view); +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts b/packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts deleted file mode 100644 index 387450b1be..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* eslint-disable no-case-declarations */ -import { - CheckoutConfiguration, - CheckoutFlowType, - CheckoutWidgetConfiguration, - CheckoutWidgetParams, -} from '@imtbl/checkout-sdk'; - -import { Environment } from '@imtbl/config'; - -import { encodeObject } from '../utils/encode'; -import { CHECKOUT_APP_URL, ENV_DEVELOPMENT } from '../../../lib/constants'; - -/** - * Converts a record of parameters to a query string. - */ -const toQueryString = (params: Record): string => { - const sanitizedParams = Object.entries(params) - .filter(([, value]) => value !== undefined) - .map(([key, value]) => { - const safeValue = Array.isArray(value) || typeof value === 'object' - ? JSON.stringify(value) - : value; - - return [key, safeValue]; - }) - .map(([key, value]) => [key, value] as [string, string]); - - return new URLSearchParams(sanitizedParams).toString(); -}; - -/** - * Maps the flow configuration and params to the corresponding query parameters. - */ -const getIframeParams = async ( - params: CheckoutWidgetParams, - widgetConfig: CheckoutWidgetConfiguration, - checkoutConfig: CheckoutConfiguration, -): Promise => { - const { flow } = params; - const commonConfig = { - theme: widgetConfig.theme, - language: widgetConfig.language, - publishableKey: checkoutConfig.publishableKey, - sdkVersion: checkoutConfig.sdkVersion, - }; - - switch (flow) { - case CheckoutFlowType.CONNECT: - return toQueryString({ - ...commonConfig, - ...(widgetConfig.connect || {}), - chainId: params.targetChainId, - walletRdns: params.targetWalletRdns, - blocklistWalletRdns: params.blocklistWalletRdns, - }); - case CheckoutFlowType.WALLET: - return toQueryString({ - ...commonConfig, - ...(widgetConfig.wallet || {}), - // FIMXE: Add connection params - // chainId: - // walletRdns: - // blocklistWalletRdns: - - // FIXME: remove walletProviderName - walletProviderName: params.walletProviderName, - }); - case CheckoutFlowType.SWAP: - return toQueryString({ - ...commonConfig, - ...(widgetConfig.swap || {}), - // FIMXE: Add connection params - // chainId: - // walletRdns: - // blocklistWalletRdns: - - // FIXME: remove walletProviderName - walletProviderName: params.walletProviderName, - fromToken: params.fromTokenAddress, - fromAmount: params.amount, - toToken: params.toTokenAddress, - }); - case CheckoutFlowType.BRIDGE: - return toQueryString({ - ...commonConfig, - ...(widgetConfig.bridge || {}), - // FIMXE: Add bridge params - // fromChainId: - // toChainId: - // toToken: - // toAmount: - - // FIXME: remove walletProviderName - walletProviderName: params.walletProviderName, - fromToken: params.tokenAddress, - fromAmount: params.amount, - }); - case CheckoutFlowType.ONRAMP: - return toQueryString({ - ...commonConfig, - ...(widgetConfig.onRamp || {}), - // FIMXE: Add connection params - // chainId: - // walletRdns: - // blocklistWalletRdns: - - // FIXME: remove walletProviderName - walletProviderName: params.walletProviderName, - toToken: params.amount, - toAmount: params.tokenAddress, - }); - case CheckoutFlowType.SALE: - const items = await encodeObject(params.items || []); - - return toQueryString({ - ...commonConfig, - ...(widgetConfig.sale || {}), - // FIMXE: Add connection params - // chainId: - // walletRdns: - // blocklistWalletRdns: - - // FIXME: remove walletProviderName - walletProviderName: params.walletProviderName, - - // TODO: Get from hub - environmentId: params.environmentId, - collectionName: params.collectionName, - - items, - preferredCurrency: params.preferredCurrency, - excludePaymentTypes: params.excludePaymentTypes, - excludeFiatCurrencies: params.excludeFiatCurrencies, - }); - default: - return ''; - } -}; - -/** - * Returns the iframe URL for the Checkout App based on the environment. - */ -export const getIframeURL = async ( - params: CheckoutWidgetParams, - widgetConfig: CheckoutWidgetConfiguration, - checkoutConfig: CheckoutConfiguration, -): Promise => { - const { flow } = params; - const { publishableKey } = checkoutConfig; - - if (!publishableKey) return ''; - - let environment: Environment = checkoutConfig.environment || Environment.SANDBOX; - if (checkoutConfig.isDevelopment) { - environment = ENV_DEVELOPMENT; - } - - if (checkoutConfig.overrides?.environment) { - environment = checkoutConfig.overrides.environment; - } - - const baseURL = (checkoutConfig.overrides?.checkoutAppUrl as string) - ?? CHECKOUT_APP_URL[environment]; - const queryParams = await getIframeParams( - params, - widgetConfig, - checkoutConfig, - ); - - const iframeURL = `${baseURL}/${flow}?${queryParams}`; - - return iframeURL; -}; diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/isOrchestrationEvent.ts b/packages/checkout/widgets-lib/src/widgets/checkout/functions/isOrchestrationEvent.ts new file mode 100644 index 0000000000..4ca7b87402 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/functions/isOrchestrationEvent.ts @@ -0,0 +1,21 @@ +import { OrchestrationEventType } from '@imtbl/checkout-sdk'; + +/** Orchestration Events List */ +const orchestrationEvents = [ + OrchestrationEventType.REQUEST_CONNECT, + OrchestrationEventType.REQUEST_WALLET, + OrchestrationEventType.REQUEST_SWAP, + OrchestrationEventType.REQUEST_BRIDGE, + OrchestrationEventType.REQUEST_ONRAMP, + OrchestrationEventType.REQUEST_ADD_FUNDS, + OrchestrationEventType.REQUEST_GO_BACK, +]; + +/** + * Check if event is orchestration event + */ +export function isOrchestrationEvent( + event: CustomEvent<{ type: OrchestrationEventType }>, +): boolean { + return orchestrationEvents.includes(event.detail.type); +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useCheckoutEventsRelayer.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useCheckoutEventsRelayer.ts deleted file mode 100644 index 43bf561c10..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useCheckoutEventsRelayer.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable no-case-declarations */ -import { useContext, useEffect, useRef } from 'react'; -import { - CheckoutEventType, - PostMessageHandlerEventType, - CheckoutSuccessEventType, -} from '@imtbl/checkout-sdk'; -import { useCheckoutContext } from '../context/CheckoutContextProvider'; -import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext'; -import { CheckoutActions } from '../context/CheckoutContext'; -import { sendCheckoutEvent } from '../CheckoutWidgetEvents'; - -export function useCheckoutEventsRelayer() { - const [{ postMessageHandler, provider }, checkoutDispatch] = useCheckoutContext(); - const { - eventTargetState: { eventTarget }, - } = useContext(EventTargetContext); - const unsubscribePostMessageHandler = useRef<(() => void) | undefined>(); - - useEffect(() => { - if (!postMessageHandler) return undefined; - unsubscribePostMessageHandler.current?.(); - - unsubscribePostMessageHandler.current = postMessageHandler.subscribe( - ({ type, payload }) => { - if (type !== PostMessageHandlerEventType.WIDGET_EVENT) { - return; - } - - const event = { ...payload }; - - if (event.detail.type === CheckoutEventType.INITIALISED) { - checkoutDispatch({ - payload: { - type: CheckoutActions.SET_INITIALISED, - initialised: true, - }, - }); - } - - if (event.detail.type === CheckoutEventType.DISCONNECTED) { - checkoutDispatch({ - payload: { - type: CheckoutActions.SET_PROVIDER, - provider: undefined, - }, - }); - } - - if ( - event.detail.type === CheckoutEventType.SUCCESS - && event.detail.data.type === CheckoutSuccessEventType.CONNECT_SUCCESS - ) { - if (!provider) { - throw new Error( - 'Provider not found, unable to send checkout connect success event', - ); - } - - event.detail.data.data.provider = provider; - } - - sendCheckoutEvent(eventTarget, event.detail); - }, - ); - - return () => { - unsubscribePostMessageHandler.current?.(); - }; - }, [postMessageHandler, checkoutDispatch, eventTarget, provider]); -} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useEip6963Relayer.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useEip6963Relayer.ts deleted file mode 100644 index a973fa76ea..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useEip6963Relayer.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; -import { EIP6963ProviderDetail, PostMessageHandlerEventType } from '@imtbl/checkout-sdk'; -import { useCheckoutContext } from '../context/CheckoutContextProvider'; - -export function useEip6963Relayer() { - const [checkoutState] = useCheckoutContext(); - const { postMessageHandler } = checkoutState; - const unsubscribePostMessageHandler = useRef<() => void>(); - - const onAnnounce = useCallback((event: CustomEvent) => { - postMessageHandler?.send(PostMessageHandlerEventType.EIP_6963_EVENT, { - message: 'eip6963:announceProvider', - info: event.detail.info, - isPassport: (event.detail.provider as unknown as any)?.isPassport, - isMetamask: (event.detail.provider as unknown as any)?.isMetamask, - }); - }, [postMessageHandler]); - - useEffect( - () => { - if (!postMessageHandler) return () => { }; - - window.addEventListener('eip6963:announceProvider', onAnnounce as any); - - return () => window.removeEventListener('eip6963:announceProvider', onAnnounce as any); - }, - [postMessageHandler, onAnnounce], - ); - - const onRequest = useCallback((payload: any) => { - if (payload.message !== 'eip6963:requestProvider') return; - - window.dispatchEvent(new CustomEvent('eip6963:requestProvider')); - }, [postMessageHandler]); - - useEffect(() => { - if (!postMessageHandler) return; - unsubscribePostMessageHandler.current?.(); - unsubscribePostMessageHandler.current = postMessageHandler.subscribe((message) => { - if (message.type === PostMessageHandlerEventType.EIP_6963_EVENT) { - onRequest(message.payload); - } - }); - }, [postMessageHandler, onRequest]); -} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useProviderRelay.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useProviderRelay.ts deleted file mode 100644 index d870413da2..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useProviderRelay.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; -import { - EIP6963ProviderInfo, - PostMessageData, - PostMessageHandlerEventType, -} from '@imtbl/checkout-sdk'; -import { Web3Provider } from '@ethersproject/providers'; -import { useCheckoutContext } from '../context/CheckoutContextProvider'; -import { CheckoutActions } from '../context/CheckoutContext'; - -// TODO these types should be in sync with Checkout App -type MessageId = number | string | null; - -enum ConnectMethods { - REQUEST_ACCOUNTS = 'eth_requestAccounts', - REQUEST_PERMISSIONS = 'wallet_requestPermissions', -} - -interface JsonRpcRequestMessage { - type: 'dapp'; - jsonrpc: '2.0'; - // Optional in the request. - id?: MessageId; - method: string; - params?: TParams; -} - -type ProviderRelayPayload = { - jsonRpcRequestMessage: JsonRpcRequestMessage; - eip6963Info: EIP6963ProviderInfo; -}; - -export function useProviderRelay() { - const [{ checkout, postMessageHandler, provider }, checkoutDispatch] = useCheckoutContext(); - - const unsubscribePostMessageHandler = useRef<() => void>(); - - /** - * Execute a request using the provider - * and relay the response back using the postMessageHandler - */ - const execute = useCallback( - async (payload: ProviderRelayPayload, executeProvider: Web3Provider) => { - if (!postMessageHandler) { - throw new Error( - 'Provider can execute request because PostMessageHandler is not initialized', - ); - } - - if (!executeProvider?.provider.request) { - throw new Error("Provider only supports 'request' method"); - } - - try { - const { id, params, method } = payload.jsonRpcRequestMessage; - - // Execute the request - const result = await executeProvider.provider.request({ method, params }); - const formattedResponse = { id, result, jsonrpc: '2.0' }; - - // Send the response using the postMessageHandler - postMessageHandler.send(PostMessageHandlerEventType.PROVIDER_RELAY, { - response: formattedResponse, - eip6963Info: payload.eip6963Info, - }); - } catch (error: any) { - throw new Error(error); - } - }, - [postMessageHandler], - ); - - /** - * Handle incoming provider relay messages - */ - const onJsonRpcRequestMessage = useCallback( - async ({ type, payload }: PostMessageData) => { - if (!postMessageHandler || !checkout) return; - if (type !== PostMessageHandlerEventType.PROVIDER_RELAY) return; - - const providerRelayPayload = payload as ProviderRelayPayload; - - const injectedProviders = checkout.getInjectedProviders(); - const targetProvider = injectedProviders.find( - (p) => p.info.uuid === providerRelayPayload?.eip6963Info?.uuid, - ); - - if (!targetProvider && providerRelayPayload.eip6963Info !== undefined) { - // eslint-disable-next-line no-console - console.error( - 'PARENT - requested provider not found', - providerRelayPayload?.eip6963Info, - injectedProviders, - ); - return; - } - - let currentProvider = provider; - // If provider is not defined, create a provider - if (!currentProvider && targetProvider) { - currentProvider = new Web3Provider(targetProvider.provider); - } - - if (!currentProvider) { - throw new Error('Provider is not defined'); - } - - try { - await execute(providerRelayPayload, currentProvider); - if (providerRelayPayload.jsonRpcRequestMessage.method === ConnectMethods.REQUEST_ACCOUNTS - || providerRelayPayload.jsonRpcRequestMessage.method === ConnectMethods.REQUEST_PERMISSIONS) { - checkoutDispatch({ - payload: { - type: CheckoutActions.SET_PROVIDER, - provider: currentProvider, - }, - }); - } - } catch (error: any) { - // Send the error using the postMessageHandler - postMessageHandler.send(PostMessageHandlerEventType.PROVIDER_RELAY, { - response: { id: providerRelayPayload.jsonRpcRequestMessage.id, error: error.message, jsonrpc: '2.0' }, - eip6963Info: providerRelayPayload.eip6963Info, - }); - } - }, - [provider, postMessageHandler, checkout, execute], - ); - - /** - * Subscribe to provider relay messages - */ - useEffect(() => { - if (!postMessageHandler) return; - unsubscribePostMessageHandler.current?.(); - unsubscribePostMessageHandler.current = postMessageHandler?.subscribe(onJsonRpcRequestMessage); - }, [postMessageHandler, onJsonRpcRequestMessage]); -} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetEvents.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetEvents.ts new file mode 100644 index 0000000000..7669f33475 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetEvents.ts @@ -0,0 +1,95 @@ +import { useEffect, useMemo } from 'react'; +import { + IMTBLWidgetEvents, + OrchestrationEvent, + OrchestrationEventType, +} from '@imtbl/checkout-sdk'; +import { getCheckoutWidgetEvent } from '../functions/getCheckoutWidgetEvent'; +import { sendCheckoutEvent } from '../CheckoutWidgetEvents'; +import { + useViewState, + ViewActions, +} from '../../../context/view-context/ViewContext'; +import { getViewFromOrchestrationEventType } from '../functions/getViewFromOrchestrationEventType'; +import { isOrchestrationEvent } from '../functions/isOrchestrationEvent'; + +/** Widget Events List */ +const widgetEvents = [ + IMTBLWidgetEvents.IMTBL_CONNECT_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_WALLET_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_SWAP_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_BRIDGE_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_ONRAMP_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_SALE_WIDGET_EVENT, +]; + +/** + * Subscribe and Handle widget events + */ +export function useWidgetEvents( + eventTarget: Window | EventTarget, + viewState: ReturnType, +) { + const [{ history }, viewDispatch] = viewState; + + /** + * Change view as per orchestration event requests + */ + const handleOrchestrationEvent = ( + event: CustomEvent>, + ) => { + const { type, data } = event.detail; + + if (type === OrchestrationEventType.REQUEST_GO_BACK) { + viewDispatch({ + payload: { + type: ViewActions.UPDATE_VIEW, + view: history?.[0], + }, + }); + + return; + } + + const flow = getViewFromOrchestrationEventType(type); + if (!flow) return; + + viewDispatch({ + payload: { + type: ViewActions.UPDATE_VIEW, + view: { + type: flow as any, + data: { params: data, config: {}, showBackButton: true }, + }, + }, + }); + }; + + /** + * Proxy widget events to checkout widget events + */ + const handleWidgetEvent = useMemo(() => { + if (!eventTarget) return null; + + return (event: Event) => { + const customEvent = event as CustomEvent; + + if (isOrchestrationEvent(customEvent)) { + handleOrchestrationEvent(customEvent); + return; + } + + const eventDetail = getCheckoutWidgetEvent(customEvent); + sendCheckoutEvent(eventTarget, eventDetail); + }; + }, [eventTarget]); + + useEffect(() => { + if (!handleWidgetEvent) return () => {}; + + widgetEvents.map((event) => window.addEventListener(event, handleWidgetEvent)); + return () => { + widgetEvents.map((event) => window.removeEventListener(event, handleWidgetEvent)); + }; + }, [handleWidgetEvent]); +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetProviderEventRelayer.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetProviderEventRelayer.ts deleted file mode 100644 index cd3690e93c..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useWidgetProviderEventRelayer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { PostMessageHandlerEventType } from '@imtbl/checkout-sdk'; -import { useCallback, useEffect } from 'react'; -import { useCheckoutContext } from '../context/CheckoutContextProvider'; - -export function useWidgetProviderEventRelayer() { - const [checkoutState] = useCheckoutContext(); - const { postMessageHandler } = checkoutState; - - const onWidgetProviderEvent = useCallback(() => { - postMessageHandler?.send(PostMessageHandlerEventType.WIDGET_PROVIDER_EVENT, {}); - }, [postMessageHandler]); - - useEffect( - () => { - if (!postMessageHandler) return () => { }; - - window.addEventListener(PostMessageHandlerEventType.WIDGET_PROVIDER_EVENT, onWidgetProviderEvent); - - return () => window.removeEventListener(PostMessageHandlerEventType.WIDGET_PROVIDER_EVENT, onWidgetProviderEvent); - }, - [postMessageHandler, onWidgetProviderEvent], - ); -} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/utils/.gitkeep b/packages/checkout/widgets-lib/src/widgets/checkout/utils/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts b/packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts deleted file mode 100644 index a796a1e7fb..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * The timeout in milliseconds for the iframe to be initialized. - */ -export const IFRAME_INIT_TIMEOUT_MS = 10000; - -/** - * The permissions to allow on the iframe. - */ -export const IFRAME_ALLOW_PERMISSIONS = ` - accelerometer; - camera; - microphone; - geolocation; - gyroscope; - fullscreen; - autoplay; - encrypted-media; - picture-in-picture; - clipboard-write; - clipboard-read; -` - .trim() - .replace(/\n/g, ''); diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/utils/encode.ts b/packages/checkout/widgets-lib/src/widgets/checkout/utils/encode.ts deleted file mode 100644 index 82404a60e5..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/utils/encode.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Encodes a JSON object using base64 encoding. - */ -export const encodeObject = async (value: Object): Promise => { - try { - const str = JSON.stringify(value); - const base64String = btoa(str); - return encodeURIComponent(base64String); - } catch (error) { - throw new Error(`Compression failed: ${(error as Error).message}`); - } -}; - -/** - * Decodes a string encoded using encodeObject. - */ -export const decodeObject = async ( - encodedValue: string, -): Promise => { - try { - const decodedString = atob(decodeURIComponent(encodedValue)); - return JSON.parse(decodedString); - } catch (error) { - throw new Error(`Decompression failed: ${(error as Error).message}`); - } -}; diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx deleted file mode 100644 index 71e1a6590d..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Box } from '@biom3/react'; -import { - CheckoutEventType, -} from '@imtbl/checkout-sdk'; -import { - useContext, - useEffect, - useRef, useState, -} from 'react'; -import { useTranslation } from 'react-i18next'; -import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext'; -import { ErrorView } from '../../../views/error/ErrorView'; -import { LoadingView } from '../../../views/loading/LoadingView'; -import { sendCheckoutEvent } from '../CheckoutWidgetEvents'; -import { CheckoutActions } from '../context/CheckoutContext'; -import { useCheckoutContext } from '../context/CheckoutContextProvider'; -import { useCheckoutEventsRelayer } from '../hooks/useCheckoutEventsRelayer'; -import { useEip6963Relayer } from '../hooks/useEip6963Relayer'; -import { useProviderRelay } from '../hooks/useProviderRelay'; -import { - IFRAME_ALLOW_PERMISSIONS, - IFRAME_INIT_TIMEOUT_MS, -} from '../utils/config'; -import { useWidgetProviderEventRelayer } from '../hooks/useWidgetProviderEventRelayer'; - -export interface LoadingHandoverProps { - text: string; - duration?: number; - animationUrl: string; - inputValue?: number; -} -export function CheckoutAppIframe() { - const { t } = useTranslation(); - const iframeRef = useRef(null); - const [loadingError, setLoadingError] = useState(false); - const [ - { - iframeURL, iframeContentWindow, initialised, - }, - checkoutDispatch, - ] = useCheckoutContext(); - const timeoutRef = useRef(null); - const initialisedRef = useRef(initialised); - useCheckoutEventsRelayer(); - useEip6963Relayer(); - useProviderRelay(); - useWidgetProviderEventRelayer(); - - const loading = !iframeURL || !iframeContentWindow || !initialised; - - const { - eventTargetState: { eventTarget }, - } = useContext(EventTargetContext); - - useEffect(() => { - initialisedRef.current = initialised; - }, [initialised]); - - useEffect(() => { - timeoutRef.current = setTimeout(() => { - if (initialisedRef.current) return; - - setLoadingError(true); - clearTimeout(timeoutRef.current!); - }, IFRAME_INIT_TIMEOUT_MS); - - return () => { - clearTimeout(timeoutRef.current!); - }; - }, []); - - const onIframeLoad = () => { - const iframe = iframeRef.current; - if (!iframe?.contentWindow) { - return; - } - - checkoutDispatch({ - payload: { - type: CheckoutActions.SET_CHECKOUT_APP_IFRAME, - iframeContentWindow: iframe.contentWindow, - }, - }); - }; - - if (loadingError) { - return ( - { - sendCheckoutEvent(eventTarget, { - type: CheckoutEventType.CLOSE, - data: {}, - }); - }} - onActionClick={() => { - setLoadingError(false); - iframeContentWindow?.location.reload(); - }} - actionText={t('views.ERROR_VIEW.actionText')} - /> - ); - } - - return ( - <> - {loading && } - {iframeURL && ( - - )} - sx={{ - w: '100%', - h: '100%', - border: 'none', - boxShadow: 'none', - }} - /> - )} - - ); -} diff --git a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx index 2fcd6e12f0..ea5058db35 100644 --- a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx +++ b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx @@ -28,6 +28,7 @@ import { CheckoutFlowType, WalletProviderName, Widget, + SalePaymentTypes, } from "@imtbl/checkout-sdk"; import { Passport } from "@imtbl/passport"; import { WidgetsFactory } from "@imtbl/checkout-widgets"; @@ -37,6 +38,7 @@ import { useAsyncMemo, usePrevState } from "../../../hooks"; import { Message } from "./components/messages"; import { Legend } from "./components/legend"; import { itemsMock } from "./items.mock"; +import { ChainId } from "@imtbl/checkout-sdk"; // const ENVIRONMENT_DEV = "development" as Environment; @@ -130,6 +132,7 @@ const flows: Array = [ CheckoutFlowType.SWAP, CheckoutFlowType.BRIDGE, CheckoutFlowType.SALE, + CheckoutFlowType.ADD_FUNDS, ]; function CheckoutUI() { @@ -165,14 +168,35 @@ function CheckoutUI() { const [flowParams, setFlowParams] = useState< Partial> >({ - sale: { + CONNECT: { + flow: CheckoutFlowType.CONNECT, + // blocklistWalletRdns: ["io.metamask"], + // targetChainId: ChainId.SEPOLIA, + // targetWalletRdns: "io.metamask", + theme: WidgetTheme.LIGHT, + }, + SALE: { flow: CheckoutFlowType.SALE, items: itemsMock, - environmentId: "249d9b0b-ee16-4dd5-91ee-96bece3b0473", + environmentId: "4dfc4bec-1867-49aa-ad35-d8a13b206c94", collectionName: "Pixel Aussie Farm", - // excludePaymentTypes: [checkout.SalePaymentTypes.CREDIT], + excludePaymentTypes: [SalePaymentTypes.CREDIT], // preferredCurrency: 'USDC', }, + SWAP: { + flow: CheckoutFlowType.SWAP, + amount: "10", + fromTokenAddress: "native", + toTokenAddress: "0x3B2d8A1931736Fc321C24864BceEe981B11c3c57", + }, + WALLET: { + flow: CheckoutFlowType.WALLET, + }, + ADD_FUNDS: { + flow: CheckoutFlowType.ADD_FUNDS, + toAmount: "100", + toTokenAddress: "native", + }, }); // set a state to keep widget event results @@ -240,18 +264,18 @@ function CheckoutUI() { config: { theme, language, - // swap: {}, - // bridge: {}, - // connect: {}, - // onRamp: {}, - // sale: { - // hideExcludedPaymentTypes: false, - // waitFulfillmentSettlements: false, - // }, - // wallet: { - // showDisconnectButton: true, - // showNetworkMenu: true, - // } + // SWAP: {}, + // BRIDGE: {}, + // CONNECT: {}, + // ONRAMP: {}, + SALE: { + hideExcludedPaymentTypes: true, + waitFulfillmentSettlements: false, + }, + WALLET: { + showDisconnectButton: true, + showNetworkMenu: true, + }, }, }); }, [widgetsFactory, web3Provider, renderAfterConnect]); @@ -430,7 +454,7 @@ function CheckoutUI() { name: "Kangaroo", image: "https://iguanas.mystagingwebsite.com/wp-content/uploads/2024/05/character-image-10-1.png", - description: "Pixel Art Kangaroo" + description: "Pixel Art Kangaroo", }, ], environmentId: "249d9b0b-ee16-4dd5-91ee-96bece3b0473", @@ -669,12 +693,6 @@ function CheckoutUI() { )} - - Events - {eventResults.map((result) => ( - - ))} - ); }