Skip to content
Draft
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
9 changes: 5 additions & 4 deletions e2e/tests/playground/settings.developer-controls.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1003,11 +1003,12 @@ test.describe('Playground settings — Developer controls (Widget events)', () =
await widget.settingsButton.click()
})

await test.step('expand Route priority and click Fastest to change the setting', async () => {
// Clicking Route priority expands the accordion; then clicking the Fastest tab calls
// setValue('routePriority', 'FASTEST') which emits WidgetEvent.SettingUpdated.
await test.step('open Route priority and click Fastest to change the setting', async () => {
// Clicking Route priority navigates to the dedicated Route priority page; then clicking
// the Fastest list item calls setValue('routePriority', 'FASTEST') which emits
// WidgetEvent.SettingUpdated.
await settings.routePriorityButton.click()
await widget.root.getByRole('tab', { name: /fastest/i }).click()
await widget.root.getByRole('button', { name: /fastest/i }).click()
})

await test.step('settingUpdated appears in console output', async () => {
Expand Down
16 changes: 16 additions & 0 deletions packages/widget/src/AppDefault.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { NotFound } from './components/NotFound.js'
import { ActivitiesPage } from './pages/ActivitiesPage/ActivitiesPage.js'
import { LanguagesPage } from './pages/LanguagesPage.js'
import { MainPage } from './pages/MainPage/MainPage.js'
import { RoutePriorityPage } from './pages/RoutePriorityPage.js'
import { RoutesPage } from './pages/RoutesPage/RoutesPage.js'
import { SelectChainPage } from './pages/SelectChainPage/SelectChainPage.js'
import { SelectEnabledToolsPage } from './pages/SelectEnabledToolsPage.js'
Expand All @@ -22,6 +23,7 @@ import { RecentWalletsPage } from './pages/SendToWallet/RecentWalletsPage.js'
import { SendToConfiguredWalletPage } from './pages/SendToWallet/SendToConfiguredWalletPage.js'
import { SendToWalletPage } from './pages/SendToWallet/SendToWalletPage.js'
import { SettingsPage } from './pages/SettingsPage/SettingsPage.js'
import { SlippagePage } from './pages/SettingsPage/SlippageSettings/SlippagePage.js'
import { TransactionDetailsPage } from './pages/TransactionDetailsPage/TransactionDetailsPage.js'
import { TransactionPage } from './pages/TransactionPage/TransactionPage.js'
import { navigationRoutes } from './utils/navigationRoutes.js'
Expand Down Expand Up @@ -66,6 +68,18 @@ const settingsLanguagesRoute = createRoute({
component: LanguagesPage,
})

const settingsRoutePriorityRoute = createRoute({
getParentRoute: () => settingsLayoutRoute,
path: navigationRoutes.routePriority,
component: RoutePriorityPage,
})

const settingsSlippageRoute = createRoute({
getParentRoute: () => settingsLayoutRoute,
path: navigationRoutes.slippage,
component: SlippagePage,
})

const fromTokenLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
path: navigationRoutes.fromToken,
Expand Down Expand Up @@ -214,6 +228,8 @@ const routeTree = rootRoute.addChildren([
settingsLayoutRoute.addChildren([
settingsIndexRoute,
settingsLanguagesRoute,
settingsRoutePriorityRoute,
settingsSlippageRoute,
settingsBridgesRoute,
settingsExchangesRoute,
]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Box, ButtonBase, styled, Typography } from '@mui/material'
import type React from 'react'

export const QuickSettingsContainer: React.FC<
React.ComponentProps<typeof Box>
> = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
}))

export const QuickSettingButton: React.FC<
React.ComponentProps<typeof ButtonBase>
> = styled(ButtonBase)(({ theme }) => ({
display: 'flex',
width: '100%',
justifyContent: 'space-between',
alignItems: 'center',
gap: theme.spacing(1),
padding: theme.spacing(1.5),
borderRadius: theme.vars.shape.borderRadius,
}))

export const QuickSettingTitle: React.FC<
React.ComponentProps<typeof Typography>
> = styled(Typography)(({ theme }) => ({
fontSize: 14,
lineHeight: 1,
fontWeight: 700,
color: theme.vars.palette.text.primary,
}))

export const QuickSettingValue: React.FC<
React.ComponentProps<typeof Typography>
> = styled(Typography)(({ theme }) => ({
fontSize: 14,
lineHeight: 1,
fontWeight: 500,
color: theme.vars.palette.text.secondary,
whiteSpace: 'nowrap',
}))
69 changes: 69 additions & 0 deletions packages/widget/src/components/QuickSettings/QuickSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { BoxProps } from '@mui/material'
import { useNavigate } from '@tanstack/react-router'
import type { JSX } from 'react'
import { useTranslation } from 'react-i18next'
import { useSplitMode } from '../../stores/navigationTabs/useNavigationTabsStore.js'
import { useSettingsStore } from '../../stores/settings/SettingsStore.js'
import { useSettings } from '../../stores/settings/useSettings.js'
import { navigationRoutes } from '../../utils/navigationRoutes.js'
import { Card } from '../Card/Card.js'
import {
QuickSettingButton,
QuickSettingsContainer,
QuickSettingTitle,
QuickSettingValue,
} from './QuickSettings.style.js'
import {
bridgeQuickSettings,
type QuickSettingKey,
quickSettingsConfig,
swapQuickSettings,
} from './quickSettingsRows.js'

export const QuickSettings: React.FC<BoxProps> = (props): JSX.Element => {
const { t } = useTranslation()
const navigate = useNavigate()
const splitMode = useSplitMode()
const { slippage, routePriority } = useSettings(['slippage', 'routePriority'])
const [enabledBridges, totalBridges, enabledExchanges, totalExchanges] =
useSettingsStore((state) => [
Object.values(state._enabledBridges).filter(Boolean).length,
Object.values(state._enabledBridges).length,
Object.values(state._enabledExchanges).filter(Boolean).length,
Object.values(state._enabledExchanges).length,
])

const rows = splitMode === 'bridge' ? bridgeQuickSettings : swapQuickSettings

const valueByKey: Record<QuickSettingKey, string> = {
routePriority: t(
`settings.routePriorityOptions.${(routePriority ?? 'CHEAPEST').toLowerCase()}.title` as any
),
exchanges: `${enabledExchanges}/${totalExchanges}`,
bridges: `${enabledBridges}/${totalBridges}`,
slippage: slippage ? `${slippage}%` : t('button.auto'),
}

const handleClick = (key: QuickSettingKey) => {
const { route } = quickSettingsConfig[key]
navigate({
to: `/${navigationRoutes.settings}/${navigationRoutes[route]}`,
})
}

return (
<QuickSettingsContainer {...props}>
{rows.map((key) => {
const { labelKey } = quickSettingsConfig[key]
return (
<Card key={key}>
<QuickSettingButton onClick={() => handleClick(key)} disableRipple>
<QuickSettingTitle>{t(labelKey as any)}</QuickSettingTitle>
<QuickSettingValue>{valueByKey[key]}</QuickSettingValue>
</QuickSettingButton>
</Card>
)
})}
</QuickSettingsContainer>
)
}
50 changes: 50 additions & 0 deletions packages/widget/src/components/QuickSettings/quickSettingsRows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { NavigationTabKey } from '../../types/widget.js'

export type QuickSettingKey =
| 'routePriority'
| 'exchanges'
| 'bridges'
| 'slippage'

/** Navigation tabs that surface quick settings on the main page. */
export const advancedNavigationTabKeys: NavigationTabKey[] = [
'swap-advanced',
'bridge-advanced',
]

interface QuickSettingConfig {
/** i18n key for the row label. */
labelKey: string
/** Dedicated settings sub-route to navigate to. */
route: 'bridges' | 'exchanges' | 'routePriority' | 'slippage'
}

export const quickSettingsConfig: Record<QuickSettingKey, QuickSettingConfig> =
{
routePriority: {
labelKey: 'settings.routePriority',
route: 'routePriority',
},
exchanges: {
labelKey: 'settings.enabledExchanges',
route: 'exchanges',
},
bridges: {
labelKey: 'settings.enabledBridges',
route: 'bridges',
},
slippage: {
labelKey: 'settings.slippage',
route: 'slippage',
},
}

// Swap is same-chain, so bridges are not relevant.
export const swapQuickSettings: QuickSettingKey[] = ['exchanges', 'slippage']

export const bridgeQuickSettings: QuickSettingKey[] = [
'routePriority',
'exchanges',
'bridges',
'slippage',
]
15 changes: 15 additions & 0 deletions packages/widget/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,22 @@
"title": "Gas price"
},
"routePriority": "Route priority",
"routePriorityOptions": {
"recommended": {
"title": "Best Return",
"description": "Maximum tokens after all fees"
},
"fastest": {
"title": "Fastest",
"description": "Shortest execution time"
},
"cheapest": {
"title": "Cheapest",
"description": "Lowest network cost"
}
},
"slippage": "Max. slippage",
"slippageAutoDescription": "Optimised per route",
"custom": "Custom",
"resetSettings": "You're using custom settings that can affect the number or sorting of available routes.",
"hideSmallBalances": "Hide small balances"
Expand Down
8 changes: 8 additions & 0 deletions packages/widget/src/pages/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { ContractComponent } from '../../components/ContractComponent/ContractCo
import { GasRefuelMessage } from '../../components/Messages/GasRefuelMessage.js'
import { PageContainer } from '../../components/PageContainer.js'
import { PoweredBy } from '../../components/PoweredBy/PoweredBy.js'
import { QuickSettings } from '../../components/QuickSettings/QuickSettings.js'
import { advancedNavigationTabKeys } from '../../components/QuickSettings/quickSettingsRows.js'
import { Routes } from '../../components/Routes/Routes.js'
import { SelectChainAndToken } from '../../components/SelectChainAndToken.js'
import { SendToWalletButton } from '../../components/SendToWallet/SendToWalletButton.js'
import { SendToWalletExpandButton } from '../../components/SendToWallet/SendToWalletExpandButton.js'
import { useHeader } from '../../hooks/useHeader.js'
import { useWideVariant } from '../../hooks/useWideVariant.js'
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
import { useNavigationTabsStore } from '../../stores/navigationTabs/useNavigationTabsStore.js'
import { MainWarningMessages } from './MainWarningMessages.js'
import { ReviewButton } from './ReviewButton.js'

Expand All @@ -20,6 +23,10 @@ export const MainPage: React.FC = () => {
const wideVariant = useWideVariant()
const { mode, modeOptions, contractComponent, hiddenUI } = useWidgetConfig()
const custom = mode === 'custom'
const activeTab = useNavigationTabsStore((state) => state.activeTab)
// Surface quick settings on the main page for advanced navigation tabs.
const advancedMode =
!!activeTab && advancedNavigationTabKeys.includes(activeTab)
const showPoweredBy = !hiddenUI?.poweredBy
const showGasRefuelMessage = !hiddenUI?.gasRefuelMessage

Expand Down Expand Up @@ -51,6 +58,7 @@ export const MainPage: React.FC = () => {
{!custom || modeOptions?.custom?.type === 'deposit' ? (
<AmountInput formType="from" sx={marginSx} />
) : null}
{advancedMode ? <QuickSettings sx={marginSx} /> : null}
{!wideVariant ? <Routes sx={marginSx} /> : null}
<SendToWalletButton sx={marginSx} />
{showGasRefuelMessage ? <GasRefuelMessage sx={marginSx} /> : null}
Expand Down
48 changes: 48 additions & 0 deletions packages/widget/src/pages/RoutePriorityPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Order } from '@lifi/sdk'
import Check from '@mui/icons-material/Check'
import { List } from '@mui/material'
import { useTranslation } from 'react-i18next'
import { ListItemText } from '../components/ListItemText.js'
import { PageContainer } from '../components/PageContainer.js'
import { SettingsListItemButton } from '../components/SettingsListItemButton.js'
import { useHeader } from '../hooks/useHeader.js'
import { useSettings } from '../stores/settings/useSettings.js'
import { useSettingsActions } from '../stores/settings/useSettingsActions.js'

const Priorities: Order[] = ['RECOMMENDED', 'FASTEST', 'CHEAPEST']

export const RoutePriorityPage: React.FC = () => {
const { t } = useTranslation()
const { setValue } = useSettingsActions()
const { routePriority } = useSettings(['routePriority'])

useHeader(t('settings.routePriority'))

return (
<PageContainer disableGutters>
<List
sx={{
padding: 1.5,
}}
>
{Priorities.map((priority) => {
const key = priority.toLowerCase()
return (
<SettingsListItemButton
key={priority}
onClick={() => setValue('routePriority', priority)}
>
<ListItemText
primary={t(`settings.routePriorityOptions.${key}.title` as any)}
secondary={t(
`settings.routePriorityOptions.${key}.description` as any
)}
/>
{routePriority === priority && <Check color="primary" />}
</SettingsListItemButton>
)
})}
</List>
</PageContainer>
)
}
Loading
Loading