From 307be70260e3df71398556e831c102995c327979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=A3=20de=20Souza?= Date: Thu, 28 Nov 2024 15:56:39 +1100 Subject: [PATCH] feat: Add skeleton for Purchase widget (#2398) --- .../definitions/configurations/index.ts | 1 + .../definitions/configurations/purchase.ts | 4 + .../src/widgets/definitions/events/index.ts | 1 + .../definitions/events/orchestration.ts | 1 + .../widgets/definitions/events/purchase.ts | 35 +++++++ .../src/widgets/definitions/events/widgets.ts | 1 + .../widgets/definitions/parameters/index.ts | 1 + .../definitions/parameters/purchase.ts | 23 +++++ .../sdk/src/widgets/definitions/types.ts | 26 ++++- .../SegmentAnalyticsProvider.ts | 1 + .../view-context/PurchaseViewContextTypes.ts | 24 +++++ .../src/context/view-context/ViewContext.tsx | 4 +- packages/checkout/widgets-lib/src/factory.ts | 7 ++ .../src/widgets/purchase/PurchaseWidget.tsx | 98 +++++++++++++++++++ .../widgets/purchase/PurchaseWidgetEvents.ts | 86 ++++++++++++++++ .../widgets/purchase/PurchaseWidgetRoot.tsx | 82 ++++++++++++++++ .../purchase/context/PurchaseContext.tsx | 56 +++++++++++ .../widgets-lib/src/widgets/purchase/types.ts | 0 .../src/widgets/purchase/views/Purchase.tsx | 74 ++++++++++++++ .../src/widgets/purchase/views/Review.tsx | 3 + .../checkout/widgets-sample-app/src/App.tsx | 4 + .../src/components/ui/purchase/purchase.tsx | 53 ++++++++++ .../checkout/widgets-sample-app/src/index.tsx | 5 + 23 files changed, 585 insertions(+), 5 deletions(-) create mode 100644 packages/checkout/sdk/src/widgets/definitions/configurations/purchase.ts create mode 100644 packages/checkout/sdk/src/widgets/definitions/events/purchase.ts create mode 100644 packages/checkout/sdk/src/widgets/definitions/parameters/purchase.ts create mode 100644 packages/checkout/widgets-lib/src/context/view-context/PurchaseViewContextTypes.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidget.tsx create mode 100644 packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetEvents.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetRoot.tsx create mode 100644 packages/checkout/widgets-lib/src/widgets/purchase/context/PurchaseContext.tsx create mode 100644 packages/checkout/widgets-lib/src/widgets/purchase/types.ts create mode 100644 packages/checkout/widgets-lib/src/widgets/purchase/views/Purchase.tsx create mode 100644 packages/checkout/widgets-lib/src/widgets/purchase/views/Review.tsx create mode 100644 packages/checkout/widgets-sample-app/src/components/ui/purchase/purchase.tsx diff --git a/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts b/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts index 3f8bc87604..5b6c5a6367 100644 --- a/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts +++ b/packages/checkout/sdk/src/widgets/definitions/configurations/index.ts @@ -8,3 +8,4 @@ export * from './theme'; export * from './widget'; export * from './addTokens'; export * from './commerce'; +export * from './purchase'; diff --git a/packages/checkout/sdk/src/widgets/definitions/configurations/purchase.ts b/packages/checkout/sdk/src/widgets/definitions/configurations/purchase.ts new file mode 100644 index 0000000000..6fa381f7c5 --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/configurations/purchase.ts @@ -0,0 +1,4 @@ +import { WidgetConfiguration } from './widget'; + +export type PurchaseWidgetConfiguration = { +} & WidgetConfiguration; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/index.ts b/packages/checkout/sdk/src/widgets/definitions/events/index.ts index 75dfd69d1e..ca05e162e9 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/index.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/index.ts @@ -8,3 +8,4 @@ export * from './orchestration'; export * from './onramp'; export * from './commerce'; export * from './addTokens'; +export * from './purchase'; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts index 96a675dff9..4a07fd698f 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts @@ -10,6 +10,7 @@ export enum OrchestrationEventType { REQUEST_BRIDGE = 'request-bridge', REQUEST_ONRAMP = 'request-onramp', REQUEST_ADD_TOKENS = 'request-add-tokens', + REQUEST_PURCHASE = 'request-purchase', REQUEST_GO_BACK = 'request-go-back', } diff --git a/packages/checkout/sdk/src/widgets/definitions/events/purchase.ts b/packages/checkout/sdk/src/widgets/definitions/events/purchase.ts new file mode 100644 index 0000000000..e4d54c6d83 --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/events/purchase.ts @@ -0,0 +1,35 @@ +import { Web3Provider } from '@ethersproject/providers'; +import { EIP6963ProviderInfo } from '../../../types'; + +export enum PurchaseEventType { + CLOSE_WIDGET = 'close-widget', + CONNECT_SUCCESS = 'connect-success', + SUCCESS = 'success', + FAILURE = 'failure', +} + +/** + * Represents a successful purchase. + */ +export type PurchaseSuccess = {}; + +/** + * Type representing a purchase failure + * @property {string} reason + */ +export type PurchaseFailed = { + reason: string; + timestamp: number; +}; + +/** + * Type representing a successful provider connection + * @property {Web3Provider} provider + * @property {EIP6963ProviderInfo} providerInfo + * @property {'from' | 'to'} providerType + */ +export type PurchaseConnectSuccess = { + provider: Web3Provider; + providerInfo: EIP6963ProviderInfo; + providerType: 'from' | 'to'; +}; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts b/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts index b767adb3b4..897e4a274e 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts @@ -14,6 +14,7 @@ export enum IMTBLWidgetEvents { IMTBL_SALE_WIDGET_EVENT = 'imtbl-sale-widget', IMTBL_COMMERCE_WIDGET_EVENT = 'imtbl-commerce-widget', IMTBL_ADD_TOKENS_WIDGET_EVENT = 'imtbl-add-tokens-widget', + IMTBL_PURCHASE_WIDGET_EVENT = 'imtbl-purchase-widget', } /** diff --git a/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts b/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts index f298cfb83b..03608540ae 100644 --- a/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts +++ b/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts @@ -7,3 +7,4 @@ export * from './swap'; export * from './onramp'; export * from './sale'; export * from './commerce'; +export * from './purchase'; diff --git a/packages/checkout/sdk/src/widgets/definitions/parameters/purchase.ts b/packages/checkout/sdk/src/widgets/definitions/parameters/purchase.ts new file mode 100644 index 0000000000..bd382443e2 --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/parameters/purchase.ts @@ -0,0 +1,23 @@ +import { WidgetLanguage } from '../configurations'; + +export type PurchaseWidgetParams = { + /** The language to use for the Purchase widget */ + language?: WidgetLanguage; + + /** Environment id from Immutable Hub */ + environmentId?: string; + + /** Whether to show a back button on the first screen, on click triggers REQUEST_GO_BACK event */ + showBackButton?: boolean; + + /** The list of products to be purchased */ + items?: PurchaseItem[]; +}; + +export type PurchaseItem = { + productId: string; + qty: number; + name: string; + image: string; + description: string; +}; diff --git a/packages/checkout/sdk/src/widgets/definitions/types.ts b/packages/checkout/sdk/src/widgets/definitions/types.ts index 5c98666339..d49ef883e5 100644 --- a/packages/checkout/sdk/src/widgets/definitions/types.ts +++ b/packages/checkout/sdk/src/widgets/definitions/types.ts @@ -45,6 +45,10 @@ import { AddTokensConnectSuccess, AddTokensSuccess, AddTokensFailed, + PurchaseConnectSuccess, + PurchaseEventType, + PurchaseFailed, + PurchaseSuccess, } from './events'; import { BridgeWidgetParams, @@ -54,8 +58,9 @@ import { OnRampWidgetParams, CommerceWidgetParams, AddTokensWidgetParams, + SaleWidgetParams, + PurchaseWidgetParams, } from './parameters'; -import { SaleWidgetParams } from './parameters/sale'; import { BridgeWidgetConfiguration, ConnectWidgetConfiguration, @@ -65,8 +70,9 @@ import { WalletWidgetConfiguration, CommerceWidgetConfiguration, AddTokensWidgetConfiguration, + WidgetTheme, + PurchaseWidgetConfiguration, } from './configurations'; -import { WidgetTheme } from './configurations/theme'; /** * Enum representing the list of widget types. @@ -80,6 +86,7 @@ export enum WidgetType { SALE = 'sale', IMMUTABLE_COMMERCE = 'immutableCommerce', ADD_TOKENS = 'addTokens', + PURCHASE = 'purchase', } /** @@ -98,6 +105,7 @@ export type WidgetConfigurations = { [WidgetType.ONRAMP]: OnrampWidgetConfiguration; [WidgetType.SALE]: SaleWidgetConfiguration; [WidgetType.ADD_TOKENS]: AddTokensWidgetConfiguration; + [WidgetType.PURCHASE]: PurchaseWidgetConfiguration; [WidgetType.IMMUTABLE_COMMERCE]: CommerceWidgetConfiguration; }; @@ -110,6 +118,7 @@ export type WidgetParameters = { [WidgetType.ONRAMP]: OnRampWidgetParams; [WidgetType.SALE]: SaleWidgetParams; [WidgetType.ADD_TOKENS]: AddTokensWidgetParams; + [WidgetType.PURCHASE]: PurchaseWidgetParams; [WidgetType.IMMUTABLE_COMMERCE]: CommerceWidgetParams; }; @@ -125,6 +134,7 @@ export type WidgetEventTypes = { [WidgetType.SALE]: SaleEventType | OrchestrationEventType; [WidgetType.IMMUTABLE_COMMERCE]: CommerceEventType | OrchestrationEventType; [WidgetType.ADD_TOKENS]: AddTokensEventType | OrchestrationEventType; + [WidgetType.PURCHASE]: PurchaseEventType | OrchestrationEventType; }; // Mapping of Orchestration events to their payloads @@ -143,9 +153,9 @@ type ProviderEventMapping = { }; /** - * Mapping of widget type, to each of it's events and then each event's payload + * Mapping of widget type, to each of its events and then each event's payload * Update this whenever a new event is created and used by a widget - * Each widget also has all of the orchestration events + * Each widget also has all the orchestration events */ export type WidgetEventData = { [WidgetType.CONNECT]: { @@ -218,6 +228,14 @@ export type WidgetEventData = { [AddTokensEventType.FAILURE]: AddTokensFailed; } & OrchestrationMapping & ProviderEventMapping; + + [WidgetType.PURCHASE]: { + [PurchaseEventType.CLOSE_WIDGET]: {}; + [PurchaseEventType.CONNECT_SUCCESS]: PurchaseConnectSuccess; + [PurchaseEventType.SUCCESS]: PurchaseSuccess; + [PurchaseEventType.FAILURE]: PurchaseFailed; + } & 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 c6a631ccb6..0aa70def03 100644 --- a/packages/checkout/widgets-lib/src/context/analytics-provider/SegmentAnalyticsProvider.ts +++ b/packages/checkout/widgets-lib/src/context/analytics-provider/SegmentAnalyticsProvider.ts @@ -12,6 +12,7 @@ export enum UserJourney { BRIDGE = 'Bridge', SALE = 'PrimarySale', ADD_TOKENS = 'AddTokens', + PURCHASE = 'Purchase', } export type AnalyticsControlTypes = diff --git a/packages/checkout/widgets-lib/src/context/view-context/PurchaseViewContextTypes.ts b/packages/checkout/widgets-lib/src/context/view-context/PurchaseViewContextTypes.ts new file mode 100644 index 0000000000..a52e3e6c3d --- /dev/null +++ b/packages/checkout/widgets-lib/src/context/view-context/PurchaseViewContextTypes.ts @@ -0,0 +1,24 @@ +import { ViewType } from './ViewType'; + +export enum PurchaseWidgetViews { + PURCHASE = 'PURCHASE', + REVIEW = 'REVIEW', + GEO_BLOCK_ERROR = 'GEO_BLOCK_ERROR', +} + +export type PurchaseWidgetView = PurchaseView | PurchaseReview | GeoBlockErrorView; + +interface PurchaseView extends ViewType { + type: PurchaseWidgetViews.PURCHASE; +} + +interface PurchaseReview extends ViewType { + type: PurchaseWidgetViews.REVIEW; + data: PurchaseReviewData; +} + +interface GeoBlockErrorView extends ViewType { + type: PurchaseWidgetViews.GEO_BLOCK_ERROR; +} + +export interface PurchaseReviewData {} diff --git a/packages/checkout/widgets-lib/src/context/view-context/ViewContext.tsx b/packages/checkout/widgets-lib/src/context/view-context/ViewContext.tsx index 1b3a458ee7..c99ae32347 100644 --- a/packages/checkout/widgets-lib/src/context/view-context/ViewContext.tsx +++ b/packages/checkout/widgets-lib/src/context/view-context/ViewContext.tsx @@ -10,6 +10,7 @@ import { ViewType } from './ViewType'; import { OnRampWidgetView } from './OnRampViewContextTypes'; import { AddTokensWidgetView } from './AddTokensViewContextTypes'; import { CheckoutWidgetView } from './CheckoutWidgetViewContextTypes'; +import { PurchaseWidgetView } from './PurchaseViewContextTypes'; export enum SharedViews { LOADING_VIEW = 'LOADING_VIEW', @@ -54,7 +55,8 @@ export type View = | SaleWidgetView | BridgeWidgetView | AddTokensWidgetView - | CheckoutWidgetView; + | CheckoutWidgetView + | PurchaseWidgetView; export interface ViewState { view: View; diff --git a/packages/checkout/widgets-lib/src/factory.ts b/packages/checkout/widgets-lib/src/factory.ts index 7a9b323093..285b166bdc 100644 --- a/packages/checkout/widgets-lib/src/factory.ts +++ b/packages/checkout/widgets-lib/src/factory.ts @@ -24,6 +24,7 @@ import { OnRamp } from './widgets/on-ramp/OnRampWidgetRoot'; import { Sale } from './widgets/sale/SaleWidgetRoot'; import { Swap } from './widgets/swap/SwapWidgetRoot'; import { Wallet } from './widgets/wallet/WalletWidgetRoot'; +import { Purchase } from './widgets/purchase/PurchaseWidgetRoot'; export class WidgetsFactory implements IWidgetsFactory { private sdk: Checkout; @@ -107,6 +108,12 @@ export class WidgetsFactory implements IWidgetsFactory { provider, }) as Widget as Widget; } + case WidgetType.PURCHASE: { + return new Purchase(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/purchase/PurchaseWidget.tsx b/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidget.tsx new file mode 100644 index 0000000000..3ad48b0664 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidget.tsx @@ -0,0 +1,98 @@ +import { IMTBLWidgetEvents, PurchaseWidgetParams } from '@imtbl/checkout-sdk'; +import { CloudImage, Stack, useTheme } from '@biom3/react'; +import { useContext, useMemo, useReducer } from 'react'; +import { StrongCheckoutWidgetsConfig } from '../../lib/withDefaultWidgetConfig'; +import { initialPurchaseState, PurchaseContext, purchaseReducer } from './context/PurchaseContext'; +import { PurchaseWidgetViews } from '../../context/view-context/PurchaseViewContextTypes'; +import { + initialViewState, + ViewContext, + viewReducer, +} from '../../context/view-context/ViewContext'; +import { Purchase } from './views/Purchase'; +import { sendPurchaseCloseEvent } from './PurchaseWidgetEvents'; +import { orchestrationEvents } from '../../lib/orchestrationEvents'; +import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; +import { getRemoteImage } from '../../lib/utils'; + +export type PurchaseWidgetInputs = PurchaseWidgetParams & { + config: StrongCheckoutWidgetsConfig; +}; + +export default function PurchaseWidget({ + config, + showBackButton, +}: PurchaseWidgetInputs) { + const { base: { colorMode } } = useTheme(); + + const [viewState, viewDispatch] = useReducer(viewReducer, { + ...initialViewState, + view: { type: PurchaseWidgetViews.PURCHASE }, + history: [{ type: PurchaseWidgetViews.PURCHASE }], + }); + + const viewReducerValues = useMemo( + () => ({ + viewState, + viewDispatch, + }), + [viewState, viewReducer], + ); + + const [purchaseState, purchaseDispatch] = useReducer( + purchaseReducer, + initialPurchaseState, + ); + + const purchaseReducerValues = useMemo( + () => ({ + purchaseState, + purchaseDispatch, + }), + [purchaseState, purchaseReducer], + ); + + const { + eventTargetState: { eventTarget }, + } = useContext(EventTargetContext); + + return ( + + + + + )} + sx={{ + pos: 'absolute', + h: '100%', + w: '100%', + objectFit: 'cover', + objectPosition: 'center', + }} + /> + {viewState.view.type === PurchaseWidgetViews.PURCHASE && ( + sendPurchaseCloseEvent(eventTarget)} + onBackButtonClick={() => { + orchestrationEvents.sendRequestGoBackEvent( + eventTarget, + IMTBLWidgetEvents.IMTBL_PURCHASE_WIDGET_EVENT, + {}, + ); + }} + /> + )} + + + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetEvents.ts b/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetEvents.ts new file mode 100644 index 0000000000..caa71ae9a3 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetEvents.ts @@ -0,0 +1,86 @@ +import { Web3Provider } from '@ethersproject/providers'; +import { + WidgetEvent, + WidgetType, + PurchaseEventType, + IMTBLWidgetEvents, + EIP6963ProviderInfo, +} from '@imtbl/checkout-sdk'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function sendPurchaseCloseEvent(eventTarget: Window | EventTarget) { + const closeWidgetEvent = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_PURCHASE_WIDGET_EVENT, { + detail: { + type: PurchaseEventType.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); +} + +export function sendConnectProviderSuccessEvent( + eventTarget: Window | EventTarget, + providerType: 'from' | 'to', + provider: Web3Provider, + providerInfo: EIP6963ProviderInfo, +) { + const successEvent = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_PURCHASE_WIDGET_EVENT, { + detail: { + type: PurchaseEventType.CONNECT_SUCCESS, + data: { + provider, + providerType, + providerInfo, + }, + }, + }); + // eslint-disable-next-line no-console + console.log( + `connect ${providerType}Provider success event:`, + eventTarget, + successEvent, + ); + if (eventTarget !== undefined) eventTarget.dispatchEvent(successEvent); +} + +export const sendPurchaseSuccessEvent = (eventTarget: Window | EventTarget, transactionHash: string) => { + const successEvent = new CustomEvent>( + IMTBLWidgetEvents.IMTBL_PURCHASE_WIDGET_EVENT, + { + detail: { + type: PurchaseEventType.SUCCESS, + data: { + transactionHash, + }, + }, + }, + ); + // eslint-disable-next-line no-console + console.log('purchase success event:', eventTarget, successEvent); + if (eventTarget !== undefined) eventTarget.dispatchEvent(successEvent); +}; + +export const sendPurchaseFailedEvent = (eventTarget: Window | EventTarget, reason: string) => { + const failedEvent = new CustomEvent>( + IMTBLWidgetEvents.IMTBL_PURCHASE_WIDGET_EVENT, + { + detail: { + type: PurchaseEventType.FAILURE, + data: { + reason, + timestamp: new Date().getTime(), + }, + }, + }, + ); + // eslint-disable-next-line no-console + console.log('purchase failed event:', eventTarget, failedEvent); + if (eventTarget !== undefined) eventTarget.dispatchEvent(failedEvent); +}; diff --git a/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetRoot.tsx b/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetRoot.tsx new file mode 100644 index 0000000000..31622c175a --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/purchase/PurchaseWidgetRoot.tsx @@ -0,0 +1,82 @@ +import { + IMTBLWidgetEvents, + PurchaseWidgetParams, + WidgetConfiguration, + WidgetProperties, + WidgetTheme, + WidgetType, +} from '@imtbl/checkout-sdk'; +import React, { Suspense } from 'react'; +import { Base } from '../BaseWidgetRoot'; +import i18n from '../../i18n'; +import { CustomAnalyticsProvider } from '../../context/analytics-provider/CustomAnalyticsProvider'; +import { ThemeProvider } from '../../components/ThemeProvider/ThemeProvider'; +import { HandoverProvider } from '../../context/handover-context/HandoverProvider'; +import { ProvidersContextProvider } from '../../context/providers-context/ProvidersContext'; +import { LoadingView } from '../../views/loading/LoadingView'; +import PurchaseWidget from './PurchaseWidget'; + +export class Purchase extends Base { + protected eventTopic: IMTBLWidgetEvents = IMTBLWidgetEvents.IMTBL_PURCHASE_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: PurchaseWidgetParams, + ): PurchaseWidgetParams { + const validatedParams = params; + + // TODO + + return validatedParams; + } + + protected render() { + if (!this.reactRoot) return; + + const { t } = i18n; + + this.reactRoot.render( + + + + + + + } + > + + + + + + + , + ); + } +} diff --git a/packages/checkout/widgets-lib/src/widgets/purchase/context/PurchaseContext.tsx b/packages/checkout/widgets-lib/src/widgets/purchase/context/PurchaseContext.tsx new file mode 100644 index 0000000000..ff5fa41154 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/purchase/context/PurchaseContext.tsx @@ -0,0 +1,56 @@ +import { Squid } from '@0xsquid/sdk'; +import { createContext } from 'react'; + +export interface PurchaseState { + squid: Squid | null; +} + +export const initialPurchaseState: PurchaseState = { + squid: null, +}; + +export interface PurchaseContextState { + purchaseState: PurchaseState; + purchaseDispatch: React.Dispatch; +} + +export interface PurchaseAction { + payload: ActionPayload; +} + +type ActionPayload = + | SetSquid; + +export enum PurchaseActions { + SET_SQUID = 'SET_SQUID', +} + +export interface SetSquid { + type: PurchaseActions.SET_SQUID; + squid: Squid; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const PurchaseContext = createContext({ + purchaseState: initialPurchaseState, + purchaseDispatch: () => {}, +}); + +PurchaseContext.displayName = 'PurchaseContext'; + +export type Reducer = (prevState: S, action: A) => S; + +export const purchaseReducer: Reducer = ( + state: PurchaseState, + action: PurchaseAction, +) => { + switch (action.payload.type) { + case PurchaseActions.SET_SQUID: + return { + ...state, + squid: action.payload.squid, + }; + default: + return state; + } +}; diff --git a/packages/checkout/widgets-lib/src/widgets/purchase/types.ts b/packages/checkout/widgets-lib/src/widgets/purchase/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/checkout/widgets-lib/src/widgets/purchase/views/Purchase.tsx b/packages/checkout/widgets-lib/src/widgets/purchase/views/Purchase.tsx new file mode 100644 index 0000000000..263a16d442 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/purchase/views/Purchase.tsx @@ -0,0 +1,74 @@ +import { ButtCon, Caption, Stack } from '@biom3/react'; +import { SimpleLayout } from '../../../components/SimpleLayout/SimpleLayout'; + +interface PurchaseProps { + showBackButton?: boolean; + onCloseButtonClick?: () => void; + onBackButtonClick?: () => void; +} + +export function Purchase({ + onCloseButtonClick, + showBackButton, + onBackButtonClick, +}: PurchaseProps) { + const shouldShowBackButton = showBackButton && onBackButtonClick; + + return ( + + {shouldShowBackButton && ( + + )} + + + )} + > + + + + Coming soon + + + + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/purchase/views/Review.tsx b/packages/checkout/widgets-lib/src/widgets/purchase/views/Review.tsx new file mode 100644 index 0000000000..a733a343bb --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/purchase/views/Review.tsx @@ -0,0 +1,3 @@ +export function Review() { + return
Review
; +} diff --git a/packages/checkout/widgets-sample-app/src/App.tsx b/packages/checkout/widgets-sample-app/src/App.tsx index 5290da2cc7..30ae9baa55 100644 --- a/packages/checkout/widgets-sample-app/src/App.tsx +++ b/packages/checkout/widgets-sample-app/src/App.tsx @@ -35,6 +35,10 @@ function App() { Add Tokens Widget
+ +
new Checkout(), []); + const factory = useMemo(() => new WidgetsFactory(checkout, { theme: WidgetTheme.DARK }), [checkout]); + const purchase = useMemo(() => factory.create(WidgetType.PURCHASE), [factory]); + + useEffect(() => { + purchase.mount(PURCHASE_WIDGET_ID, {}); + + purchase.addListener(PurchaseEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + purchase.unmount(); + }); + + purchase.addListener(PurchaseEventType.CONNECT_SUCCESS, (data: any) => { + console.log("CONNECT_SUCCESS", data); + }); + + return () => { + purchase.removeListener(PurchaseEventType.CLOSE_WIDGET); + purchase.removeListener(PurchaseEventType.CONNECT_SUCCESS); + } + }, [purchase]); + + return ( +
+

Checkout Purchase

+ +
+ + + + + +
+ ); +} diff --git a/packages/checkout/widgets-sample-app/src/index.tsx b/packages/checkout/widgets-sample-app/src/index.tsx index 1be4f520a7..0c8e0c4a69 100644 --- a/packages/checkout/widgets-sample-app/src/index.tsx +++ b/packages/checkout/widgets-sample-app/src/index.tsx @@ -17,6 +17,7 @@ import { SaleUI } from "./components/ui/sale/sale"; import AddTokensUI from "./components/ui/add-tokens/addTokens"; import { AddTokensPassportLogin } from "./components/ui/add-tokens/login"; import { AddTokensPassportLogout } from "./components/ui/add-tokens/logout"; +import PurchaseUI from './components/ui/purchase/purchase'; const router = createBrowserRouter([ { @@ -51,6 +52,10 @@ const router = createBrowserRouter([ path: "/checkout", element: , }, + { + path: "/purchase", + element: , + }, { path: "/add-tokens", element: ,