From 549a631a18338e1beac51af4fc7709b88654e789 Mon Sep 17 00:00:00 2001 From: Ji Young Lee <641712+jiyounglee@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:18:12 +1000 Subject: [PATCH] Feat: [NO CHANGELOG][Add Funds Widget] Create Add Funds Widget by surfacing Top Up View (#2089) Co-authored-by: Mimi Tran --- .../definitions/configurations/addFunds.ts | 8 ++ .../widgets/definitions/events/addFunds.ts | 11 ++ .../src/widgets/definitions/events/index.ts | 1 + .../definitions/events/orchestration.ts | 7 ++ .../src/widgets/definitions/events/widgets.ts | 1 + .../definitions/parameters/addFunds.ts | 21 ++++ .../sdk/src/widgets/definitions/types.ts | 18 +++ .../SegmentAnalyticsProvider.ts | 1 + .../view-context/AddFundsViewContextTypes.ts | 11 ++ packages/checkout/widgets-lib/src/factory.ts | 7 ++ .../src/widgets/add-funds/AddFundsRoot.tsx | 100 ++++++++++++++++ .../src/widgets/add-funds/AddFundsWidget.tsx | 60 ++++++++++ .../widgets/add-funds/AddFundsWidgetEvents.ts | 38 ++++++ .../checkout/widgets-sample-app/src/App.tsx | 4 + .../src/components/ui/add-funds/addFunds.tsx | 108 ++++++++++++++++++ .../checkout/widgets-sample-app/src/index.tsx | 5 + 16 files changed, 401 insertions(+) create mode 100644 packages/checkout/sdk/src/widgets/definitions/configurations/addFunds.ts create mode 100644 packages/checkout/sdk/src/widgets/definitions/events/addFunds.ts create mode 100644 packages/checkout/sdk/src/widgets/definitions/parameters/addFunds.ts create mode 100644 packages/checkout/widgets-lib/src/context/view-context/AddFundsViewContextTypes.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsRoot.tsx create mode 100644 packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidget.tsx create mode 100644 packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidgetEvents.ts create mode 100644 packages/checkout/widgets-sample-app/src/components/ui/add-funds/addFunds.tsx diff --git a/packages/checkout/sdk/src/widgets/definitions/configurations/addFunds.ts b/packages/checkout/sdk/src/widgets/definitions/configurations/addFunds.ts new file mode 100644 index 0000000000..7e2aeff97a --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/configurations/addFunds.ts @@ -0,0 +1,8 @@ +import { WidgetConfiguration } from './widget'; + +/** + * Add Funds Widget Configuration represents the configuration options for the Add Funds Widget. + */ +export type AddFundsWidgetConfiguration = { +// TODO : [ADD_FUNDS] - What are the configuration needed? +} & WidgetConfiguration; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/addFunds.ts b/packages/checkout/sdk/src/widgets/definitions/events/addFunds.ts new file mode 100644 index 0000000000..39a624628b --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/events/addFunds.ts @@ -0,0 +1,11 @@ +/** + * Enum of possible Add Funds Widget event types. + */ +export enum AddFundsEventType { + CLOSE_WIDGET = 'close-widget', + LANGUAGE_CHANGED = 'language-changed', + REQUEST_BRIDGE = 'request-bridge', + REQUEST_ONRAMP = 'request-onramp', + REQUEST_SWAP = 'request-swap', + GO_BACK = 'go-back', +} diff --git a/packages/checkout/sdk/src/widgets/definitions/events/index.ts b/packages/checkout/sdk/src/widgets/definitions/events/index.ts index 60579cdead..fecd91d320 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/index.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/index.ts @@ -7,3 +7,4 @@ export * from './bridge'; export * from './orchestration'; export * from './onramp'; export * from './checkout'; +export * from './addFunds'; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts index 41c48ca9e6..295e04e5de 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts @@ -7,6 +7,7 @@ export enum OrchestrationEventType { REQUEST_SWAP = 'request-swap', REQUEST_BRIDGE = 'request-bridge', REQUEST_ONRAMP = 'request-onramp', + REQUEST_ADD_FUNDS = 'request-add-funds', } /** @@ -66,6 +67,12 @@ export type RequestOnrampEvent = { amount: string; }; +/** + * Represents the add funds event object when the add funds widget is requested. + */ +export type RequestAddFundsEvent = { +}; + /* * Type representing the orchestration events. */ diff --git a/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts b/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts index e790a72a00..531a5e0d3d 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts @@ -13,6 +13,7 @@ export enum IMTBLWidgetEvents { IMTBL_ONRAMP_WIDGET_EVENT = 'imtbl-onramp-widget', IMTBL_SALE_WIDGET_EVENT = 'imtbl-sale-widget', IMTBL_CHECKOUT_WIDGET_EVENT = 'imtbl-checkout-widget', + IMTBL_ADD_FUNDS_WIDGET_EVENT = 'imtbl-add-funds-widget', } /** diff --git a/packages/checkout/sdk/src/widgets/definitions/parameters/addFunds.ts b/packages/checkout/sdk/src/widgets/definitions/parameters/addFunds.ts new file mode 100644 index 0000000000..d3f4e83bf4 --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/parameters/addFunds.ts @@ -0,0 +1,21 @@ +import { WidgetLanguage } from '../configurations'; + +export type AddFundsWidgetParams = { + /** The language to use for the Add Funds widget */ + language?: WidgetLanguage; + + /** Configure to show on-ramp option */ + showOnrampOption?: boolean; + + /** Configure to show swap option */ + showSwapOption?: boolean; + + /** Configure to show on bridge option */ + showBridgeOption?: boolean; + + /** Token address of the fund to be added */ + tokenAddress?: string; + + /** Amount of the fund to be added */ + amount?: string; +}; diff --git a/packages/checkout/sdk/src/widgets/definitions/types.ts b/packages/checkout/sdk/src/widgets/definitions/types.ts index 1084f6a3dd..c9e9a12d0e 100644 --- a/packages/checkout/sdk/src/widgets/definitions/types.ts +++ b/packages/checkout/sdk/src/widgets/definitions/types.ts @@ -39,6 +39,7 @@ import { CheckoutSuccessEvent, CheckoutFailureEvent, CheckoutUserActionEvent, + RequestAddFundsEvent, } from './events'; import { BridgeWidgetParams, @@ -59,6 +60,9 @@ import { CheckoutWidgetConfiguration, } 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. @@ -71,6 +75,7 @@ export enum WidgetType { ONRAMP = 'onramp', SALE = 'sale', CHECKOUT = 'checkout', + ADD_FUNDS = 'addFunds', } /** @@ -89,6 +94,7 @@ export type WidgetConfigurations = { [WidgetType.ONRAMP]: OnrampWidgetConfiguration; [WidgetType.SALE]: SaleWidgetConfiguration; [WidgetType.CHECKOUT]: CheckoutWidgetConfiguration; + [WidgetType.ADD_FUNDS]: AddFundsWidgetConfiguration; }; // Mapping each widget type to their parameters @@ -100,6 +106,7 @@ export type WidgetParameters = { [WidgetType.ONRAMP]: OnRampWidgetParams; [WidgetType.SALE]: SaleWidgetParams; [WidgetType.CHECKOUT]: CheckoutWidgetParams; + [WidgetType.ADD_FUNDS]: AddFundsWidgetParams; }; /** @@ -113,6 +120,7 @@ export type WidgetEventTypes = { [WidgetType.ONRAMP]: OnRampEventType | OrchestrationEventType; [WidgetType.SALE]: SaleEventType | OrchestrationEventType; [WidgetType.CHECKOUT]: CheckoutEventType | OrchestrationEventType; + [WidgetType.ADD_FUNDS]: AddFundsEventType | OrchestrationEventType; }; // Mapping of Orchestration events to their payloads @@ -122,6 +130,7 @@ type OrchestrationMapping = { [OrchestrationEventType.REQUEST_SWAP]: RequestSwapEvent; [OrchestrationEventType.REQUEST_BRIDGE]: RequestBridgeEvent; [OrchestrationEventType.REQUEST_ONRAMP]: RequestOnrampEvent; + [OrchestrationEventType.REQUEST_ADD_FUNDS]: RequestAddFundsEvent; }; type ProviderEventMapping = { @@ -196,6 +205,15 @@ export type WidgetEventData = { [CheckoutEventType.DISCONNECTED]: {}; [CheckoutEventType.USER_ACTION]: CheckoutUserActionEvent; }; + + [WidgetType.ADD_FUNDS]: { + [AddFundsEventType.CLOSE_WIDGET]: {}; + [AddFundsEventType.GO_BACK]: {}; + [AddFundsEventType.REQUEST_BRIDGE]: {}; + [AddFundsEventType.REQUEST_SWAP]: {}; + [AddFundsEventType.REQUEST_ONRAMP]: {}; + } & OrchestrationMapping & + ProviderEventMapping; }; /** diff --git a/packages/checkout/widgets-lib/src/context/analytics-provider/SegmentAnalyticsProvider.ts b/packages/checkout/widgets-lib/src/context/analytics-provider/SegmentAnalyticsProvider.ts index 9e802f3249..2edbc88583 100644 --- a/packages/checkout/widgets-lib/src/context/analytics-provider/SegmentAnalyticsProvider.ts +++ b/packages/checkout/widgets-lib/src/context/analytics-provider/SegmentAnalyticsProvider.ts @@ -11,6 +11,7 @@ export enum UserJourney { SWAP = 'Swap', BRIDGE = 'Bridge', SALE = 'PrimarySale', + ADD_FUNDS = 'AddFunds', } export type AnalyticsControlTypes = diff --git a/packages/checkout/widgets-lib/src/context/view-context/AddFundsViewContextTypes.ts b/packages/checkout/widgets-lib/src/context/view-context/AddFundsViewContextTypes.ts new file mode 100644 index 0000000000..3f42e7534c --- /dev/null +++ b/packages/checkout/widgets-lib/src/context/view-context/AddFundsViewContextTypes.ts @@ -0,0 +1,11 @@ +import { ViewType } from './ViewType'; + +export enum AddFundsWidgetViews { + ADD_FUNDS = 'ADD_FUNDS', +} + +export type AddFundsWidgetView = AddFundsView; + +interface AddFundsView extends ViewType { + type: AddFundsWidgetViews.ADD_FUNDS; +} diff --git a/packages/checkout/widgets-lib/src/factory.ts b/packages/checkout/widgets-lib/src/factory.ts index 9bb2cd36c8..c7eeb8ab80 100644 --- a/packages/checkout/widgets-lib/src/factory.ts +++ b/packages/checkout/widgets-lib/src/factory.ts @@ -23,6 +23,7 @@ import { } from './lib'; import './i18n'; import { CheckoutWidgetRoot } from './widgets/checkout/CheckoutWidgetRoot'; +import { AddFunds } from './widgets/add-funds/AddFundsRoot'; export class WidgetsFactory implements IWidgetsFactory { private sdk: Checkout; @@ -103,6 +104,12 @@ export class WidgetsFactory implements IWidgetsFactory { provider, }) as Widget as Widget; } + case WidgetType.ADD_FUNDS: { + return new AddFunds(this.sdk, { + config: { ...this.widgetConfig, ...(config) }, + provider, + }) as Widget as Widget; + } default: throw new Error('widget type not supported'); } diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsRoot.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsRoot.tsx new file mode 100644 index 0000000000..aeee4dbbad --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsRoot.tsx @@ -0,0 +1,100 @@ +import { + ChainId, + IMTBLWidgetEvents, + WidgetConfiguration, + WidgetProperties, + WidgetTheme, + WidgetType, +} from '@imtbl/checkout-sdk'; +import React, { Suspense } from 'react'; +import { AddFundsWidgetParams } from '@imtbl/checkout-sdk/dist/widgets/definitions/parameters/addFunds'; +import { Base } from '../BaseWidgetRoot'; +import { CustomAnalyticsProvider } from '../../context/analytics-provider/CustomAnalyticsProvider'; +import { HandoverProvider } from '../../context/handover-context/HandoverProvider'; +import i18n from '../../i18n'; +import { LoadingView } from '../../views/loading/LoadingView'; +import { ThemeProvider } from '../../components/ThemeProvider/ThemeProvider'; +import { + ConnectLoader, + ConnectLoaderParams, +} from '../../components/ConnectLoader/ConnectLoader'; +import { getL1ChainId, getL2ChainId } from '../../lib'; +import { sendAddFundsCloseEvent } from './AddFundsWidgetEvents'; + +const AddFundsWidget = React.lazy(() => import('./AddFundsWidget')); + +export class AddFunds extends Base { + protected eventTopic: IMTBLWidgetEvents = IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT; + + protected getValidatedProperties({ + config, + }: WidgetProperties): WidgetProperties { + let validatedConfig: WidgetConfiguration | undefined; + + if (config) { + validatedConfig = config; + if (config.theme === WidgetTheme.LIGHT) validatedConfig.theme = WidgetTheme.LIGHT; + else validatedConfig.theme = WidgetTheme.DARK; + } + + return { + config: validatedConfig, + }; + } + + protected getValidatedParameters( + params: AddFundsWidgetParams, + ): AddFundsWidgetParams { + const validatedParams = params; + return validatedParams; + } + + protected render() { + if (!this.reactRoot) return; + + const { t } = i18n; + const connectLoaderParams: ConnectLoaderParams = { + targetChainId: this.checkout.config.isProduction + ? ChainId.IMTBL_ZKEVM_MAINNET + : ChainId.IMTBL_ZKEVM_TESTNET, + web3Provider: this.web3Provider, + checkout: this.checkout, + allowedChains: [ + getL1ChainId(this.checkout.config), + getL2ChainId(this.checkout.config), + ], + }; + + this.reactRoot.render( + + + + + sendAddFundsCloseEvent(window)} + > + + } + > + + + + + + + , + ); + } +} diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidget.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidget.tsx new file mode 100644 index 0000000000..9827f30cf7 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidget.tsx @@ -0,0 +1,60 @@ +import { AddFundsWidgetParams } from '@imtbl/checkout-sdk/dist/widgets/definitions/parameters/addFunds'; +import { Checkout, IMTBLWidgetEvents } from '@imtbl/checkout-sdk'; +import { Web3Provider } from '@ethersproject/providers'; +import { useContext, useMemo, useReducer } from 'react'; +import { UserJourney } from '../../context/analytics-provider/SegmentAnalyticsProvider'; +import { TopUpView } from '../../views/top-up/TopUpView'; +import { + sendAddFundsCloseEvent, + sendAddFundsGoBackEvent, +} from './AddFundsWidgetEvents'; +import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; +import { + ViewContext, + initialViewState, + viewReducer, +} from '../../context/view-context/ViewContext'; + +export type AddFundsWidgetInputs = AddFundsWidgetParams & { + checkout: Checkout; + web3Provider?: Web3Provider; +}; + +export default function AddFundsWidget({ + checkout, + web3Provider, + showOnrampOption = true, + showSwapOption = true, + showBridgeOption = true, + tokenAddress, + amount, +}: AddFundsWidgetInputs) { + const [viewState, viewDispatch] = useReducer(viewReducer, initialViewState); + + const viewReducerValues = useMemo( + () => ({ viewState, viewDispatch }), + [viewState, viewReducer], + ); + + const { + eventTargetState: { eventTarget }, + } = useContext(EventTargetContext); + + return ( + + sendAddFundsCloseEvent(eventTarget)} + onBackButtonClick={() => sendAddFundsGoBackEvent(eventTarget)} + /> + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidgetEvents.ts b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidgetEvents.ts new file mode 100644 index 0000000000..6f03661e54 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidgetEvents.ts @@ -0,0 +1,38 @@ +import { + WidgetEvent, + WidgetType, + AddFundsEventType, + IMTBLWidgetEvents, +} from '@imtbl/checkout-sdk'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function sendAddFundsCloseEvent(eventTarget: Window | EventTarget) { + const closeWidgetEvent = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT, { + detail: { + type: AddFundsEventType.CLOSE_WIDGET, + data: {}, + }, + }); + // TODO: please remove or if necessary keep the eslint ignore + // eslint-disable-next-line no-console + console.log('close widget event:', closeWidgetEvent); + if (eventTarget !== undefined) eventTarget.dispatchEvent(closeWidgetEvent); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function sendAddFundsGoBackEvent(eventTarget: Window | EventTarget) { + const closeWidgetEvent = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT, { + detail: { + type: AddFundsEventType.GO_BACK, + data: {}, + }, + }); + // TODO: please remove or if necessary keep the eslint ignore + // eslint-disable-next-line no-console + console.log('go back event:', closeWidgetEvent); + if (eventTarget !== undefined) eventTarget.dispatchEvent(closeWidgetEvent); +} diff --git a/packages/checkout/widgets-sample-app/src/App.tsx b/packages/checkout/widgets-sample-app/src/App.tsx index 605d251aaf..c210693a42 100644 --- a/packages/checkout/widgets-sample-app/src/App.tsx +++ b/packages/checkout/widgets-sample-app/src/App.tsx @@ -31,6 +31,10 @@ function App() { Checkout Widget
+ +
new Checkout(), []); + const factory = useMemo( + () => new WidgetsFactory(checkout,{}), + [checkout] + ); + const addFunds = useMemo( + () => factory.create(WidgetType.ADD_FUNDS, { + config: { theme: WidgetTheme.LIGHT }, + }), + [factory] + ); + const onRamp = useMemo(() => factory.create(WidgetType.ONRAMP), [factory]); + const swap = useMemo(() => factory.create(WidgetType.SWAP), [factory]); + const bridge = useMemo(() => factory.create(WidgetType.BRIDGE), [factory]); + + useEffect(() => { + addFunds.mount(ADD_FUNDS_TARGET_ID, { + showSwapOption:false, + amount: "10", + tokenAddress: "0x1CcCa691501174B4A623CeDA58cC8f1a76dc3439" + }); + addFunds.addListener(AddFundsEventType.GO_BACK, (data: any) => { + console.log("GO_BACK", data); + }); + addFunds.addListener(AddFundsEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + addFunds.unmount(); + }); + addFunds.addListener(AddFundsEventType.REQUEST_ONRAMP, (data: any) => { + console.log("REQUEST_ONRAMP", data); + addFunds.unmount(); + onRamp.addListener(OnRampEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + onRamp.unmount(); + }); + onRamp.mount(ADD_FUNDS_TARGET_ID, {}); + }); + addFunds.addListener(AddFundsEventType.REQUEST_SWAP, (data: any) => { + console.log("REQUEST_SWAP", data); + addFunds.unmount(); + swap.addListener(SwapEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + swap.unmount(); + }); + swap.mount(ADD_FUNDS_TARGET_ID, {}); + }); + addFunds.addListener(AddFundsEventType.REQUEST_BRIDGE, (data: any) => { + console.log("REQUEST_BRIDGE", data); + addFunds.unmount(); + bridge.addListener(BridgeEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + bridge.unmount(); + }); + bridge.mount(ADD_FUNDS_TARGET_ID, {}); + }); + }, [addFunds]); + + return ( +
+

Checkout Add Funds

+
+ + + + + +
+ ); +} + +export default AddFundsUI; diff --git a/packages/checkout/widgets-sample-app/src/index.tsx b/packages/checkout/widgets-sample-app/src/index.tsx index b75af754ef..b65861d01e 100644 --- a/packages/checkout/widgets-sample-app/src/index.tsx +++ b/packages/checkout/widgets-sample-app/src/index.tsx @@ -12,6 +12,7 @@ import { PassportLoginCallback } from './components/ui/marketplace-orchestrator/ import { Marketplace } from './components/ui/marketplace-orchestrator'; import { SaleUI } from './components/ui/sale/sale'; import CheckoutUI from './components/ui/checkout/checkout'; +import AddFundsUI from './components/ui/add-funds/addFunds'; const router = createBrowserRouter([ { @@ -46,6 +47,10 @@ const router = createBrowserRouter([ path: '/checkout', element: , }, + { + path: '/add-funds', + element: , + }, { path: '/marketplace-orchestrator', element: