From 14cf8cfa4c7e71457b8a4984169996c816e5c6a7 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Tue, 16 Jun 2026 10:24:19 +0200 Subject: [PATCH 1/3] feat(widget): surface route settings on main page with dedicated pages --- packages/widget/src/AppDefault.tsx | 24 +++ .../QuickSettings/QuickSettings.style.tsx | 41 +++++ .../QuickSettings/QuickSettings.tsx | 74 ++++++++ .../QuickSettings/quickSettingsRows.ts | 59 +++++++ packages/widget/src/i18n/en.json | 26 +++ .../widget/src/pages/MainPage/MainPage.tsx | 8 + .../widget/src/pages/RoutePriorityPage.tsx | 48 ++++++ packages/widget/src/pages/RouteTypePage.tsx | 42 +++++ .../SettingsPage/RoutePrioritySettings.tsx | 53 ++---- .../pages/SettingsPage/RouteTypeSettings.tsx | 30 ++++ .../src/pages/SettingsPage/SettingsPage.tsx | 2 + .../SlippageSettings/SlippagePage.tsx | 160 ++++++++++++++++++ .../SlippageSettings.style.tsx | 32 +--- .../SlippageSettings/SlippageSettings.tsx | 142 ++-------------- .../stores/settings/createSettingsStore.ts | 4 +- packages/widget/src/stores/settings/types.ts | 6 + packages/widget/src/utils/navigationRoutes.ts | 9 + 17 files changed, 562 insertions(+), 198 deletions(-) create mode 100644 packages/widget/src/components/QuickSettings/QuickSettings.style.tsx create mode 100644 packages/widget/src/components/QuickSettings/QuickSettings.tsx create mode 100644 packages/widget/src/components/QuickSettings/quickSettingsRows.ts create mode 100644 packages/widget/src/pages/RoutePriorityPage.tsx create mode 100644 packages/widget/src/pages/RouteTypePage.tsx create mode 100644 packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx create mode 100644 packages/widget/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx diff --git a/packages/widget/src/AppDefault.tsx b/packages/widget/src/AppDefault.tsx index 2641289ca..c986fb07c 100644 --- a/packages/widget/src/AppDefault.tsx +++ b/packages/widget/src/AppDefault.tsx @@ -12,7 +12,9 @@ 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 { RouteTypePage } from './pages/RouteTypePage.js' import { SelectChainPage } from './pages/SelectChainPage/SelectChainPage.js' import { SelectEnabledToolsPage } from './pages/SelectEnabledToolsPage.js' import { SelectTokenPage } from './pages/SelectTokenPage/SelectTokenPage.js' @@ -22,6 +24,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' @@ -66,6 +69,24 @@ const settingsLanguagesRoute = createRoute({ component: LanguagesPage, }) +const settingsRouteTypeRoute = createRoute({ + getParentRoute: () => settingsLayoutRoute, + path: navigationRoutes.routeType, + component: RouteTypePage, +}) + +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, @@ -214,6 +235,9 @@ const routeTree = rootRoute.addChildren([ settingsLayoutRoute.addChildren([ settingsIndexRoute, settingsLanguagesRoute, + settingsRouteTypeRoute, + settingsRoutePriorityRoute, + settingsSlippageRoute, settingsBridgesRoute, settingsExchangesRoute, ]), diff --git a/packages/widget/src/components/QuickSettings/QuickSettings.style.tsx b/packages/widget/src/components/QuickSettings/QuickSettings.style.tsx new file mode 100644 index 000000000..70c8d6d15 --- /dev/null +++ b/packages/widget/src/components/QuickSettings/QuickSettings.style.tsx @@ -0,0 +1,41 @@ +import { Box, ButtonBase, styled, Typography } from '@mui/material' +import type React from 'react' + +export const QuickSettingsContainer: React.FC< + React.ComponentProps +> = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), +})) + +export const QuickSettingButton: React.FC< + React.ComponentProps +> = 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 +> = styled(Typography)(({ theme }) => ({ + fontSize: 14, + lineHeight: 1, + fontWeight: 700, + color: theme.vars.palette.text.primary, +})) + +export const QuickSettingValue: React.FC< + React.ComponentProps +> = styled(Typography)(({ theme }) => ({ + fontSize: 14, + lineHeight: 1, + fontWeight: 500, + color: theme.vars.palette.text.secondary, + whiteSpace: 'nowrap', +})) diff --git a/packages/widget/src/components/QuickSettings/QuickSettings.tsx b/packages/widget/src/components/QuickSettings/QuickSettings.tsx new file mode 100644 index 000000000..c1757ed2c --- /dev/null +++ b/packages/widget/src/components/QuickSettings/QuickSettings.tsx @@ -0,0 +1,74 @@ +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 = (props): JSX.Element => { + const { t } = useTranslation() + const navigate = useNavigate() + const splitMode = useSplitMode() + const { slippage, routePriority, routeType } = useSettings([ + 'slippage', + 'routePriority', + 'routeType', + ]) + 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 = { + routeType: t(`settings.routeType.${routeType ?? 'all'}.title`), + 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 ( + + {rows.map((key) => { + const { labelKey } = quickSettingsConfig[key] + return ( + + handleClick(key)} disableRipple> + {t(labelKey as any)} + {valueByKey[key]} + + + ) + })} + + ) +} diff --git a/packages/widget/src/components/QuickSettings/quickSettingsRows.ts b/packages/widget/src/components/QuickSettings/quickSettingsRows.ts new file mode 100644 index 000000000..471fbf30c --- /dev/null +++ b/packages/widget/src/components/QuickSettings/quickSettingsRows.ts @@ -0,0 +1,59 @@ +import type { NavigationTabKey } from '../../types/widget.js' + +export type QuickSettingKey = + | 'routeType' + | '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' | 'routeType' | 'routePriority' | 'slippage' +} + +export const quickSettingsConfig: Record = + { + routeType: { + labelKey: 'settings.routeType.title', + route: 'routeType', + }, + 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[] = [ + 'routeType', + 'exchanges', + 'slippage', +] + +export const bridgeQuickSettings: QuickSettingKey[] = [ + 'routePriority', + 'exchanges', + 'bridges', + 'slippage', +] diff --git a/packages/widget/src/i18n/en.json b/packages/widget/src/i18n/en.json index 4effbebb2..6604037e1 100644 --- a/packages/widget/src/i18n/en.json +++ b/packages/widget/src/i18n/en.json @@ -333,7 +333,33 @@ "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" + } + }, + "routeType": { + "title": "Route type", + "all": { + "title": "All", + "description": "Every available route across bridges and exchanges" + }, + "private": { + "title": "Private", + "description": "Privacy-preserving routes only" + } + }, "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" diff --git a/packages/widget/src/pages/MainPage/MainPage.tsx b/packages/widget/src/pages/MainPage/MainPage.tsx index bcf3f896f..77f16f4f4 100644 --- a/packages/widget/src/pages/MainPage/MainPage.tsx +++ b/packages/widget/src/pages/MainPage/MainPage.tsx @@ -5,6 +5,8 @@ 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' @@ -12,6 +14,7 @@ import { SendToWalletExpandButton } from '../../components/SendToWallet/SendToWa 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' @@ -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 @@ -51,6 +58,7 @@ export const MainPage: React.FC = () => { {!custom || modeOptions?.custom?.type === 'deposit' ? ( ) : null} + {advancedMode ? : null} {!wideVariant ? : null} {showGasRefuelMessage ? : null} diff --git a/packages/widget/src/pages/RoutePriorityPage.tsx b/packages/widget/src/pages/RoutePriorityPage.tsx new file mode 100644 index 000000000..5bdb3800e --- /dev/null +++ b/packages/widget/src/pages/RoutePriorityPage.tsx @@ -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 ( + + + {Priorities.map((priority) => { + const key = priority.toLowerCase() + return ( + setValue('routePriority', priority)} + > + + {routePriority === priority && } + + ) + })} + + + ) +} diff --git a/packages/widget/src/pages/RouteTypePage.tsx b/packages/widget/src/pages/RouteTypePage.tsx new file mode 100644 index 000000000..e5353db40 --- /dev/null +++ b/packages/widget/src/pages/RouteTypePage.tsx @@ -0,0 +1,42 @@ +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 { RouteTypes } from '../stores/settings/types.js' +import { useSettings } from '../stores/settings/useSettings.js' +import { useSettingsActions } from '../stores/settings/useSettingsActions.js' + +export const RouteTypePage: React.FC = () => { + const { t } = useTranslation() + const { setValue } = useSettingsActions() + const { routeType } = useSettings(['routeType']) + const currentRouteType = routeType ?? 'all' + + useHeader(t('settings.routeType.title')) + + return ( + + + {RouteTypes.map((type) => ( + setValue('routeType', type)} + > + + {currentRouteType === type && } + + ))} + + + ) +} diff --git a/packages/widget/src/pages/SettingsPage/RoutePrioritySettings.tsx b/packages/widget/src/pages/SettingsPage/RoutePrioritySettings.tsx index c372170de..3a23d713e 100644 --- a/packages/widget/src/pages/SettingsPage/RoutePrioritySettings.tsx +++ b/packages/widget/src/pages/SettingsPage/RoutePrioritySettings.tsx @@ -1,57 +1,34 @@ -import type { Order } from '@lifi/sdk' import Route from '@mui/icons-material/Route' +import { useNavigate } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' -import { CardTabs, Tab } from '../../components/Tabs/Tabs.style.js' +import { CardButton } from '../../components/Card/CardButton.js' import { useSettingMonitor } from '../../hooks/useSettingMonitor.js' import { useSettings } from '../../stores/settings/useSettings.js' -import { useSettingsActions } from '../../stores/settings/useSettingsActions.js' +import { navigationRoutes } from '../../utils/navigationRoutes.js' import { BadgedValue } from './SettingsCard/BadgedValue.js' -import { SettingCardExpandable } from './SettingsCard/SettingCardExpandable.js' - -const Priorities: Order[] = ['CHEAPEST', 'FASTEST'] export const RoutePrioritySettings: React.FC = () => { const { t } = useTranslation() - const { setValue } = useSettingsActions() + const navigate = useNavigate() const { isRoutePriorityChanged } = useSettingMonitor() const { routePriority } = useSettings(['routePriority']) - const currentRoutePriority = routePriority ?? '' + const currentRoutePriority = (routePriority ?? 'CHEAPEST').toLowerCase() - const handleRoutePriorityChange = ( - _: React.SyntheticEvent, - routePriority: Order - ) => { - setValue('routePriority', routePriority) + const handleClick = () => { + navigate({ to: navigationRoutes.routePriority }) } return ( - - {t(`main.tags.${currentRoutePriority.toLowerCase()}` as any)} - - } + } title={t('settings.routePriority')} > - - {Priorities.map((priority) => { - return ( - - ) - })} - - + + {t( + `settings.routePriorityOptions.${currentRoutePriority}.title` as any + )} + + ) } diff --git a/packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx b/packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx new file mode 100644 index 000000000..1db464c79 --- /dev/null +++ b/packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx @@ -0,0 +1,30 @@ +import AltRoute from '@mui/icons-material/AltRoute' +import { useNavigate } from '@tanstack/react-router' +import { useTranslation } from 'react-i18next' +import { CardButton } from '../../components/Card/CardButton.js' +import { useSettings } from '../../stores/settings/useSettings.js' +import { navigationRoutes } from '../../utils/navigationRoutes.js' +import { BadgedValue } from './SettingsCard/BadgedValue.js' + +export const RouteTypeSettings: React.FC = () => { + const { t } = useTranslation() + const navigate = useNavigate() + const { routeType } = useSettings(['routeType']) + const currentRouteType = routeType ?? 'all' + + const handleClick = () => { + navigate({ to: navigationRoutes.routeType }) + } + + return ( + } + title={t('settings.routeType.title')} + > + + {t(`settings.routeType.${currentRouteType}.title`)} + + + ) +} diff --git a/packages/widget/src/pages/SettingsPage/SettingsPage.tsx b/packages/widget/src/pages/SettingsPage/SettingsPage.tsx index e83931b25..8f67897f8 100644 --- a/packages/widget/src/pages/SettingsPage/SettingsPage.tsx +++ b/packages/widget/src/pages/SettingsPage/SettingsPage.tsx @@ -8,6 +8,7 @@ import { GasPriceSettings } from './GasPriceSettings.js' import { LanguageSetting } from './LanguageSetting.js' import { ResetSettingsButton } from './ResetSettingsButton.js' import { RoutePrioritySettings } from './RoutePrioritySettings.js' +import { RouteTypeSettings } from './RouteTypeSettings.js' import { SettingsList } from './SettingsCard/SettingCard.style.js' import { SettingsCardAccordion } from './SettingsCard/SettingsAccordian.js' import { SlippageSettings } from './SlippageSettings/SlippageSettings.js' @@ -26,6 +27,7 @@ export const SettingsPage = (): JSX.Element => { + {!hiddenUI?.hideSmallBalances && } diff --git a/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx b/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx new file mode 100644 index 000000000..266660b1b --- /dev/null +++ b/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx @@ -0,0 +1,160 @@ +import Check from '@mui/icons-material/Check' +import WarningRounded from '@mui/icons-material/WarningRounded' +import { Box, List, Typography } from '@mui/material' +import type { ChangeEventHandler, FocusEventHandler } from 'react' +import { useRef, useState } from 'react' +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 { useSettingMonitor } from '../../../hooks/useSettingMonitor.js' +import { defaultSlippage } from '../../../stores/settings/createSettingsStore.js' +import { useSettings } from '../../../stores/settings/useSettings.js' +import { useSettingsActions } from '../../../stores/settings/useSettingsActions.js' +import { formatInputAmount, formatSlippage } from '../../../utils/format.js' +import { + SettingsCustomInput, + SettingsFieldSet, + SlippageLimitsWarningContainer, +} from './SlippageSettings.style.js' + +const slippagePresets = ['0.5', '1', '3'] +const maxFractionDigits = 5 + +export const SlippagePage: React.FC = () => { + const { t } = useTranslation() + const { + isSlippageNotRecommended, + isSlippageUnderRecommendedLimits, + isSlippageOutsideRecommendedLimits, + } = useSettingMonitor() + const { slippage } = useSettings(['slippage']) + const { setValue } = useSettingsActions() + const defaultValue = useRef(slippage) + + useHeader(t('settings.slippage')) + + const isAuto = !slippage || slippage === defaultSlippage + const isPreset = !!slippage && slippagePresets.includes(slippage) + const isCustom = !isAuto && !isPreset + + const [customMode, setCustomMode] = useState(isCustom) + const [inputValue, setInputValue] = useState(isCustom ? slippage : '') + + const handleAuto = () => { + setCustomMode(false) + setValue('slippage', defaultSlippage) + } + + const handlePreset = (preset: string) => { + setCustomMode(false) + setValue('slippage', preset) + } + + const handleCustom = () => { + setCustomMode(true) + if (inputValue) { + setValue('slippage', inputValue) + } + } + + const formatAndSetSlippage = (value: string, returnInitial = false) => { + const formattedSlippage = formatSlippage( + value, + defaultValue.current, + returnInitial + ) + const formattedValue = + Number(formattedSlippage) === 0 && !returnInitial + ? '0' + : formatInputAmount(formattedSlippage, maxFractionDigits, returnInitial) + const maxLength = + Number(formattedValue) < 10 + ? maxFractionDigits + 2 + : maxFractionDigits + 3 + const slicedValue = formattedValue.slice(0, maxLength) + setInputValue(slicedValue) + setValue('slippage', slicedValue.length ? slicedValue : defaultSlippage) + } + + const handleInputUpdate: ChangeEventHandler = (event) => { + formatAndSetSlippage(event.target.value, true) + } + + const handleInputFocus: FocusEventHandler = (event) => { + formatAndSetSlippage(event.target.value) + } + + const handleInputBlur: FocusEventHandler = (event) => { + formatAndSetSlippage(event.target.value) + } + + const slippageWarningText = isSlippageOutsideRecommendedLimits + ? t('warning.message.slippageOutsideRecommendedLimits') + : isSlippageUnderRecommendedLimits + ? t('warning.message.slippageUnderRecommendedLimits') + : '' + + return ( + + + + + {isAuto && } + + {slippagePresets.map((preset) => ( + handlePreset(preset)} + > + + {slippage === preset && } + + ))} + + + {isCustom && } + + + {customMode && ( + + + + + + )} + {isSlippageNotRecommended && ( + + + + {slippageWarningText} + + + )} + + ) +} diff --git a/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx b/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx index df20b480f..a72cf3a55 100644 --- a/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +++ b/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx @@ -1,11 +1,5 @@ import type { Theme } from '@mui/material' -import { - Box, - ButtonBase, - InputBase, - inputBaseClasses, - styled, -} from '@mui/material' +import { Box, InputBase, inputBaseClasses, styled } from '@mui/material' import type React from 'react' export const SettingsFieldSet: React.FC< @@ -36,30 +30,6 @@ interface SettingsControlProps { selected?: boolean } -export const SettingsDefaultButton: React.FC< - React.ComponentProps & SettingsControlProps -> = styled(ButtonBase)(({ theme, selected }) => { - const settingsControlSelectedStyles = settingsControlSelected(theme) - const selectedStyle = selected - ? { - '&:not(:focus)': { - ...settingsControlSelectedStyles, - }, - } - : {} - - return { - height: '100%', - width: '100%', - fontSize: '1rem', - fontWeight: 700, - '&:focus': { - ...settingsControlSelectedStyles, - }, - ...selectedStyle, - } -}) - export const SettingsCustomInput: React.FC< React.ComponentProps & SettingsControlProps > = styled(InputBase)(({ theme, selected }) => { diff --git a/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx b/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx index c865fcf73..dcec82c82 100644 --- a/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +++ b/packages/widget/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx @@ -1,82 +1,17 @@ import Percent from '@mui/icons-material/Percent' -import WarningRounded from '@mui/icons-material/WarningRounded' -import { Box, Typography } from '@mui/material' -import type { ChangeEventHandler, FocusEventHandler } from 'react' -import { useRef, useState } from 'react' +import { useNavigate } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' +import { CardButton } from '../../../components/Card/CardButton.js' import { useSettingMonitor } from '../../../hooks/useSettingMonitor.js' -import { defaultSlippage } from '../../../stores/settings/createSettingsStore.js' import { useSettings } from '../../../stores/settings/useSettings.js' -import { useSettingsActions } from '../../../stores/settings/useSettingsActions.js' -import { formatInputAmount, formatSlippage } from '../../../utils/format.js' +import { navigationRoutes } from '../../../utils/navigationRoutes.js' import { BadgedValue } from '../SettingsCard/BadgedValue.js' -import { SettingCardExpandable } from '../SettingsCard/SettingCardExpandable.js' -import { - SettingsCustomInput, - SettingsDefaultButton, - SettingsFieldSet, - SlippageLimitsWarningContainer, -} from './SlippageSettings.style.js' - -const defaultSlippageInputValue = '0.5' -const maxFractionDigits = 5 export const SlippageSettings: React.FC = () => { const { t } = useTranslation() - const { - isSlippageNotRecommended, - isSlippageUnderRecommendedLimits, - isSlippageOutsideRecommendedLimits, - isSlippageChanged, - } = useSettingMonitor() + const navigate = useNavigate() + const { isSlippageNotRecommended, isSlippageChanged } = useSettingMonitor() const { slippage } = useSettings(['slippage']) - const { setValue } = useSettingsActions() - const defaultValue = useRef(slippage) - const [focused, setFocused] = useState<'input' | 'button'>() - - const customInputValue = - !slippage || slippage === defaultSlippage - ? defaultSlippageInputValue - : slippage - - const [inputValue, setInputValue] = useState(customInputValue) - - const handleDefaultClick = () => { - setValue('slippage', defaultSlippage) - } - - const formatAndSetSlippage = (value: string, returnInitial = false) => { - const formattedSlippage = formatSlippage( - value, - defaultValue.current, - returnInitial - ) - const formattedValue = - Number(formattedSlippage) === 0 && !returnInitial - ? '0' - : formatInputAmount(formattedSlippage, maxFractionDigits, returnInitial) - const maxLength = - Number(formattedValue) < 10 - ? maxFractionDigits + 2 - : maxFractionDigits + 3 - const slicedValue = formattedValue.slice(0, maxLength) - setInputValue(slicedValue) - setValue('slippage', slicedValue.length ? slicedValue : defaultSlippage) - } - - const handleInputUpdate: ChangeEventHandler = (event) => { - formatAndSetSlippage(event.target.value, true) - } - - const handleInputFocus: FocusEventHandler = (event) => { - setFocused('input') - formatAndSetSlippage(event.target.value) - } - - const handleInputBlur: FocusEventHandler = (event) => { - setFocused(undefined) - formatAndSetSlippage(event.target.value) - } const badgeColor = isSlippageNotRecommended ? 'warning' @@ -84,68 +19,19 @@ export const SlippageSettings: React.FC = () => { ? 'info' : undefined - const slippageWarningText = isSlippageOutsideRecommendedLimits - ? t('warning.message.slippageOutsideRecommendedLimits') - : isSlippageUnderRecommendedLimits - ? t('warning.message.slippageUnderRecommendedLimits') - : '' + const handleClick = () => { + navigate({ to: navigationRoutes.slippage }) + } return ( - - {slippage ? `${slippage}%` : t('button.auto')} - - } + } title={t('settings.slippage')} > - - - { - setFocused('button') - }} - onBlur={() => { - setFocused(undefined) - }} - onClick={handleDefaultClick} - disableRipple - > - {t('button.auto')} - - - - {isSlippageNotRecommended && ( - - - - {slippageWarningText} - - - )} - - + + {slippage ? `${slippage}%` : t('button.auto')} + + ) } diff --git a/packages/widget/src/stores/settings/createSettingsStore.ts b/packages/widget/src/stores/settings/createSettingsStore.ts index ae1b0ced3..1c8a2c75f 100644 --- a/packages/widget/src/stores/settings/createSettingsStore.ts +++ b/packages/widget/src/stores/settings/createSettingsStore.ts @@ -15,15 +15,17 @@ export const defaultSlippage = undefined export const defaultConfigurableSettings: Pick< SettingsState, - 'routePriority' | 'slippage' | 'gasPrice' + 'routePriority' | 'slippage' | 'gasPrice' | 'routeType' > = { routePriority: 'CHEAPEST', slippage: defaultSlippage, gasPrice: 'normal', + routeType: 'all', } const defaultSettings: SettingsProps = { gasPrice: 'normal', + routeType: 'all', enabledAutoRefuel: true, disabledBridges: [], disabledExchanges: [], diff --git a/packages/widget/src/stores/settings/types.ts b/packages/widget/src/stores/settings/types.ts index fd45a6067..0e173ea06 100644 --- a/packages/widget/src/stores/settings/types.ts +++ b/packages/widget/src/stores/settings/types.ts @@ -12,12 +12,18 @@ type ValueGetter = (key: K) => S[K] export const SettingsToolTypes = ['Bridges', 'Exchanges'] as const export type SettingsToolType = (typeof SettingsToolTypes)[number] +export const RouteTypes = ['all', 'private'] as const +export type RouteType = (typeof RouteTypes)[number] + export interface SettingsProps { gasPrice?: string language?: string languageCache?: LanguageResource lastDefaultLanguage?: string routePriority?: Order + // TODO: display-only for now — the SDK has no route-type/privacy parameter, + // so this setting does not yet affect routing. Wire into useRoutes once supported. + routeType?: RouteType enabledAutoRefuel: boolean slippage?: string disabledBridges: string[] diff --git a/packages/widget/src/utils/navigationRoutes.ts b/packages/widget/src/utils/navigationRoutes.ts index f8be09230..e448b73d9 100644 --- a/packages/widget/src/utils/navigationRoutes.ts +++ b/packages/widget/src/utils/navigationRoutes.ts @@ -8,6 +8,9 @@ export const navigationRoutes = { languages: 'languages', routes: 'routes', settings: 'settings', + routeType: 'route-type', + routePriority: 'route-priority', + slippage: 'slippage', toChain: 'to-chain', toToken: 'to-token', toTokenNative: 'to-token-native', @@ -30,6 +33,9 @@ export const stickyHeaderRoutes: string[] = [ navigationRoutes.home, navigationRoutes.routes, navigationRoutes.settings, + navigationRoutes.routeType, + navigationRoutes.routePriority, + navigationRoutes.slippage, navigationRoutes.toChain, navigationRoutes.toTokenNative, navigationRoutes.transactionDetails, @@ -50,6 +56,9 @@ export const backButtonRoutes: string[] = [ navigationRoutes.fromToken, navigationRoutes.routes, navigationRoutes.settings, + navigationRoutes.routeType, + navigationRoutes.routePriority, + navigationRoutes.slippage, navigationRoutes.toChain, navigationRoutes.toToken, navigationRoutes.toTokenNative, From 638d3608ac2b0fa735188c27cbe8b3a3835df424 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Tue, 23 Jun 2026 18:17:43 +0200 Subject: [PATCH 2/3] chore(widget): remove route type setting (always all for now) --- packages/widget/src/AppDefault.tsx | 8 ---- .../QuickSettings/QuickSettings.tsx | 7 +--- .../QuickSettings/quickSettingsRows.ts | 13 +----- packages/widget/src/i18n/en.json | 11 ----- packages/widget/src/pages/RouteTypePage.tsx | 42 ------------------- .../pages/SettingsPage/RouteTypeSettings.tsx | 30 ------------- .../src/pages/SettingsPage/SettingsPage.tsx | 2 - .../stores/settings/createSettingsStore.ts | 4 +- packages/widget/src/stores/settings/types.ts | 6 --- packages/widget/src/utils/navigationRoutes.ts | 3 -- 10 files changed, 4 insertions(+), 122 deletions(-) delete mode 100644 packages/widget/src/pages/RouteTypePage.tsx delete mode 100644 packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx diff --git a/packages/widget/src/AppDefault.tsx b/packages/widget/src/AppDefault.tsx index c986fb07c..11918cee0 100644 --- a/packages/widget/src/AppDefault.tsx +++ b/packages/widget/src/AppDefault.tsx @@ -14,7 +14,6 @@ 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 { RouteTypePage } from './pages/RouteTypePage.js' import { SelectChainPage } from './pages/SelectChainPage/SelectChainPage.js' import { SelectEnabledToolsPage } from './pages/SelectEnabledToolsPage.js' import { SelectTokenPage } from './pages/SelectTokenPage/SelectTokenPage.js' @@ -69,12 +68,6 @@ const settingsLanguagesRoute = createRoute({ component: LanguagesPage, }) -const settingsRouteTypeRoute = createRoute({ - getParentRoute: () => settingsLayoutRoute, - path: navigationRoutes.routeType, - component: RouteTypePage, -}) - const settingsRoutePriorityRoute = createRoute({ getParentRoute: () => settingsLayoutRoute, path: navigationRoutes.routePriority, @@ -235,7 +228,6 @@ const routeTree = rootRoute.addChildren([ settingsLayoutRoute.addChildren([ settingsIndexRoute, settingsLanguagesRoute, - settingsRouteTypeRoute, settingsRoutePriorityRoute, settingsSlippageRoute, settingsBridgesRoute, diff --git a/packages/widget/src/components/QuickSettings/QuickSettings.tsx b/packages/widget/src/components/QuickSettings/QuickSettings.tsx index c1757ed2c..06aa643cb 100644 --- a/packages/widget/src/components/QuickSettings/QuickSettings.tsx +++ b/packages/widget/src/components/QuickSettings/QuickSettings.tsx @@ -24,11 +24,7 @@ export const QuickSettings: React.FC = (props): JSX.Element => { const { t } = useTranslation() const navigate = useNavigate() const splitMode = useSplitMode() - const { slippage, routePriority, routeType } = useSettings([ - 'slippage', - 'routePriority', - 'routeType', - ]) + const { slippage, routePriority } = useSettings(['slippage', 'routePriority']) const [enabledBridges, totalBridges, enabledExchanges, totalExchanges] = useSettingsStore((state) => [ Object.values(state._enabledBridges).filter(Boolean).length, @@ -40,7 +36,6 @@ export const QuickSettings: React.FC = (props): JSX.Element => { const rows = splitMode === 'bridge' ? bridgeQuickSettings : swapQuickSettings const valueByKey: Record = { - routeType: t(`settings.routeType.${routeType ?? 'all'}.title`), routePriority: t( `settings.routePriorityOptions.${(routePriority ?? 'CHEAPEST').toLowerCase()}.title` as any ), diff --git a/packages/widget/src/components/QuickSettings/quickSettingsRows.ts b/packages/widget/src/components/QuickSettings/quickSettingsRows.ts index 471fbf30c..fe8fad96a 100644 --- a/packages/widget/src/components/QuickSettings/quickSettingsRows.ts +++ b/packages/widget/src/components/QuickSettings/quickSettingsRows.ts @@ -1,7 +1,6 @@ import type { NavigationTabKey } from '../../types/widget.js' export type QuickSettingKey = - | 'routeType' | 'routePriority' | 'exchanges' | 'bridges' @@ -17,15 +16,11 @@ interface QuickSettingConfig { /** i18n key for the row label. */ labelKey: string /** Dedicated settings sub-route to navigate to. */ - route: 'bridges' | 'exchanges' | 'routeType' | 'routePriority' | 'slippage' + route: 'bridges' | 'exchanges' | 'routePriority' | 'slippage' } export const quickSettingsConfig: Record = { - routeType: { - labelKey: 'settings.routeType.title', - route: 'routeType', - }, routePriority: { labelKey: 'settings.routePriority', route: 'routePriority', @@ -45,11 +40,7 @@ export const quickSettingsConfig: Record = } // Swap is same-chain, so bridges are not relevant. -export const swapQuickSettings: QuickSettingKey[] = [ - 'routeType', - 'exchanges', - 'slippage', -] +export const swapQuickSettings: QuickSettingKey[] = ['exchanges', 'slippage'] export const bridgeQuickSettings: QuickSettingKey[] = [ 'routePriority', diff --git a/packages/widget/src/i18n/en.json b/packages/widget/src/i18n/en.json index 6604037e1..ebfb84204 100644 --- a/packages/widget/src/i18n/en.json +++ b/packages/widget/src/i18n/en.json @@ -347,17 +347,6 @@ "description": "Lowest network cost" } }, - "routeType": { - "title": "Route type", - "all": { - "title": "All", - "description": "Every available route across bridges and exchanges" - }, - "private": { - "title": "Private", - "description": "Privacy-preserving routes only" - } - }, "slippage": "Max. slippage", "slippageAutoDescription": "Optimised per route", "custom": "Custom", diff --git a/packages/widget/src/pages/RouteTypePage.tsx b/packages/widget/src/pages/RouteTypePage.tsx deleted file mode 100644 index e5353db40..000000000 --- a/packages/widget/src/pages/RouteTypePage.tsx +++ /dev/null @@ -1,42 +0,0 @@ -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 { RouteTypes } from '../stores/settings/types.js' -import { useSettings } from '../stores/settings/useSettings.js' -import { useSettingsActions } from '../stores/settings/useSettingsActions.js' - -export const RouteTypePage: React.FC = () => { - const { t } = useTranslation() - const { setValue } = useSettingsActions() - const { routeType } = useSettings(['routeType']) - const currentRouteType = routeType ?? 'all' - - useHeader(t('settings.routeType.title')) - - return ( - - - {RouteTypes.map((type) => ( - setValue('routeType', type)} - > - - {currentRouteType === type && } - - ))} - - - ) -} diff --git a/packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx b/packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx deleted file mode 100644 index 1db464c79..000000000 --- a/packages/widget/src/pages/SettingsPage/RouteTypeSettings.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import AltRoute from '@mui/icons-material/AltRoute' -import { useNavigate } from '@tanstack/react-router' -import { useTranslation } from 'react-i18next' -import { CardButton } from '../../components/Card/CardButton.js' -import { useSettings } from '../../stores/settings/useSettings.js' -import { navigationRoutes } from '../../utils/navigationRoutes.js' -import { BadgedValue } from './SettingsCard/BadgedValue.js' - -export const RouteTypeSettings: React.FC = () => { - const { t } = useTranslation() - const navigate = useNavigate() - const { routeType } = useSettings(['routeType']) - const currentRouteType = routeType ?? 'all' - - const handleClick = () => { - navigate({ to: navigationRoutes.routeType }) - } - - return ( - } - title={t('settings.routeType.title')} - > - - {t(`settings.routeType.${currentRouteType}.title`)} - - - ) -} diff --git a/packages/widget/src/pages/SettingsPage/SettingsPage.tsx b/packages/widget/src/pages/SettingsPage/SettingsPage.tsx index 8f67897f8..e83931b25 100644 --- a/packages/widget/src/pages/SettingsPage/SettingsPage.tsx +++ b/packages/widget/src/pages/SettingsPage/SettingsPage.tsx @@ -8,7 +8,6 @@ import { GasPriceSettings } from './GasPriceSettings.js' import { LanguageSetting } from './LanguageSetting.js' import { ResetSettingsButton } from './ResetSettingsButton.js' import { RoutePrioritySettings } from './RoutePrioritySettings.js' -import { RouteTypeSettings } from './RouteTypeSettings.js' import { SettingsList } from './SettingsCard/SettingCard.style.js' import { SettingsCardAccordion } from './SettingsCard/SettingsAccordian.js' import { SlippageSettings } from './SlippageSettings/SlippageSettings.js' @@ -27,7 +26,6 @@ export const SettingsPage = (): JSX.Element => { - {!hiddenUI?.hideSmallBalances && } diff --git a/packages/widget/src/stores/settings/createSettingsStore.ts b/packages/widget/src/stores/settings/createSettingsStore.ts index 1c8a2c75f..ae1b0ced3 100644 --- a/packages/widget/src/stores/settings/createSettingsStore.ts +++ b/packages/widget/src/stores/settings/createSettingsStore.ts @@ -15,17 +15,15 @@ export const defaultSlippage = undefined export const defaultConfigurableSettings: Pick< SettingsState, - 'routePriority' | 'slippage' | 'gasPrice' | 'routeType' + 'routePriority' | 'slippage' | 'gasPrice' > = { routePriority: 'CHEAPEST', slippage: defaultSlippage, gasPrice: 'normal', - routeType: 'all', } const defaultSettings: SettingsProps = { gasPrice: 'normal', - routeType: 'all', enabledAutoRefuel: true, disabledBridges: [], disabledExchanges: [], diff --git a/packages/widget/src/stores/settings/types.ts b/packages/widget/src/stores/settings/types.ts index 0e173ea06..fd45a6067 100644 --- a/packages/widget/src/stores/settings/types.ts +++ b/packages/widget/src/stores/settings/types.ts @@ -12,18 +12,12 @@ type ValueGetter = (key: K) => S[K] export const SettingsToolTypes = ['Bridges', 'Exchanges'] as const export type SettingsToolType = (typeof SettingsToolTypes)[number] -export const RouteTypes = ['all', 'private'] as const -export type RouteType = (typeof RouteTypes)[number] - export interface SettingsProps { gasPrice?: string language?: string languageCache?: LanguageResource lastDefaultLanguage?: string routePriority?: Order - // TODO: display-only for now — the SDK has no route-type/privacy parameter, - // so this setting does not yet affect routing. Wire into useRoutes once supported. - routeType?: RouteType enabledAutoRefuel: boolean slippage?: string disabledBridges: string[] diff --git a/packages/widget/src/utils/navigationRoutes.ts b/packages/widget/src/utils/navigationRoutes.ts index e448b73d9..5c45fe533 100644 --- a/packages/widget/src/utils/navigationRoutes.ts +++ b/packages/widget/src/utils/navigationRoutes.ts @@ -8,7 +8,6 @@ export const navigationRoutes = { languages: 'languages', routes: 'routes', settings: 'settings', - routeType: 'route-type', routePriority: 'route-priority', slippage: 'slippage', toChain: 'to-chain', @@ -33,7 +32,6 @@ export const stickyHeaderRoutes: string[] = [ navigationRoutes.home, navigationRoutes.routes, navigationRoutes.settings, - navigationRoutes.routeType, navigationRoutes.routePriority, navigationRoutes.slippage, navigationRoutes.toChain, @@ -56,7 +54,6 @@ export const backButtonRoutes: string[] = [ navigationRoutes.fromToken, navigationRoutes.routes, navigationRoutes.settings, - navigationRoutes.routeType, navigationRoutes.routePriority, navigationRoutes.slippage, navigationRoutes.toChain, From 8309cc606b6d47c9a46b482d2715c43952b99a32 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Tue, 23 Jun 2026 18:20:40 +0200 Subject: [PATCH 3/3] test(e2e): update route priority test for dedicated-page navigation --- e2e/tests/playground/settings.developer-controls.spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/e2e/tests/playground/settings.developer-controls.spec.ts b/e2e/tests/playground/settings.developer-controls.spec.ts index c8542e7e8..744ec90d7 100644 --- a/e2e/tests/playground/settings.developer-controls.spec.ts +++ b/e2e/tests/playground/settings.developer-controls.spec.ts @@ -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 () => {