Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/widget-playground-vite/.env.staging
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
VITE_API_URL=https://staging.li.quest/v1
# Set VITE_API_KEY in .env.staging.local (git-ignored)
# in .env.dev.local (git-ignored)
# VITE_API_KEY
11 changes: 11 additions & 0 deletions packages/widget-playground-vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@ import type { JSX, PropsWithChildren } from 'react'
const queryClient = new QueryClient()

const AppProvider = ({ children }: PropsWithChildren) => {
const checkoutToChainRaw = import.meta.env.VITE_CHECKOUT_TO_CHAIN
const checkoutToChain = checkoutToChainRaw
? Number(checkoutToChainRaw)
: undefined

return (
<EnvVariablesProvider
EVMWalletConnectId={import.meta.env.VITE_EVM_WALLET_CONNECT}
TVMWalletConnectId={import.meta.env.VITE_TVM_WALLET_CONNECT}
checkoutIntegrator={import.meta.env.VITE_CHECKOUT_INTEGRATOR || undefined}
checkoutToChain={
Number.isNaN(checkoutToChain) ? undefined : checkoutToChain
}
checkoutToToken={import.meta.env.VITE_CHECKOUT_TO_TOKEN || undefined}
checkoutToAddress={import.meta.env.VITE_CHECKOUT_TO_ADDRESS || undefined}
>
<QueryClientProvider client={queryClient}>
<WidgetConfigProvider defaultWidgetConfig={defaultWidgetConfig}>
Expand Down
3 changes: 3 additions & 0 deletions packages/widget-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@lifi/widget": "workspace:*",
"@lifi/widget-checkout": "workspace:*",
"@lifi/widget-provider-bitcoin": "workspace:*",
"@lifi/widget-provider-ethereum": "workspace:*",
"@lifi/widget-provider-mesh": "workspace:*",
"@lifi/widget-provider-solana": "workspace:*",
"@lifi/widget-provider-sui": "workspace:*",
"@lifi/widget-provider-transak": "workspace:*",
"@lifi/widget-provider-tron": "workspace:*",
"@metamask/connect-evm": "^1.4.0",
"@mui/icons-material": "^9.0.1",
Expand Down
22 changes: 17 additions & 5 deletions packages/widget-playground/src/components/ModeDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useConfigActions } from '../store/widgetConfig/useConfigActions.js'
import {
useConfigMode,
useConfigModeOptions,
usePlaygroundWidgetMode,
} from '../store/widgetConfig/useConfigValues.js'
import { useDefaultConfig } from '../store/widgetConfig/useDefaultConfig.js'
import { docsLinks } from '../utils/docsLinks.js'
Expand All @@ -26,26 +27,37 @@ export const ModeDetailView = ({
}: ModeDetailViewProps): JSX.Element => {
const { mode } = useConfigMode()
const { modeOptions } = useConfigModeOptions()
const { setMode, setSplitOption } = useConfigActions()
const { playgroundWidgetMode } = usePlaygroundWidgetMode()
const { setMode, setSplitOption, setPlaygroundWidgetMode } =
useConfigActions()
const { defaultConfig } = useDefaultConfig()

const splitOption = getSplitOption(modeOptions)
const activeMode = getActiveMode(mode, splitOption)
const activeMode =
playgroundWidgetMode === 'checkout'
? 'checkout'
: getActiveMode(mode, splitOption)

const handleReset = useCallback((): void => {
setPlaygroundWidgetMode('swap')
setMode(defaultConfig?.mode ?? 'default')
const defaultSplit = defaultConfig?.modeOptions?.split
setSplitOption(typeof defaultSplit === 'string' ? defaultSplit : undefined)
}, [defaultConfig, setMode, setSplitOption])
}, [defaultConfig, setMode, setSplitOption, setPlaygroundWidgetMode])

const handleSelect = useCallback(
(selectedMode: ModeOption): void => {
if (selectedMode === 'checkout') {
setPlaygroundWidgetMode('checkout')
return
}
setPlaygroundWidgetMode('swap')
const { mode: nextMode, splitOption: nextSplitOption } =
getModeConfig(selectedMode)
setMode(nextMode)
setMode(nextMode ?? 'default')
setSplitOption(nextSplitOption)
},
[setMode, setSplitOption]
[setMode, setSplitOption, setPlaygroundWidgetMode]
)

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { ChainType } from '@lifi/widget'
import { LifiWidgetCheckout } from '@lifi/widget-checkout'
import { meshProvider } from '@lifi/widget-provider-mesh'
import { transakProvider } from '@lifi/widget-provider-transak'
import { Box, Button, Typography } from '@mui/material'
import { useAppKit, useAppKitAccount } from '@reown/appkit/react'
import { type JSX, useCallback, useEffect, useMemo, useState } from 'react'
import { widgetBaseConfig } from '../../defaultWidgetConfig.js'
import { useEnvVariables } from '../../providers/EnvVariablesProvider.js'
import { useConfig } from '../../store/widgetConfig/useConfig.js'

const DEFAULT_CHECKOUT_INTEGRATOR = 'widget-transak-test'
const DEFAULT_CHECKOUT_TO_CHAIN = 1
// USDC on Ethereum mainnet — demo destination asset when none is configured.
const DEFAULT_CHECKOUT_TO_TOKEN = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'

export function CheckoutWidgetView(): JSX.Element {
const { config } = useConfig()
const {
checkoutIntegrator,
checkoutToChain,
checkoutToToken,
checkoutToAddress,
} = useEnvVariables()
const [open, setOpen] = useState(false)
const { open: openWallet } = useAppKit()
const { address: connectedAddress } = useAppKitAccount()

// Keep the first connected address — disconnecting a funding wallet must not wipe the recipient.
const [demoRecipient, setDemoRecipient] = useState<string>()
useEffect(() => {
if (connectedAddress) {
setDemoRecipient(connectedAddress)
}
}, [connectedAddress])

const handleOpen = useCallback(() => {
setOpen(true)
}, [])

const handleClose = useCallback(() => {
setOpen(false)
}, [])

const handleConnect = useCallback(() => {
openWallet()
}, [openWallet])

// TODO(cleanup-remove-integrator-override-heuristic): Remove this heuristic comparison against
// widgetBaseConfig.integrator and use a strict precedence contract.
const resolvedIntegrator =
config?.integrator && config.integrator !== widgetBaseConfig.integrator
? config.integrator
: checkoutIntegrator?.trim() || DEFAULT_CHECKOUT_INTEGRATOR

const resolvedToChain =
config?.toChain ?? checkoutToChain ?? DEFAULT_CHECKOUT_TO_CHAIN
const resolvedToToken =
config?.toToken ?? checkoutToToken ?? DEFAULT_CHECKOUT_TO_TOKEN
const resolvedToAddress = checkoutToAddress ?? demoRecipient

const checkoutConfig = useMemo(
() => ({
...config,
providers: config?.providers ?? widgetBaseConfig.providers,
toChain: resolvedToChain,
toToken: resolvedToToken,
...(resolvedToAddress
? {
toAddress: {
address: resolvedToAddress,
chainType: ChainType.EVM,
},
}
: null),
walletConfig: { onConnect: () => openWallet() },
}),
[config, resolvedToChain, resolvedToToken, resolvedToAddress, openWallet]
)

const onRampProviders = useMemo(() => [transakProvider(), meshProvider()], [])

return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
p: 3,
flex: 1,
minHeight: 320,
}}
>
<Typography
variant="body2"
color="text.secondary"
sx={{ textAlign: 'center' }}
>
Checkout — opens as a centered widget
</Typography>
{resolvedToAddress ? (
<>
<Typography
variant="caption"
color="text.secondary"
sx={{ textAlign: 'center', wordBreak: 'break-all' }}
>
Recipient: {resolvedToAddress}
</Typography>
<Button variant="contained" onClick={handleOpen}>
Deposit
</Button>
</>
) : (
<>
<Typography
variant="caption"
color="text.secondary"
sx={{ textAlign: 'center' }}
>
Connect a wallet to set the checkout recipient.
</Typography>
<Button variant="contained" onClick={handleConnect}>
Connect wallet
</Button>
</>
)}
<LifiWidgetCheckout
open={open}
integrator={resolvedIntegrator}
config={checkoutConfig}
onRampProviders={onRampProviders}
onClose={handleClose}
/>
</Box>
)
}
14 changes: 14 additions & 0 deletions packages/widget-playground/src/components/Widget/WidgetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import type { FieldNames, FormState, WidgetDrawer } from '@lifi/widget'
import { LiFiWidget, WidgetSkeleton } from '@lifi/widget'
import type { JSX } from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { CheckoutWalletProvider } from '../../providers/ExternalWalletProvider/CheckoutWalletProvider.js'
import { useFormValues } from '../../store/editTools/useFormValues.js'
import { useSkeletonToolValues } from '../../store/editTools/useSkeletonToolValues.js'
import { useConfig } from '../../store/widgetConfig/useConfig.js'
import { usePlaygroundWidgetMode } from '../../store/widgetConfig/useConfigValues.js'
import { CheckoutWidgetView } from './CheckoutWidgetView.js'
import { WidgetViewContainer } from './WidgetViewContainer.js'

export function WidgetView(): JSX.Element {
const { config } = useConfig()
const { playgroundWidgetMode } = usePlaygroundWidgetMode()
const drawerRef = useRef<WidgetDrawer>(null)
const formRef = useRef<FormState>(null)
const { isSkeletonShown } = useSkeletonToolValues()
Expand All @@ -30,6 +34,16 @@ export function WidgetView(): JSX.Element {
}
}, [formValues])

if (playgroundWidgetMode === 'checkout') {
return (
<WidgetViewContainer>
<CheckoutWalletProvider>
<CheckoutWidgetView />
</CheckoutWalletProvider>
</WidgetViewContainer>
)
}

return (
<WidgetViewContainer toggleDrawer={toggleDrawer}>
{!isSkeletonShown ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useHeaderAndFooterToolValues } from '../../store/editTools/useHeaderAnd
import {
useConfigContainer,
useConfigVariant,
usePlaygroundWidgetMode,
} from '../../store/widgetConfig/useConfigValues.js'
import { useWidgetConfigStore } from '../../store/widgetConfig/WidgetConfigProvider.js'
import { isFullHeightLayout } from '../../utils/layout.js'
Expand Down Expand Up @@ -37,13 +38,16 @@ export function WidgetViewContainer({
}: WidgetViewContainerProps): JSX.Element {
const { container } = useConfigContainer()
const { variant } = useConfigVariant()
const { playgroundWidgetMode } = usePlaygroundWidgetMode()
const walletConfig = useWidgetConfigStore((s) => s.config?.walletConfig)
const { isDrawerOpen, drawerWidth } = useDrawerToolValues()
const { setDrawerOpen } = useEditToolsActions()
const { showMockHeader, showMockFooter, isFooterFixed } =
useHeaderAndFooterToolValues()

const isWalletManagementExternal = !!walletConfig
// Checkout owns its wallet stack via CheckoutWalletProvider; mounting here would create a second AppKit.
const isWalletManagementExternal =
playgroundWidgetMode !== 'checkout' && !!walletConfig
const isFullHeight = isFullHeightLayout(container)

const showHeader = isFullHeight && showMockHeader
Expand Down
3 changes: 3 additions & 0 deletions packages/widget-playground/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ declare global {
readonly VITE_API_URL?: string
readonly VITE_API_KEY?: string
readonly VITE_TVM_WALLET_CONNECT?: string
readonly VITE_CHECKOUT_INTEGRATOR?: string
readonly VITE_CHECKOUT_TO_CHAIN?: string
readonly VITE_CHECKOUT_TO_TOKEN?: string
}

interface ImportMeta {
Expand Down
7 changes: 6 additions & 1 deletion packages/widget-playground/src/hooks/useSidebarNavLabels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useConfigModeOptions,
useConfigVariant,
useConfigWalletManagement,
usePlaygroundWidgetMode,
} from '../store/widgetConfig/useConfigValues.js'
import { useThemeValues } from '../store/widgetConfig/useThemeValues.js'
import { getLayoutLabel, getLayoutMode } from '../utils/layout.js'
Expand All @@ -19,6 +20,7 @@ export const useSidebarNavLabels = (): {
walletValue: string
} => {
const { themeMode } = useThemeMode()
const { playgroundWidgetMode } = usePlaygroundWidgetMode()
const { mode } = useConfigMode()
const { modeOptions } = useConfigModeOptions()
const { variant } = useConfigVariant()
Expand All @@ -33,7 +35,10 @@ export const useSidebarNavLabels = (): {
themeLabel: selectedThemeItem
? formatThemeDisplayName(selectedThemeItem, themeMode)
: undefined,
modeValue: getModeLabel(mode, modeOptions?.split as string | undefined),
modeValue:
playgroundWidgetMode === 'checkout'
? 'Checkout'
: getModeLabel(mode, modeOptions?.split as string | undefined),
variantValue:
variant === 'compact'
? 'Compact'
Expand Down
41 changes: 35 additions & 6 deletions packages/widget-playground/src/providers/EnvVariablesProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,62 @@
import type { JSX, PropsWithChildren } from 'react'
import { createContext, useContext } from 'react'

const EnvVariablesContext = createContext({
export interface PlaygroundEnvVariables {
EVMWalletConnectId: string
TVMWalletConnectId: string
/** Checkout integrator forwarded in body/header, e.g. `local-test`. */
checkoutIntegrator?: string
/** Default checkout target chain id for testing, e.g. `1`. */
checkoutToChain?: number
/** Default checkout target token address for testing. */
checkoutToToken?: string
/** Default checkout recipient address; falls back to the connected wallet. */
checkoutToAddress?: string
}

const EnvVariablesContext = createContext<PlaygroundEnvVariables>({
EVMWalletConnectId: '',
TVMWalletConnectId: '',
checkoutIntegrator: undefined,
checkoutToChain: undefined,
checkoutToToken: undefined,
checkoutToAddress: undefined,
})

interface EvnVariablesProviderProps extends PropsWithChildren {
EVMWalletConnectId: string
TVMWalletConnectId: string
checkoutIntegrator?: string
checkoutToChain?: number
checkoutToToken?: string
checkoutToAddress?: string
}

export const EnvVariablesProvider = ({
children,
EVMWalletConnectId,
TVMWalletConnectId,
checkoutIntegrator,
checkoutToChain,
checkoutToToken,
checkoutToAddress,
}: EvnVariablesProviderProps): JSX.Element => {
return (
<EnvVariablesContext.Provider
value={{ EVMWalletConnectId, TVMWalletConnectId }}
value={{
EVMWalletConnectId,
TVMWalletConnectId,
checkoutIntegrator,
checkoutToChain,
checkoutToToken,
checkoutToAddress,
}}
>
{children}
</EnvVariablesContext.Provider>
)
}

export const useEnvVariables = (): {
EVMWalletConnectId: string
TVMWalletConnectId: string
} => {
export const useEnvVariables = (): PlaygroundEnvVariables => {
return useContext(EnvVariablesContext)
}
Loading