ci(release): publish latest release

parent d00aa5dd
* @uniswap/web-admins
IPFS hash of the deployment:
- CIDv0: `QmSen79xDfziDqosVLQFWT5F1cxK7pYHirdnMMkCjZqtDz`
- CIDv1: `bafybeicacoiqphc6t7aqqtkawiwotwqxwxqdtapccc4iy64xx3ompux7du`
We are back with some new updates! Here’s the latest:
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
Manage dapp connections - Users can now see all dapps they’re connected to, and disconnect to one or all of them.
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeicacoiqphc6t7aqqtkawiwotwqxwxqdtapccc4iy64xx3ompux7du.ipfs.dweb.link/
- https://bafybeicacoiqphc6t7aqqtkawiwotwqxwxqdtapccc4iy64xx3ompux7du.ipfs.cf-ipfs.com/
- [ipfs://QmSen79xDfziDqosVLQFWT5F1cxK7pYHirdnMMkCjZqtDz/](ipfs://QmSen79xDfziDqosVLQFWT5F1cxK7pYHirdnMMkCjZqtDz/)
### 5.48.1 (2024-09-24)
Report Spam NFTs - You can now report spam NFTs and hide them from your feed and activity.
Other changes:
- Added explainers for hidden tokens, popular tokens, and hidden NFTs
- Removed activity feed items related to any hidden NFTs
- Various bug fixes and performance improvements
\ No newline at end of file
web/5.48.1
\ No newline at end of file
extension/1.6.0
\ No newline at end of file
......@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux'
import { EditLabelModal } from 'src/app/features/accounts/EditLabelModal'
import { removeAllDappConnectionsForAccount } from 'src/app/features/dapp/actions'
import { ContextMenu, Flex, MenuContentItem, Text, TouchableArea } from 'ui/src'
import { CopySheets, Edit, Ellipsis, TrashFilled } from 'ui/src/components/icons'
import { CopySheets, Edit, TrashFilled, TripleDots } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal'
import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types'
......@@ -179,7 +179,7 @@ export function AccountItem({ address, onAccountSelect, balanceUSD }: AccountIte
opacity={0}
p="$spacing4"
>
<Ellipsis color="$neutral2" size="$icon.16" />
<TripleDots color="$neutral2" size="$icon.16" />
</Flex>
</ContextMenu>
</Flex>
......
import { cloneDeep } from '@apollo/client/utilities'
import EventEmitter from 'eventemitter3'
import { getOrderedConnectedAddresses, isConnectedAccount } from 'src/app/features/dapp/utils'
import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains'
......@@ -224,28 +223,31 @@ function removeAccountDappConnections(account: Account): void {
* @returns the updated state
*/
function removeDappConnectionHelper(initialState: DappState, dappUrl: string, account?: Account): DappState {
const newState = cloneDeep(initialState)
const dappInfo = newState[dappUrl]
const dappUrlState = initialState[dappUrl]
if (!dappInfo) {
if (!dappUrlState) {
return initialState
}
dappInfo.connectedAccounts = dappInfo.connectedAccounts.filter(
(existingAccount) => existingAccount.address !== account?.address,
)
const nextConnectedAccount = dappInfo.connectedAccounts[0]
if (!nextConnectedAccount || !account) {
delete newState[dappUrl]
return newState
}
if (dappInfo.activeConnectedAddress === account.address) {
dappInfo.activeConnectedAddress = nextConnectedAccount.address
const updatedAccounts = account
? dappUrlState.connectedAccounts?.filter((existingAccount) => existingAccount.address !== account.address)
: []
const activeConnected = updatedAccounts[0]
if (activeConnected) {
return {
...initialState,
[dappUrl]: {
...dappUrlState,
connectedAccounts: updatedAccounts,
activeConnectedAddress: activeConnected.address,
},
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [dappUrl]: _, ...restState } = initialState
return restState
}
return newState
}
function removeAllDappConnections(): void {
......
......@@ -132,7 +132,7 @@ export function ViewMnemonic(): JSX.Element {
) : (
<Flex gap="$spacing16" my="$spacing24" pt="$spacing8" width="100%">
<MnemonicViewer mnemonic={onboardingAccountMnemonic} />
<Flex backgroundColor="$surface2" borderRadius="$rounded16" p="$spacing12">
<Flex backgroundColor="$surface2" borderRadius="$rounded16" p="$spacing12" overflow="hidden">
<LabeledCheckbox
checked={disclaimerChecked}
text={<Text variant="body3">{t('onboarding.backup.view.disclaimer')}</Text>}
......
......@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { removeAllDappConnectionsForAccount } from 'src/app/features/dapp/actions'
import { ContextMenu, Flex, TouchableArea } from 'ui/src'
import { Ellipsis, Power } from 'ui/src/components/icons'
import { Power, TripleDots } from 'ui/src/components/icons'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { pushNotification } from 'wallet/src/features/notifications/slice'
......@@ -49,7 +49,7 @@ export function EllipsisDropdown(): JSX.Element {
onLeftClick={true}
>
<TouchableArea borderRadius="$roundedFull" hoverStyle={{ backgroundColor: '$surface2Hovered' }} p="$spacing8">
<Ellipsis color="$neutral2" size="$icon.16" />
<TripleDots color="$neutral2" size="$icon.16" />
</TouchableArea>
</ContextMenu>
)
......
......@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "Uniswap Extension",
"description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.",
"version": "1.7.0",
"version": "1.6.0",
"minimum_chrome_version": "116",
"icons": {
"16": "assets/icon16.png",
......
......@@ -90,9 +90,9 @@ if (isCI && datadogPropertiesAvailable && !isDetox) {
apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle"
}
def devVersionName = "1.37"
def betaVersionName = "1.37"
def prodVersionName = "1.37"
def devVersionName = "1.36"
def betaVersionName = "1.36"
def prodVersionName = "1.36"
android {
ndkVersion rootProject.ext.ndkVersion
......
......@@ -2167,7 +2167,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2220,7 +2220,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
......@@ -2273,7 +2273,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
......@@ -2326,7 +2326,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
......@@ -2364,7 +2364,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2400,7 +2400,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
......@@ -2435,7 +2435,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
......@@ -2470,7 +2470,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
......@@ -2517,7 +2517,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2563,7 +2563,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
......@@ -2609,7 +2609,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
......@@ -2655,7 +2655,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
......@@ -2697,7 +2697,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2740,7 +2740,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
......@@ -2783,7 +2783,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
......@@ -2826,7 +2826,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
......@@ -2862,7 +2862,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -2900,7 +2900,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3078,7 +3078,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -3122,7 +3122,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
......@@ -3222,7 +3222,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3293,7 +3293,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
......@@ -3393,7 +3393,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3464,7 +3464,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.37;
MARKETING_VERSION = 1.36;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
......@@ -22,7 +22,7 @@ public class DataQueries {
let tokens = graphQLResult.data?.tokens ?? []
let tokenResponses = tokens.map {
let symbol = $0?.symbol
let name = $0?.name
let name = $0?.project?.name
let chain = $0?.chain
let address = $0?.address
return TokenResponse(chain: chain?.rawValue ?? "", address: address, symbol: symbol ?? "", name: name ?? "")
......@@ -43,7 +43,7 @@ public class DataQueries {
let topTokens = graphQLResult.data?.topTokens ?? []
let tokenResponses = topTokens.map { (tokenData) -> TokenResponse in
let symbol = tokenData?.symbol
let name = tokenData?.name
let name = tokenData?.project?.name
let chain = tokenData?.chain
let address = tokenData?.address
return TokenResponse(chain: chain?.rawValue ?? "", address: address, symbol: symbol ?? "", name: name ?? "")
......@@ -63,7 +63,7 @@ public class DataQueries {
case .success(let graphQLResult):
let token = graphQLResult.data?.token
let symbol = token?.symbol
let name = token?.name
let name = token?.project?.name
let logoUrl = token?.project?.logoUrl ?? nil
let markets = token?.project?.markets
let spotPrice = (markets != nil) && !markets!.isEmpty ? markets?[0]?.price?.value : nil
......@@ -109,7 +109,7 @@ public class DataQueries {
$0?.tokenBalances?.forEach { tokenBalance in
let value = tokenBalance?.denominatedValue?.value
let token = tokenBalance?.token
let tokenResponse = TokenResponse(chain: token?.chain.rawValue ?? "", address: token?.address, symbol: token?.symbol ?? "", name: token?.name ?? "")
let tokenResponse = TokenResponse(chain: token?.chain.rawValue ?? "", address: token?.address, symbol: token?.symbol ?? "", name: token?.project?.name ?? "")
let isSpam = token?.project?.isSpam ?? false
if (!isSpam) {
tokens[tokenResponse] = (tokens[tokenResponse] ?? 0) + (value ?? 0)
......
......@@ -122,7 +122,7 @@ exports[`PriceText renders without error 1`] = `
jestAnimatedStyle={
{
"value": {
"color": "#BFBFBF",
"color": "#CECECE",
"fontSize": 106,
},
}
......@@ -130,7 +130,7 @@ exports[`PriceText renders without error 1`] = `
maxFontSizeMultiplier={1.2}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 106,
"fontWeight": "400",
......
......@@ -27,7 +27,7 @@ export function TokenDetailsHeader({
<Flex gap="$spacing12" mx="$spacing16">
<TokenLogo
chainId={fromGraphQLChain(token?.chain) ?? undefined}
name={token?.name ?? undefined}
name={token?.project?.name ?? undefined}
symbol={token?.symbol ?? undefined}
url={tokenProject?.logoUrl ?? undefined}
/>
......@@ -40,7 +40,7 @@ export function TokenDetailsHeader({
testID={TestID.TokenDetailsHeaderText}
variant="subheading1"
>
{token?.name ?? ''}
{tokenProject?.name ?? ''}
</Text>
{/* Suppress warning icon on low warning level */}
{(tokenProject?.safetyLevel === SafetyLevel.StrongWarning ||
......
......@@ -349,7 +349,7 @@ exports[`AccountHeader renders correctly 1`] = `
jestAnimatedStyle={
{
"value": {
"color": "#BFBFBF",
"color": "#CECECE",
"fontSize": 19,
"fontWeight": "400",
"lineHeight": 24,
......@@ -360,7 +360,7 @@ exports[`AccountHeader renders correctly 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -415,7 +415,7 @@ exports[`AccountHeader renders correctly 1`] = `
numberOfLines={1}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 17,
"fontWeight": "400",
......@@ -443,7 +443,7 @@ exports[`AccountHeader renders correctly 1`] = `
"borderWidth": 0,
},
{
"color": "#BFBFBF",
"color": "#CECECE",
"height": 16,
"width": 16,
},
......@@ -454,7 +454,7 @@ exports[`AccountHeader renders correctly 1`] = `
},
]
}
tintColor="#BFBFBF"
tintColor="#CECECE"
vbHeight={16}
vbWidth={16}
>
......
......@@ -224,8 +224,8 @@ function gqlTokenToTokenItemData(
return null
}
const { name, symbol, address, chain, project, market } = token
const { logoUrl, markets } = project
const { symbol, address, chain, project, market } = token
const { logoUrl, markets, name } = project
const tokenProjectMarket = markets?.[0]
const chainId = fromGraphQLChain(chain)
......
import { default as React } from 'react'
import { useTranslation } from 'react-i18next'
import { Flex, Text, TouchableArea } from 'ui/src'
import { Ellipsis } from 'ui/src/components/icons'
import { TripleDots } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { TestID } from 'uniswap/src/test/fixtures/testIDs'
......@@ -24,7 +24,7 @@ export function FavoriteHeaderRow({
</Text>
{!isEditing ? (
<TouchableArea hapticFeedback hitSlop={16} testID={TestID.Edit} onPress={onPress}>
<Ellipsis color="$neutral2" size={iconSizes.icon20} strokeLinecap="round" strokeWidth={1} />
<TripleDots color="$neutral2" size={iconSizes.icon20} strokeLinecap="round" strokeWidth={1} />
</TouchableArea>
) : (
<TouchableArea hitSlop={16} onPress={onPress}>
......
......@@ -124,7 +124,7 @@ function FavoriteTokenCard({
<Flex grow row alignItems="center" gap="$spacing8">
<TokenLogo
chainId={chainId ?? undefined}
name={token?.name ?? undefined}
name={token?.project?.name ?? undefined}
size={imageSizes.image20}
symbol={token?.symbol ?? undefined}
url={token?.project?.logoUrl ?? undefined}
......
......@@ -136,11 +136,12 @@ exports[`FavoriteHeaderRow when not editing renders without error 1`] = `
align="xMidYMid"
bbHeight={20}
bbWidth={20}
fill="none"
fill="currentColor"
focusable={false}
meetOrSlice={0}
minX={0}
minY={0}
stroke="currentColor"
strokeLinecap="round"
strokeWidth={1}
style={
......@@ -166,19 +167,29 @@ exports[`FavoriteHeaderRow when not editing renders without error 1`] = `
vbWidth={18}
>
<RNSVGGroup
fill={null}
fill={
{
"type": 2,
}
}
propList={
[
"fill",
"stroke",
"strokeWidth",
"strokeLinecap",
]
}
stroke={
{
"type": 2,
}
}
strokeLinecap={1}
strokeWidth={1}
>
<RNSVGPath
d="M9 3C9.55228 3 10 2.55228 10 2C10 1.44772 9.55228 1 9 1C8.44772 1 8 1.44772 8 2C8 2.55228 8.44772 3 9 3Z"
d="M9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
fill={
{
"payload": 4278190080,
......@@ -203,7 +214,7 @@ exports[`FavoriteHeaderRow when not editing renders without error 1`] = `
strokeWidth="2"
/>
<RNSVGPath
d="M16 3C16.5523 3 17 2.55228 17 2C17 1.44772 16.5523 1 16 1C15.4477 1 15 1.44772 15 2C15 2.55228 15.4477 3 16 3Z"
d="M16 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
fill={
{
"payload": 4278190080,
......@@ -228,7 +239,7 @@ exports[`FavoriteHeaderRow when not editing renders without error 1`] = `
strokeWidth="2"
/>
<RNSVGPath
d="M2 3C2.55228 3 3 2.55228 3 2C3 1.44772 2.55228 1 2 1C1.44772 1 1 1.44772 1 2C1 2.55228 1.44772 3 2 3Z"
d="M2 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
fill={
{
"payload": 4278190080,
......
......@@ -255,7 +255,7 @@ exports[`FavoriteWalletCard renders without error 1`] = `
style={
{
"alignItems": "center",
"backgroundColor": "#BFBFBF",
"backgroundColor": "#CECECE",
"borderBottomLeftRadius": 999999,
"borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999,
......
......@@ -25,7 +25,7 @@ exports[`RemoveButton renders without error 1`] = `
style={
{
"alignItems": "center",
"backgroundColor": "#BFBFBF",
"backgroundColor": "#CECECE",
"borderBottomLeftRadius": 999999,
"borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999,
......
......@@ -95,35 +95,14 @@ exports[`TokenItem renders without error 1`] = `
"flexDirection": "column",
"height": 40,
"justifyContent": "center",
"position": "relative",
"width": 40,
}
}
testID="token-logo"
>
<View
style={
{
"backgroundColor": "#FFFFFF",
"borderBottomLeftRadius": 20,
"borderBottomRightRadius": 20,
"borderTopLeftRadius": 20,
"borderTopRightRadius": 20,
"flexDirection": "column",
"height": "96%",
"left": "2%",
"opacity": 0,
"position": "absolute",
"top": "2%",
"width": "96%",
"zIndex": -1,
}
}
/>
<Image
height={40}
onError={[Function]}
onLoad={[Function]}
source={
{
"uri": "https://loremflickr.com/640/480",
......@@ -132,6 +111,7 @@ exports[`TokenItem renders without error 1`] = `
style={
{
"aspectRatio": undefined,
"backgroundColor": "#FFFFFF",
"borderRadius": 20,
"flex": undefined,
}
......
......@@ -5,9 +5,9 @@ query SearchPopularTokens {
chain
symbol
decimals
name
project {
id
name
logoUrl
safetyLevel
}
......
......@@ -13,7 +13,8 @@ function gqlTokenToTokenSearchResult(token: Maybe<TopToken>): TokenSearchResult
return null
}
const { name, chain, address, symbol, project, protectionInfo } = token
const { chain, address, symbol, project, protectionInfo } = token
const { name } = project
const chainId = fromGraphQLChain(chain)
if (!chainId || !symbol || !name) {
return null
......
......@@ -7,7 +7,15 @@ import {
import { Chain, ExploreSearchQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'uniswap/src/features/chains/utils'
import { SearchResultType } from 'uniswap/src/features/search/SearchResult'
import { amount, ethToken, nftCollection, nftContract, token, tokenMarket } from 'uniswap/src/test/fixtures'
import {
amount,
ethToken,
nftCollection,
nftContract,
token,
tokenMarket,
tokenProject,
} from 'uniswap/src/test/fixtures'
import { createArray } from 'uniswap/src/test/utils'
type ExploreSearchResult = NonNullable<ExploreSearchQuery>
......@@ -54,8 +62,8 @@ describe(formatTokenSearchResults, () => {
it('sorts results by best search query match', () => {
const data: ExploreSearchResult['searchTokens'] = [
token({ name: 'UniswapStartingName' }),
token({ name: 'Uniswap' }),
ethToken({ project: tokenProject({ name: 'UniswapStartingName' }) }),
ethToken({ project: tokenProject({ name: 'Uniswap' }) }),
]
const result = formatTokenSearchResults(data, 'uniswap')
......@@ -75,7 +83,7 @@ describe(formatTokenSearchResults, () => {
expect(result?.[0]?.type).toEqual(SearchResultType.Token)
expect(result?.[0]?.chainId).toEqual(fromGraphQLChain(searchToken.chain))
expect(result?.[0]?.address).toEqual(searchToken.address)
expect(result?.[0]?.name).toEqual(searchToken.name)
expect(result?.[0]?.name).toEqual(searchToken.project?.name)
expect(result?.[0]?.symbol).toEqual(searchToken.symbol)
expect(result?.[0]?.logoUrl).toEqual(searchToken.project?.logoUrl)
expect(result?.[0]?.safetyLevel).toEqual(searchToken.project?.safetyLevel)
......
......@@ -31,14 +31,14 @@ export function formatTokenSearchResults(
return tokensMap
}
const { name, chain, address, symbol, project, market, protectionInfo } = token
const { chain, address, symbol, project, market, protectionInfo } = token
const chainId = fromGraphQLChain(chain)
if (!chainId || !project) {
return tokensMap
}
const { safetyLevel, logoUrl } = project
const { name, safetyLevel, logoUrl } = project
const tokenResult: TokenSearchResult & { volume1D: number } = {
type: SearchResultType.Token,
......
......@@ -188,8 +188,8 @@ function gqlTokenToTokenItemData(
return null
}
const { name, symbol, address, chain, project } = token
const { logoUrl, markets } = project
const { symbol, address, chain, project } = token
const { logoUrl, markets, name } = project
const tokenProjectMarket = markets?.[0]
const chainId = fromGraphQLChain(chain)
......
......@@ -21,7 +21,7 @@ exports[`renders a DecimalNumber 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -57,7 +57,7 @@ exports[`renders a DecimalNumber without a comma separator 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......
......@@ -46,7 +46,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -62,7 +62,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -158,7 +158,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -174,7 +174,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -190,7 +190,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -206,7 +206,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -222,7 +222,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -238,7 +238,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -254,7 +254,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -270,7 +270,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -286,7 +286,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -302,7 +302,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -318,7 +318,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -334,7 +334,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......@@ -350,7 +350,7 @@ exports[`renders text with few matches 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 19,
"fontWeight": "400",
......
......@@ -62,10 +62,10 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
onChangeText={[Function]}
onFocus={[Function]}
onSubmitEditing={[Function]}
placeholderTextColor="#BFBFBF"
placeholderTextColor="#CECECE"
returnKeyType="done"
scrollEnabled={false}
selectionColor="#BFBFBF"
selectionColor="#CECECE"
spellCheck={false}
style={
{
......
......@@ -14,7 +14,7 @@ import { DeleteUnitagModal } from 'src/components/unitags/DeleteUnitagModal'
import { UnitagProfilePicture } from 'src/components/unitags/UnitagProfilePicture'
import { HeaderRadial, solidHeaderProps } from 'src/features/externalProfile/ProfileHeader'
import { Button, Flex, LinearGradient, ScrollView, Text, getUniconColors, useIsDarkMode, useSporeColors } from 'ui/src'
import { Ellipsis, Pen } from 'ui/src/components/icons'
import { Pen, TripleDots } from 'ui/src/components/icons'
import { borderRadii, fonts, iconSizes, imageSizes, spacing } from 'ui/src/theme'
import { useExtractedColors } from 'ui/src/utils/colors'
import { TextInput } from 'uniswap/src/components/input/TextInput'
......@@ -271,7 +271,7 @@ export function EditUnitagProfileScreen({ route }: UnitagStackScreenProp<UnitagS
}}
>
<Flex pr="$spacing8">
<Ellipsis color="$neutral2" size={iconSizes.icon24} />
<TripleDots color="$neutral2" size={iconSizes.icon24} />
</Flex>
</ContextMenu>
) : undefined
......
......@@ -274,10 +274,10 @@ exports[`RestoreCloudBackupPasswordScreen renders correctly 1`] = `
onFocus={[Function]}
onSubmitEditing={[Function]}
placeholder="Enter password"
placeholderTextColor="#BFBFBF"
placeholderTextColor="#CECECE"
returnKeyType="done"
secureTextEntry={true}
selectionColor="#BFBFBF"
selectionColor="#CECECE"
style={
{
"backgroundColor": "transparent",
......
......@@ -737,7 +737,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = `
"borderWidth": 0,
},
{
"color": "#BFBFBF",
"color": "#CECECE",
"height": 20,
"width": 20,
},
......@@ -748,7 +748,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = `
},
]
}
tintColor="#BFBFBF"
tintColor="#CECECE"
vbHeight={24}
vbWidth={24}
>
......@@ -782,7 +782,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 17,
"fontWeight": "400",
......@@ -1539,7 +1539,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = `
"borderWidth": 0,
},
{
"color": "#BFBFBF",
"color": "#CECECE",
"height": 20,
"width": 20,
},
......@@ -1550,7 +1550,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = `
},
]
}
tintColor="#BFBFBF"
tintColor="#CECECE"
vbHeight={24}
vbWidth={24}
>
......@@ -1584,7 +1584,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = `
maxFontSizeMultiplier={1.4}
style={
{
"color": "#BFBFBF",
"color": "#CECECE",
"fontFamily": "Basel Grotesk",
"fontSize": 17,
"fontWeight": "400",
......
......@@ -141,9 +141,9 @@ export function TokenDetailsScreen({ route }: AppStackScreenProp<MobileScreens.T
() => ({
address: currencyIdToAddress(_currencyId),
chain: currencyIdToChain(_currencyId),
currencyName: data?.token?.name,
currencyName: data?.token?.project?.name,
}),
[_currencyId, data?.token?.name],
[_currencyId, data?.token?.project?.name],
)
return (
......@@ -186,7 +186,7 @@ function TokenDetails({
const token = data?.token
const tokenLogoUrl = token?.project?.logoUrl
const tokenSymbol = token?.name
const tokenSymbol = token?.project?.name
const currencyInfo = useCurrencyInfo(_currencyId)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -106,13 +106,13 @@ function TokenRow({
element={InterfaceElementName.MINI_PORTFOLIO_TOKEN_ROW}
properties={{
chain_id: currency.chainId,
token_name: token?.name ?? token?.project?.name,
token_name: token?.project?.name ?? token?.name,
address: token?.address,
}}
>
<PortfolioRow
left={<PortfolioLogo chainId={currency.chainId} currencies={[currency]} size={40} />}
title={<TokenNameText>{token?.name ?? token?.project?.name}</TokenNameText>}
title={<TokenNameText>{token?.project?.name ?? token?.name}</TokenNameText>}
descriptor={
<TokenBalanceText>
{formatNumber({
......
// eslint-disable-next-line no-restricted-imports
import { Position } from '@uniswap/client-pools/dist/pools/v1/types_pb'
import { BadgeData, LiquidityPositionInfoBadges } from 'components/Liquidity/LiquidityPositionInfoBadges'
import { LiquidityPositionInfoBadges } from 'components/Liquidity/LiquidityPositionInfoBadges'
import { LiquidityPositionStatusIndicator } from 'components/Liquidity/LiquidityPositionStatusIndicator'
import { getProtocolVersionLabel, usePositionInfo } from 'components/Liquidity/utils'
import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo'
import { Flex, Text } from 'ui/src'
import { DocumentList } from 'ui/src/components/icons/DocumentList'
interface LiquidityPositionInfoProps {
position: Position
......@@ -33,15 +32,7 @@ export function LiquidityPositionInfo({ position }: LiquidityPositionInfoProps)
<Flex row gap={2} alignItems="center">
<LiquidityPositionInfoBadges
size="small"
badges={
[
versionLabel ? { label: versionLabel } : undefined,
v4hook
? { label: v4hook, copyable: true, icon: <DocumentList color="$neutral2" size={16} /> }
: undefined,
feeTier ? { label: `${Number(feeTier) / 10000}%` } : undefined,
].filter(Boolean) as BadgeData[]
}
labels={[versionLabel, v4hook, feeTier].filter(Boolean) as string[]}
/>
</Flex>
</Flex>
......
import { LiquidityPositionInfoBadges } from 'components/Liquidity/LiquidityPositionInfoBadges'
import { render } from 'test-utils/render'
const testBadgeData = [{ label: 'test', copyable: true }, { label: 'test2' }]
describe('LiquidityPositionInfoBadges', () => {
it('should render with default size', () => {
const { getByText } = render(<LiquidityPositionInfoBadges badges={testBadgeData} size="default" />)
const { getByText } = render(<LiquidityPositionInfoBadges labels={['test']} size="default" />)
expect(getByText('test')).toBeInTheDocument()
})
it('should render with small size', () => {
const { getByText } = render(<LiquidityPositionInfoBadges badges={testBadgeData} size="small" />)
const { getByText } = render(<LiquidityPositionInfoBadges labels={['test']} size="small" />)
expect(getByText('test')).toBeInTheDocument()
})
it('should render with multiple badges', () => {
const { getByText } = render(<LiquidityPositionInfoBadges badges={testBadgeData} size="default" />)
it('should render with multiple labels', () => {
const { getByText } = render(<LiquidityPositionInfoBadges labels={['test', 'test2']} size="default" />)
expect(getByText('test')).toBeInTheDocument()
expect(getByText('test2')).toBeInTheDocument()
})
......
import { CopyHelper } from 'theme/components'
import { styled, Text } from 'ui/src'
import { isAddress, shortenAddress } from 'utilities/src/addresses'
const PositionInfoBadge = styled(Text, {
display: 'flex',
flexDirection: 'row',
gap: '$spacing2',
variant: 'body3',
color: '$neutral2',
backgroundColor: '$surface3',
......@@ -41,41 +36,20 @@ function getPlacement(index: number, length: number): 'start' | 'middle' | 'end'
return length === 1 ? 'only' : index === 0 ? 'start' : index === length - 1 ? 'end' : 'middle'
}
export interface BadgeData {
label: string
copyable?: boolean
icon?: JSX.Element
}
export function LiquidityPositionInfoBadges({
badges,
labels,
size = 'default',
}: {
badges: BadgeData[]
labels: string[]
size: 'small' | 'default'
}): JSX.Element {
return (
<>
{badges.map(({ label, copyable, icon }, index) => {
const displayLabel = isAddress(label) ? shortenAddress(label) : label
return (
<PositionInfoBadge
cursor={copyable ? 'pointer' : 'unset'}
key={label + index}
placement={getPlacement(index, badges.length)}
size={size}
>
{icon}
{copyable ? (
<CopyHelper toCopy={label} iconSize={12} iconPosition="right">
{displayLabel}
</CopyHelper>
) : (
displayLabel
)}
</PositionInfoBadge>
)
})}
{labels.map((label, index) => (
<PositionInfoBadge key={label + index} placement={getPlacement(index, labels.length)} size={size}>
{label}
</PositionInfoBadge>
))}
</>
)
}
......@@ -11,6 +11,7 @@ import { usePool } from 'hooks/usePools'
import { useMemo } from 'react'
import { useAppSelector } from 'state/hooks'
import { AppTFunction } from 'ui/src/i18n/types'
import { shortenAddress } from 'utilities/src/addresses'
export function getProtocolVersionLabel(version: ProtocolVersion): string | undefined {
switch (version) {
......@@ -120,7 +121,7 @@ export function usePositionInfo(position?: Position): PositionInfo | undefined {
status: position.status,
feeTier: undefined,
version: position.protocolVersion,
v4hook: v4Position.hooks[0]?.address,
v4hook: v4Position.hooks[0]?.address ? shortenAddress(v4Position.hooks[0].address) : undefined,
restPosition: position,
currency0Amount: CurrencyAmount.fromRawAmount(
token0,
......
......@@ -8,6 +8,7 @@ const NavDropdownContent = styled(Flex, {
borderStyle: 'solid',
borderColor: '$surface2',
backgroundColor: '$surface1',
overflow: 'scroll',
maxHeight: `calc(100dvh - ${NAV_HEIGHT * 2}px)`,
$sm: {
width: '100%',
......@@ -16,10 +17,6 @@ const NavDropdownContent = styled(Flex, {
shadowColor: '$transparent',
maxHeight: `calc(100dvh - ${NAV_HEIGHT}px)`,
},
'$platform-web': {
overflowY: 'auto',
overflowX: 'hidden',
},
})
interface NavDropdownProps {
......
......@@ -11,7 +11,7 @@ import { getTokenDetailsURL, supportedChainIdFromGQLChain } from 'graphql/data/u
import styled, { css } from 'lib/styled-components'
import { searchGenieCollectionToTokenSearchResult, searchTokenToTokenSearchResult } from 'lib/utils/searchBar'
import { GenieCollection } from 'nft/types'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import { Link, useNavigate } from 'react-router-dom'
import { EllipsisStyle, ThemedText } from 'theme/components'
......@@ -23,7 +23,6 @@ import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { InterfaceSearchResultSelectionProperties } from 'uniswap/src/features/telemetry/types'
import { Trans, useTranslation } from 'uniswap/src/i18n'
import { UniverseChainId } from 'uniswap/src/types/chains'
import { shortenAddress } from 'uniswap/src/utils/addresses'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const PriceChangeContainer = styled.div`
......@@ -148,14 +147,6 @@ export function SuggestionRow({
}
}, [toggleOpen, isHovered, suggestion, navigate, handleClick, path])
const shortenedAddress = useMemo<string | null>(() => {
if (isToken && suggestion.address && suggestion.address !== NATIVE_CHAIN_ID) {
return shortenAddress(suggestion.address)
}
return null
}, [suggestion, isToken])
return (
<StyledLink
to={path}
......@@ -187,20 +178,13 @@ export function SuggestionRow({
<PrimaryText lineHeight="24px">{suggestion.name}</PrimaryText>
{isToken ? <TokenSafetyIcon warning={warning} /> : suggestion.isVerified && <Verified size={14} />}
</Flex>
<Flex row gap="$spacing4">
<ThemedText.SubHeaderSmall lineHeight="20px">
{isToken
? suggestion.symbol
: t('search.results.count', {
count: suggestion?.stats?.total_supply ?? 0,
})}
</ThemedText.SubHeaderSmall>
{shortenedAddress && (
<ThemedText.SubHeaderSmall lineHeight="20px" color="neutral3">
{shortenedAddress}
</ThemedText.SubHeaderSmall>
)}
</Flex>
<ThemedText.SubHeaderSmall lineHeight="20px">
{isToken
? suggestion.symbol
: t('search.results.count', {
count: suggestion?.stats?.total_supply ?? 0,
})}
</ThemedText.SubHeaderSmall>
</PrimaryContainer>
</Flex>
......
......@@ -228,7 +228,7 @@ exports[`disable nft on searchbar dropdown should not render popular nft collect
<svg
fill="none"
stroke-width="8"
style="color: rgb(191, 191, 191); width: 16px; height: 16px;"
style="color: rgb(206, 206, 206); width: 16px; height: 16px;"
viewBox="0 0 16 16"
>
<path
......@@ -612,7 +612,7 @@ exports[`disable nft on searchbar dropdown should render popular nft collections
<svg
fill="none"
stroke-width="8"
style="color: rgb(191, 191, 191); width: 16px; height: 16px;"
style="color: rgb(206, 206, 206); width: 16px; height: 16px;"
viewBox="0 0 16 16"
>
<path
......@@ -807,7 +807,7 @@ exports[`disable nft on searchbar dropdown should render popular nft collections
<svg
fill="none"
stroke-width="8"
style="color: rgb(191, 191, 191); width: 16px; height: 16px;"
style="color: rgb(206, 206, 206); width: 16px; height: 16px;"
viewBox="0 0 16 16"
>
<path
......
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName, useExperimentGroupNameWithLoading } from 'uniswap/src/features/gating/hooks'
import { Trans } from 'uniswap/src/i18n'
import { useExperimentGroupNameWithLoading } from 'uniswap/src/features/gating/hooks'
export function useIsAccountCTAExperimentControl() {
const { value: experimentGroupName, isLoading } = useExperimentGroupNameWithLoading(Experiments.AccountCTAs)
......@@ -9,17 +8,3 @@ export function useIsAccountCTAExperimentControl() {
isLoading,
}
}
export function ConnectWalletButtonText(): JSX.Element {
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
return isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)
}
......@@ -76,7 +76,7 @@ function TokenDescription({ token }: { token: TopToken | TokenStat }) {
return (
<Flex row gap="$gap8">
<QueryTokenLogo token={token} size={28} />
<EllipsisText data-testid="token-name">{token?.name ?? token?.project?.name}</EllipsisText>
<EllipsisText data-testid="token-name">{token?.project?.name ?? token?.name}</EllipsisText>
<TokenTableText
$platform-web={{
minWidth: 'fit-content',
......
import { QueryClientProvider } from '@tanstack/react-query'
import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
import { recentConnectorIdAtom } from 'components/Web3Provider/constants'
import { queryClient, wagmiConfig } from 'components/Web3Provider/wagmiConfig'
import { queryClient, wagmiConfig } from 'components/Web3Provider/wagmi'
import { walletTypeToAmplitudeWalletType } from 'components/Web3Provider/walletConnect'
import { useIsSupportedChainId } from 'constants/chains'
import { RPC_PROVIDERS } from 'constants/providers'
......
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { PositionInfo, useModalLiquidityPositionInfo } from 'components/Liquidity/utils'
import { Field } from 'components/addLiquidity/InputForm'
import { useDerivedAddLiquidityInfo } from 'components/addLiquidity/hooks'
import { Dispatch, PropsWithChildren, SetStateAction, createContext, useContext, useMemo, useState } from 'react'
import { PositionField } from 'types/position'
export interface AddLiquidityState {
position?: PositionInfo
exactField: PositionField
exactField: Field
exactAmount?: string
}
const DEFAULT_ADD_LIQUIDITY_STATE = {
exactField: PositionField.TOKEN0,
exactField: Field.TOKEN0,
}
export interface AddLiquidityInfo {
formattedAmounts?: { [field in PositionField]?: string }
currencyBalances?: { [field in PositionField]?: CurrencyAmount<Currency> }
currencyAmounts?: { [field in PositionField]?: CurrencyAmount<Currency> }
currencyAmountsUSDValue?: { [field in PositionField]?: CurrencyAmount<Currency> }
formattedAmounts?: { [field in Field]?: string }
currencyBalances?: { [field in Field]?: CurrencyAmount<Currency> }
currencyAmounts?: { [field in Field]?: CurrencyAmount<Currency> }
currencyAmountsUSDValue?: { [field in Field]?: CurrencyAmount<Currency> }
}
interface AddLiquidityContextType {
......
......@@ -2,16 +2,20 @@ import { Currency } from '@uniswap/sdk-core'
import { AddLiquidityInfo } from 'components/addLiquidity/AddLiquidityContext'
import { useCurrencyInfo } from 'hooks/Tokens'
import { useState } from 'react'
import { PositionField } from 'types/position'
import { Flex } from 'ui/src'
import { CurrencyInputPanel } from 'uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel'
import { CurrencyField } from 'uniswap/src/types/currency'
export enum Field {
TOKEN0 = 'TOKEN0',
TOKEN1 = 'TOKEN1',
}
type InputFormProps = {
token0: Currency
token1: Currency
onUserInput: (field: PositionField, newValue: string) => void
onSetMax: (field: PositionField, amount: string) => void
onUserInput: (field: Field, newValue: string) => void
onSetMax: (field: Field, amount: string) => void
} & AddLiquidityInfo
export function InputForm({
......@@ -24,19 +28,19 @@ export function InputForm({
onUserInput,
onSetMax,
}: InputFormProps) {
const [focusedInputField, setFocusedInputField] = useState(PositionField.TOKEN0)
const [focusedInputField, setFocusedInputField] = useState(Field.TOKEN0)
// TODO(WEB-4920): when the backend returns the logo info make sure that there is no call being made
// to graphql to retrieve it
const token0CurrencyInfo = useCurrencyInfo(token0)
const token1CurrencyInfo = useCurrencyInfo(token1)
const handleUserInput = (field: PositionField) => {
const handleUserInput = (field: Field) => {
return (newValue: string) => {
onUserInput(field, newValue)
}
}
const handleOnSetMax = (field: PositionField) => {
const handleOnSetMax = (field: Field) => {
return (amount: string) => {
setFocusedInputField(field)
onSetMax(field, amount)
......@@ -46,37 +50,37 @@ export function InputForm({
<Flex gap="$gap4">
<CurrencyInputPanel
enableInputOnly
focus={focusedInputField === PositionField.TOKEN0}
focus={focusedInputField === Field.TOKEN0}
borderRadius="$rounded20"
backgroundColor="$surface2"
currencyInfo={token0CurrencyInfo}
currencyField={CurrencyField.INPUT}
currencyAmount={currencyAmounts?.[PositionField.TOKEN0]}
currencyBalance={currencyBalances?.[PositionField.TOKEN0]}
onSetExactAmount={handleUserInput(PositionField.TOKEN0)}
currencyAmount={currencyAmounts?.[Field.TOKEN0]}
currencyBalance={currencyBalances?.[Field.TOKEN0]}
onSetExactAmount={handleUserInput(Field.TOKEN0)}
onToggleIsFiatMode={() => undefined}
usdValue={currencyAmountsUSDValue?.[PositionField.TOKEN0]}
onSetMax={handleOnSetMax(PositionField.TOKEN0)}
value={formattedAmounts?.[PositionField.TOKEN0]}
onPressIn={() => setFocusedInputField(PositionField.TOKEN0)}
usdValue={currencyAmountsUSDValue?.[Field.TOKEN0]}
onSetMax={handleOnSetMax(Field.TOKEN0)}
value={formattedAmounts?.[Field.TOKEN0]}
onPressIn={() => setFocusedInputField(Field.TOKEN0)}
/>
<CurrencyInputPanel
enableInputOnly
focus={focusedInputField === PositionField.TOKEN1}
focus={focusedInputField === Field.TOKEN1}
py="$spacing16"
borderRadius="$rounded20"
backgroundColor="$surface2"
currencyInfo={token1CurrencyInfo}
currencyField={CurrencyField.INPUT}
currencyAmount={currencyAmounts?.[PositionField.TOKEN1]}
currencyBalance={currencyBalances?.[PositionField.TOKEN1]}
onSetExactAmount={handleUserInput(PositionField.TOKEN1)}
currencyAmount={currencyAmounts?.[Field.TOKEN1]}
currencyBalance={currencyBalances?.[Field.TOKEN1]}
onSetExactAmount={handleUserInput(Field.TOKEN1)}
onToggleIsFiatMode={() => undefined}
usdValue={currencyAmountsUSDValue?.[PositionField.TOKEN1]}
onSetMax={handleOnSetMax(PositionField.TOKEN1)}
value={formattedAmounts?.[PositionField.TOKEN1]}
onPressIn={() => setFocusedInputField(PositionField.TOKEN1)}
usdValue={currencyAmountsUSDValue?.[Field.TOKEN1]}
onSetMax={handleOnSetMax(Field.TOKEN1)}
value={formattedAmounts?.[Field.TOKEN1]}
onPressIn={() => setFocusedInputField(Field.TOKEN1)}
/>
</Flex>
)
......
// eslint-disable-next-line no-restricted-imports
import { PoolPosition } from '@uniswap/client-pools/dist/pools/v1/types_pb'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { FeeAmount, Position } from '@uniswap/v3-sdk'
import { AddLiquidityInfo, AddLiquidityState } from 'components/addLiquidity/AddLiquidityContext'
import { Field } from 'components/addLiquidity/InputForm'
import { useAccount } from 'hooks/useAccount'
import { usePool } from 'hooks/usePools'
import { useV2Pair } from 'hooks/useV2Pairs'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo } from 'react'
import { PositionField } from 'types/position'
import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice'
function parseV3FeeTier(feeTier: string | undefined): FeeAmount | undefined {
const parsedFee = parseInt(feeTier || '')
return parsedFee in FeeAmount ? parsedFee : undefined
}
export function useDerivedAddLiquidityInfo(state: AddLiquidityState): AddLiquidityInfo {
const account = useAccount()
const { position: positionInfo, exactAmount, exactField } = state
const { position, exactAmount } = state
if (!positionInfo) {
if (!position) {
throw new Error('no position available')
}
const token0 = positionInfo.currency0Amount.currency
const token1 = positionInfo.currency1Amount.currency
const token0 = position.currency0Amount.currency
const token1 = position.currency1Amount.currency
const [token0Balance, token1Balance] = useCurrencyBalances(account.address, [token0, token1])
const token0CurrencyAmount = tryParseCurrencyAmount(exactAmount, token0)
const token0USDValue = useUSDCValue(token0CurrencyAmount) || undefined
const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0]
const independentAmount = tryParseCurrencyAmount(exactAmount, independentToken)
const [, pool] = usePool(token0, token1, parseV3FeeTier(positionInfo.feeTier))
const [, pair] = useV2Pair(token0, token1)
const dependentAmount: CurrencyAmount<Currency> | undefined = useMemo(() => {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = independentAmount?.wrapped
if (positionInfo.restPosition.position.case === 'v2Pair') {
const [token0Wrapped, token1Wrapped] = [token0?.wrapped, token1?.wrapped]
if (token0Wrapped && token1Wrapped && wrappedIndependentAmount && pair) {
const dependentTokenAmount =
exactField === PositionField.TOKEN0
? pair.priceOf(token0Wrapped).quote(wrappedIndependentAmount)
: pair.priceOf(token1Wrapped).quote(wrappedIndependentAmount)
return dependentToken?.isNative
? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient)
: dependentTokenAmount
}
return undefined
}
if (positionInfo.restPosition.position.case === 'v3Position') {
const position: PoolPosition = positionInfo.restPosition.position.value
const { tickLower: tickLowerStr, tickUpper: tickUpperStr } = position
const tickLower = parseInt(tickLowerStr)
const tickUpper = parseInt(tickUpperStr)
if (
independentAmount &&
wrappedIndependentAmount &&
typeof tickLower === 'number' &&
typeof tickUpper === 'number' &&
pool
) {
const position: Position | undefined = wrappedIndependentAmount.currency.equals(pool.token0)
? Position.fromAmount0({
pool,
tickLower,
tickUpper,
amount0: independentAmount.quotient,
useFullPrecision: true, // we want full precision for the theoretical position
})
: Position.fromAmount1({
pool,
tickLower,
tickUpper,
amount1: independentAmount.quotient,
})
const dependentTokenAmount = wrappedIndependentAmount.currency.equals(pool.token0)
? position.amount1
: position.amount0
return dependentToken && CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient)
}
return undefined
}
if (positionInfo.restPosition.position.case === 'v4Position') {
// TODO: calculate for v4
return undefined
}
return undefined
}, [
dependentToken,
independentAmount,
pool,
positionInfo.restPosition.position,
exactField,
pair,
token0.wrapped,
token1.wrapped,
])
const independentTokenUSDValue = useUSDCValue(independentAmount) || undefined
const dependentTokenUSDValue = useUSDCValue(dependentAmount) || undefined
// TODO: compute the dependent value
const dependentField = exactField === PositionField.TOKEN0 ? PositionField.TOKEN1 : PositionField.TOKEN0
return {
currencyBalances: { [PositionField.TOKEN0]: token0Balance, [PositionField.TOKEN1]: token1Balance },
formattedAmounts: { [exactField]: exactAmount, [dependentField]: dependentAmount?.toExact() },
currencyAmounts: { [exactField]: independentAmount, [dependentField]: dependentAmount },
currencyAmountsUSDValue: { [exactField]: independentTokenUSDValue, [dependentField]: dependentTokenUSDValue },
formattedAmounts: { [Field.TOKEN0]: exactAmount },
currencyBalances: { [Field.TOKEN0]: token0Balance, [Field.TOKEN1]: token1Balance },
currencyAmounts: { [Field.TOKEN0]: token0CurrencyAmount },
currencyAmountsUSDValue: { [Field.TOKEN0]: token0USDValue },
}
}
......@@ -17,7 +17,7 @@ describe('Routing', () => {
})
it('contains all coins for polygon', () => {
const symbols = COMMON_BASES[UniverseChainId.Polygon].map((coin) => coin.currency.symbol)
expect(symbols).toEqual(['POL', 'WETH', 'USDC', 'DAI', 'USDT', 'WBTC'])
expect(symbols).toEqual(['MATIC', 'WETH', 'USDC', 'DAI', 'USDT', 'WBTC'])
})
it('contains all coins for celo', () => {
const symbols = COMMON_BASES[UniverseChainId.Celo].map((coin) => coin.currency.symbol)
......
......@@ -111,7 +111,7 @@ export function gqlToCurrency(token: DeepPartial<GqlToken | TokenStat>): Currenc
token.address,
token.decimals ?? 18,
token.symbol ?? undefined,
token.name ?? token.project?.name ?? undefined,
token.project?.name ?? token.name ?? undefined,
)
}
}
......
......@@ -66,7 +66,7 @@ export function getSortedPortfolioTokens(
address,
tokenBalance.token?.decimals,
tokenBalance.token?.symbol,
tokenBalance.token?.name ?? tokenBalance.token?.project?.name,
tokenBalance.token?.project?.name ?? tokenBalance.token?.name,
)
return portfolioToken
......
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { DefaultTheme } from 'lib/styled-components'
import { PriceImpact } from 'nft/hooks/usePriceImpact'
import { ReactNode } from 'react'
......@@ -59,7 +58,7 @@ export function getBuyButtonStateData(
...defaultBuyButtonState,
handleClick: handleClickOverride ?? (() => undefined),
disabled: false,
buttonText: <ConnectWalletButtonText />,
buttonText: <Trans i18nKey="common.connectWallet.button" />,
},
[BuyButtonStates.NOT_SUPPORTED_CHAIN]: {
...defaultBuyButtonState,
......
import { InterfacePageName } from '@uniswap/analytics-events'
import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks'
import { ButtonPrimary } from 'components/Button/buttons'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { useAccount } from 'hooks/useAccount'
import useENSName from 'hooks/useENSName'
import styled from 'lib/styled-components'
......@@ -118,7 +117,7 @@ export default function Profile() {
</ThemedText.HeadlineMedium>
<ConnectWalletButton onClick={accountDrawer.open}>
<ThemedText.SubHeader color="white" lineHeight="20px">
<ConnectWalletButtonText />
<Trans i18nKey="common.connectWallet.button" />
</ThemedText.SubHeader>
</ConnectWalletButton>
</Center>
......
......@@ -2,9 +2,8 @@ import { LiquidityModalDetailRows } from 'components/Liquidity/LiquidityModalDet
import { LiquidityModalHeader } from 'components/Liquidity/LiquidityModalHeader'
import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo'
import { AddLiquidityContextProvider, useAddLiquidityContext } from 'components/addLiquidity/AddLiquidityContext'
import { InputForm } from 'components/addLiquidity/InputForm'
import { Field, InputForm } from 'components/addLiquidity/InputForm'
import { useCloseModal } from 'state/application/hooks'
import { PositionField } from 'types/position'
import { Button, Flex } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
......@@ -26,7 +25,7 @@ function AddLiquidityModalInner() {
const token0 = currency0Amount.currency
const token1 = currency1Amount.currency
const handleUserInput = (field: PositionField, newValue: string) => {
const handleUserInput = (field: Field, newValue: string) => {
setAddLiquidityState((prev) => ({
...prev,
exactField: field,
......@@ -34,7 +33,7 @@ function AddLiquidityModalInner() {
}))
}
const handleOnSetMax = (field: PositionField, amount: string) => {
const handleOnSetMax = (field: Field, amount: string) => {
setAddLiquidityState((prev) => ({
...prev,
exactField: field,
......
......@@ -21,7 +21,6 @@ import CurrencyInputPanel from 'components/CurrencyInputPanel'
import FeeSelector from 'components/FeeSelector'
import HoverInlineText from 'components/HoverInlineText'
import LiquidityChartRangeInput from 'components/LiquidityChartRangeInput'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { AddRemoveTabs } from 'components/NavigationTabs'
import { PositionPreview } from 'components/PositionPreview'
import RangeSelector from 'components/RangeSelector'
......@@ -85,6 +84,8 @@ import { ThemedText } from 'theme/components'
import { Text } from 'ui/src'
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains'
import { WRAPPED_NATIVE_CURRENCY } from 'uniswap/src/constants/tokens'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans, t } from 'uniswap/src/i18n'
......@@ -542,6 +543,10 @@ function AddLiquidity() {
}, [searchParams])
// END: sync values with query string
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
const Buttons = () =>
addIsUnsupported ? (
<ButtonPrimary disabled={true} $borderRadius="12px" padding="12px">
......@@ -557,7 +562,13 @@ function AddLiquidity() {
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={accountDrawer.open} $borderRadius="12px" padding="12px">
<ConnectWalletButtonText />
{isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)}
</ButtonLight>
</Trace>
) : (
......
......@@ -13,7 +13,6 @@ import { ButtonError, ButtonLight, ButtonPrimary } from 'components/Button/butto
import { BlueCard, LightCard } from 'components/Card/cards'
import CurrencyInputPanel from 'components/CurrencyInputPanel'
import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { AddRemoveTabs } from 'components/NavigationTabs'
import { MinimalPositionCard } from 'components/PositionCard'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
......@@ -48,6 +47,8 @@ import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { ThemedText } from 'theme/components'
import { Text } from 'ui/src'
import { WRAPPED_NATIVE_CURRENCY } from 'uniswap/src/constants/tokens'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans } from 'uniswap/src/i18n'
......@@ -353,6 +354,10 @@ export default function AddLiquidity() {
const addIsUnsupported = useIsSwapUnsupported(currencies?.CURRENCY_A, currencies?.CURRENCY_B)
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
if (!networkSupportsV2) {
return <V2Unsupported />
}
......@@ -469,7 +474,13 @@ export default function AddLiquidity() {
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={accountDrawer.open}>
<ConnectWalletButtonText />
{isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)}
</ButtonLight>
</Trace>
) : (
......
import { Navigate } from 'react-router-dom'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks'
export const NewPosition = () => {
const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere)
if (!isLoading && !v4Enabled) {
return <Navigate to="/pools" replace />
}
if (isLoading) {
return null
}
return (
<div>
<h1>New Position</h1>
</div>
)
}
import { AdvancedButton } from 'pages/Pool/Positions/create/shared'
import { useState } from 'react'
import { Button } from 'ui/src'
import { DocumentList } from 'ui/src/components/icons/DocumentList'
import { X } from 'ui/src/components/icons/X'
import { Flex } from 'ui/src/components/layout/Flex'
import { fonts } from 'ui/src/theme'
import { TextInput } from 'uniswap/src/components/input/TextInput'
import { useTranslation } from 'uniswap/src/i18n'
export function AddHook() {
const { t } = useTranslation()
const [hookInputEnabled, setHookInputEnabled] = useState(false)
const [hookAddress, setHookAddress] = useState('')
const handleToggleHookInput = () => {
setHookInputEnabled((prev) => !prev)
setHookAddress('')
}
if (hookInputEnabled) {
return (
<Flex row gap="$spacing4">
<TextInput
autoFocus
placeholder="Enter hook address"
autoCapitalize="none"
color="$neutral1"
fontFamily="$subHeading"
fontSize={fonts.body2.fontSize}
fontWeight={fonts.body2.fontWeight}
lineHeight={24}
maxLength={42}
numberOfLines={1}
px="$spacing16"
py={5}
returnKeyType="done"
width="100%"
borderWidth={1.5}
borderColor="$neutral3"
borderRadius="$rounded12"
focusStyle={{
borderColor: '$neutral3',
}}
hoverStyle={{
borderColor: '$neutral3',
}}
value={hookAddress}
onChangeText={(text: string) => setHookAddress(text)}
/>
<Button theme="secondary" py="$spacing8" px="$spacing12" borderWidth={0} onPress={handleToggleHookInput}>
<X size="$icon.20" color="$neutral1" />
</Button>
</Flex>
)
}
return <AdvancedButton title={t('position.addHook')} Icon={DocumentList} onPress={handleToggleHookInput} />
}
import { PoolProgressIndicator } from 'components/PoolProgressIndicator/PoolProgressIndicator'
import {
CreatePositionContextProvider,
DEFAULT_POSITION_STATE,
DEFAULT_PRICE_RANGE_STATE,
PriceRangeContextProvider,
useCreatePositionContext,
usePriceRangeContext,
} from 'pages/Pool/Positions/create/CreatePositionContext'
import { EditRangeSelectionStep, EditSelectTokensStep } from 'pages/Pool/Positions/create/EditStep'
import { SelectPriceRangeStep } from 'pages/Pool/Positions/create/RangeSelectionStep'
import { SelectTokensStep } from 'pages/Pool/Positions/create/SelectTokenStep'
import { PositionFlowStep } from 'pages/Pool/Positions/create/types'
import { useCallback } from 'react'
import { Navigate } from 'react-router-dom'
import { Button, Flex, Text } from 'ui/src'
import { AngleRightSmall } from 'ui/src/components/icons/AngleRightSmall'
import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron'
import { RotateLeft } from 'ui/src/components/icons/RotateLeft'
import { Settings } from 'ui/src/components/icons/Settings'
import { iconSizes } from 'ui/src/theme/iconSizes'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks'
import { Trans, useTranslation } from 'uniswap/src/i18n'
function CreatePositionInner() {
const { step, setStep } = useCreatePositionContext()
const handleContinue = useCallback(() => {
setStep((prevStep) => prevStep + 1)
}, [setStep])
return (
<Flex gap="$spacing24">
{step === PositionFlowStep.SELECT_TOKENS ? (
<SelectTokensStep onContinue={handleContinue} />
) : step === PositionFlowStep.PRICE_RANGE ? (
<>
<EditSelectTokensStep />
<SelectPriceRangeStep onContinue={handleContinue} />
</>
) : (
<>
<EditSelectTokensStep />
<EditRangeSelectionStep />
</>
)}
</Flex>
)
}
const Sidebar = () => {
const { t } = useTranslation()
const { step } = useCreatePositionContext()
const PoolProgressSteps = [
{ label: t(`position.step.select`), active: step === PositionFlowStep.SELECT_TOKENS },
{ label: t(`position.step.range`), active: step === PositionFlowStep.PRICE_RANGE },
{ label: t(`position.step.deposit`), active: step == PositionFlowStep.DEPOSIT },
]
return (
<Flex gap={32} width={360}>
<Flex gap="$gap20">
<Flex row alignItems="center">
<Text variant="body3" color="$neutral2">
<Trans i18nKey="pool.positions.title" />
</Text>
<AngleRightSmall color="$neutral2" size={iconSizes.icon24} />
</Flex>
<Text variant="heading2">
<Trans i18nKey="position.new" />
</Text>
</Flex>
<PoolProgressIndicator steps={PoolProgressSteps} />
</Flex>
)
}
const Toolbar = () => {
const { setPositionState, setStep } = useCreatePositionContext()
const { setPriceRangeState } = usePriceRangeContext()
const handleReset = useCallback(() => {
setPositionState(DEFAULT_POSITION_STATE)
setPriceRangeState(DEFAULT_PRICE_RANGE_STATE)
setStep(PositionFlowStep.SELECT_TOKENS)
}, [setPositionState, setPriceRangeState, setStep])
return (
<Flex flexDirection="row-reverse" alignItems="flex-end" height={88} gap="$gap8">
<Button
theme="tertiary"
py="$spacing8"
px="$spacing12"
backgroundColor="$surface1"
borderRadius="$rounded12"
borderColor="$surface3"
borderWidth="$spacing1"
gap="$gap4"
>
<Settings size={iconSizes.icon16} color="$neutral1" />
</Button>
<Button
theme="tertiary"
py={6}
pl="$spacing12"
pr="$spacing8"
alignItems="center"
backgroundColor="$surface1"
borderRadius="$rounded12"
borderColor="$surface3"
borderWidth="$spacing1"
gap={6}
>
<Text variant="buttonLabel4">
<Trans i18nKey="position.protocol" values={{ protocol: 'v4' }} />
</Text>
<RotatableChevron direction="down" color="$neutral2" width={iconSizes.icon20} height={iconSizes.icon20} />
</Button>
<Button
theme="tertiary"
py="$spacing8"
px="$spacing12"
backgroundColor="$surface1"
borderRadius="$rounded12"
borderColor="$surface3"
borderWidth="$spacing1"
gap="$gap4"
onPress={handleReset}
>
<RotateLeft size={iconSizes.icon16} color="$neutral1" />
<Text variant="buttonLabel4">
<Trans i18nKey="common.button.reset" />
</Text>
</Button>
</Flex>
)
}
export function CreatePosition() {
const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere)
if (!isLoading && !v4Enabled) {
return <Navigate to="/pools" replace />
}
if (isLoading) {
return null
}
return (
<CreatePositionContextProvider>
<PriceRangeContextProvider>
<Flex row gap={60} justifyContent="space-around" mt="$spacing48">
<Sidebar />
<Flex gap={32} width="100%" maxWidth={580}>
<Toolbar />
<CreatePositionInner />
</Flex>
</Flex>
</PriceRangeContextProvider>
</CreatePositionContextProvider>
)
}
import { useDerivedPositionInfo } from 'pages/Pool/Positions/create/hooks'
import {
CreatePositionContextType,
PositionFlowStep,
PositionState,
PriceRangeContextType,
PriceRangeState,
} from 'pages/Pool/Positions/create/types'
import React, { useContext, useState } from 'react'
export const DEFAULT_POSITION_STATE: PositionState = {
tokenInputs: {},
fee: 3000,
hook: undefined,
}
const CreatePositionContext = React.createContext<CreatePositionContextType>({
step: PositionFlowStep.SELECT_TOKENS,
setStep: () => undefined,
positionState: DEFAULT_POSITION_STATE,
setPositionState: () => undefined,
derivedPositionInfo: {
pool: undefined,
},
})
export const useCreatePositionContext = () => {
return useContext(CreatePositionContext)
}
export function CreatePositionContextProvider({ children }: { children: React.ReactNode }) {
const [positionState, setPositionState] = useState<PositionState>(DEFAULT_POSITION_STATE)
const [step, setStep] = useState<PositionFlowStep>(PositionFlowStep.SELECT_TOKENS)
const derivedPositionInfo = useDerivedPositionInfo(positionState)
return (
<CreatePositionContext.Provider value={{ step, setStep, positionState, setPositionState, derivedPositionInfo }}>
{children}
</CreatePositionContext.Provider>
)
}
export const DEFAULT_PRICE_RANGE_STATE: PriceRangeState = {
priceInverted: false,
fullRange: true,
minPrice: '0',
maxPrice: 'INF',
}
const PriceRangeContext = React.createContext<PriceRangeContextType>({
priceRangeState: DEFAULT_PRICE_RANGE_STATE,
setPriceRangeState: () => undefined,
})
export const usePriceRangeContext = () => {
return useContext(PriceRangeContext)
}
export function PriceRangeContextProvider({ children }: { children: React.ReactNode }) {
const [priceRangeState, setPriceRangeState] = useState<PriceRangeState>(DEFAULT_PRICE_RANGE_STATE)
return (
<PriceRangeContext.Provider value={{ priceRangeState, setPriceRangeState }}>{children}</PriceRangeContext.Provider>
)
}
import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo'
import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext'
import { Container } from 'pages/Pool/Positions/create/shared'
import { PositionFlowStep } from 'pages/Pool/Positions/create/types'
import { useCallback } from 'react'
import { Button, Flex, Text } from 'ui/src'
import { Edit } from 'ui/src/components/icons/Edit'
import { iconSizes } from 'ui/src/theme'
import { Trans } from 'uniswap/src/i18n'
const EditStep = ({ children, onClick }: { children: JSX.Element; onClick: () => void }) => {
return (
<Container row justifyContent="space-between" alignItems="center" borderRadius="$rounded12">
{children}
<Button theme="secondary" py="$spacing8" px="$spacing12" gap="$gap8" height={36} onPress={onClick}>
<Edit size={iconSizes.icon20} color="$neutral1" />
<Text variant="buttonLabel3">
<Trans i18nKey="common.edit.button" />
</Text>
</Button>
</Container>
)
}
export const EditSelectTokensStep = () => {
const {
positionState: {
tokenInputs: { TOKEN0: token0, TOKEN1: token1 },
},
setStep,
} = useCreatePositionContext()
const currencies = [token0, token1]
const handleEdit = useCallback(() => {
setStep(PositionFlowStep.SELECT_TOKENS)
}, [setStep])
return (
<EditStep onClick={handleEdit}>
<Flex row py="$spacing8" gap="$gap12">
<DoubleCurrencyLogo currencies={currencies} size={iconSizes.icon32} />
<Flex row gap="$gap8">
<Text variant="heading3">{token0?.symbol}</Text>
<Text variant="heading3">/</Text>
<Text variant="heading3">{token1?.symbol}</Text>
</Flex>
</Flex>
</EditStep>
)
}
export const EditRangeSelectionStep = () => {
const {
positionState: {
tokenInputs: { TOKEN0: token0, TOKEN1: token1 },
},
setStep,
} = useCreatePositionContext()
const {
priceRangeState: { priceInverted },
} = usePriceRangeContext()
const baseCurrency = priceInverted ? token1 : token0
const quoteCurrency = priceInverted ? token0 : token1
const handleEdit = useCallback(() => {
setStep(PositionFlowStep.PRICE_RANGE)
}, [setStep])
return (
<EditStep onClick={handleEdit}>
<Flex row gap={10}>
<Text variant="subheading1" width={80}>
<Trans i18nKey="common.range" />
</Text>
<Flex gap="$gap4">
<Flex row gap={10}>
<Text variant="body2" color="$neutral2">
<Trans i18nKey="chart.price.label.low" />
</Text>
<Text variant="body2">{`283,923,000 ${baseCurrency?.symbol + '/' + quoteCurrency?.symbol}`}</Text>
</Flex>
<Flex row gap={10}>
<Text variant="body2" color="$neutral2">
<Trans i18nKey="chart.price.label.high" />
</Text>
<Text variant="body2">{`481,848,481 ${baseCurrency?.symbol + '/' + quoteCurrency?.symbol}`}</Text>
</Flex>
</Flex>
</Flex>
</EditStep>
)
}
import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext'
import { Container } from 'pages/Pool/Positions/create/shared'
import { useCallback, useMemo } from 'react'
import { Minus, Plus } from 'react-feather'
import { Button, Flex, SegmentedControl, Text, useSporeColors } from 'ui/src'
import { SwapActionButton } from 'ui/src/components/icons/SwapActionButton'
import { fonts } from 'ui/src/theme'
import { AmountInput } from 'uniswap/src/components/CurrencyInputPanel/AmountInput'
import { Trans, useTranslation } from 'uniswap/src/i18n'
enum RangeSelectionInput {
MIN,
MAX,
}
enum RangeSelection {
FULL = 'FULL',
CUSTOM = 'CUSTOM',
}
function RangeControl({ value, active }: { value: string; active: boolean }) {
return (
<Text color={active ? '$neutral1' : '$neutral2'} userSelect="none" variant="buttonLabel3">
{value}
</Text>
)
}
function RangeInput({ input }: { input: RangeSelectionInput }) {
const colors = useSporeColors()
const { t } = useTranslation()
const {
positionState: {
tokenInputs: { TOKEN0: token0, TOKEN1: token1 },
},
} = useCreatePositionContext()
const {
priceRangeState: { minPrice, maxPrice, priceInverted },
setPriceRangeState,
} = usePriceRangeContext()
const baseCurrency = priceInverted ? token1 : token0
const quoteCurrency = priceInverted ? token0 : token1
const handlePriceRangeInput = useCallback(
(input: RangeSelectionInput, value: string) => {
if (input === RangeSelectionInput.MIN) {
setPriceRangeState((prev) => ({ ...prev, minPrice: value }))
} else {
setPriceRangeState((prev) => ({ ...prev, maxPrice: value }))
}
},
[setPriceRangeState],
)
return (
<Flex
row
flex={1}
position="relative"
backgroundColor="$surface2"
borderBottomRightRadius={input === RangeSelectionInput.MIN ? '$none' : '$rounded20'}
borderBottomLeftRadius={input === RangeSelectionInput.MIN ? '$rounded20' : '$none'}
p="$spacing16"
justifyContent="space-between"
overflow="hidden"
>
<Flex gap="$gap4" overflow="hidden" flex={1}>
<Text variant="body3" color="$neutral2">
{input === RangeSelectionInput.MIN ? t(`pool.minPrice`) : t(`pool.maxPrice`)}
</Text>
<AmountInput
backgroundColor="$transparent"
borderWidth={0}
borderRadius="$none"
color="$neutral1"
fontFamily="$heading3"
fontSize={fonts.heading3.fontSize}
fontWeight={fonts.heading3.fontWeight}
maxDecimals={quoteCurrency?.decimals ?? 18}
overflow="visible"
placeholder="0"
placeholderTextColor={colors.neutral3.val}
px="$none"
py="$none"
value={input === RangeSelectionInput.MIN ? minPrice : maxPrice}
onChangeText={(text) => handlePriceRangeInput(input, text)}
/>
<Text variant="body3" color="$neutral2">
<Trans
i18nKey="common.feesEarnedPerBase"
values={{
symbolA: quoteCurrency?.symbol,
symbolB: baseCurrency?.symbol,
}}
/>
</Text>
</Flex>
<Flex gap={10}>
<Button theme="secondary" p="$spacing8" borderRadius="$roundedFull">
<Plus size="16px" color={colors.neutral1.val} />
</Button>
<Button theme="secondary" p="$spacing8" borderRadius="$roundedFull" color="$neutral1">
<Minus size="16px" color={colors.neutral1.val} />
</Button>
</Flex>
</Flex>
)
}
export const SelectPriceRangeStep = ({ onContinue }: { onContinue: () => void }) => {
const { t } = useTranslation()
const {
positionState: {
tokenInputs: { TOKEN0: token0, TOKEN1: token1 },
},
} = useCreatePositionContext()
const {
priceRangeState: { priceInverted, fullRange },
setPriceRangeState,
} = usePriceRangeContext()
const baseCurrency = priceInverted ? token1 : token0
const quoteCurrency = priceInverted ? token0 : token1
const controlOptions = useMemo(() => {
return [{ value: token0?.symbol ?? '' }, { value: token1?.symbol ?? '' }]
}, [token0?.symbol, token1?.symbol])
const handleSelectToken = useCallback(
(option: string) => {
if (option === token0?.symbol) {
setPriceRangeState((prevState) => ({ ...prevState, priceInverted: false }))
} else {
setPriceRangeState((prevState) => ({ ...prevState, priceInverted: true }))
}
},
[token0?.symbol, setPriceRangeState],
)
const handleSelectRange = useCallback(
(option: RangeSelection) => {
if (option === RangeSelection.FULL) {
setPriceRangeState((prevState) => ({ ...prevState, fullRange: true }))
} else {
setPriceRangeState((prevState) => ({ ...prevState, fullRange: false }))
}
},
[setPriceRangeState],
)
const segmentedControlRangeOptions = [
{ display: <RangeControl value={t(`common.fullRange`)} active={fullRange} />, value: RangeSelection.FULL },
{ display: <RangeControl value={t(`common.customRange`)} active={!fullRange} />, value: RangeSelection.CUSTOM },
]
return (
<Container>
<Flex gap="$gap20">
<Flex row alignItems="center">
<Text flex={1} variant="subheading1">
<Trans i18nKey="position.selectRange" />
</Text>
<SegmentedControl
options={controlOptions}
selectedOption={baseCurrency?.symbol ?? ''}
onSelectOption={handleSelectToken}
/>
</Flex>
<SegmentedControl
options={segmentedControlRangeOptions}
selectedOption={fullRange ? RangeSelection.FULL : RangeSelection.CUSTOM}
onSelectOption={handleSelectRange}
fullWidth
size="large"
/>
<Text variant="body3" color="$neutral2">
<Trans i18nKey="position.provide.liquidityDescription" />
</Text>
<Flex gap="$gap4">
<Flex
backgroundColor="$surface2"
p="$padding16"
gap="$gap12"
borderTopLeftRadius="$rounded20"
borderTopRightRadius="$rounded20"
>
<Flex gap="$gap8" row alignItems="center">
<Text variant="body3" color="$neutral2">
<Trans i18nKey="common.currentPrice.label" />
</Text>
<Text variant="body3" color="$neutral1">
<Trans
i18nKey="common.amountPerBase"
// TODO: update values after WEB-4920
values={{
amount: '329,394,000.00',
symbolA: quoteCurrency?.symbol,
symbolB: baseCurrency?.symbol,
}}
/>
</Text>
<SwapActionButton size="$icon.16" color="$neutral2" />
</Flex>
</Flex>
<Flex row gap="$gap4">
<RangeInput input={RangeSelectionInput.MIN} />
<RangeInput input={RangeSelectionInput.MAX} />
</Flex>
</Flex>
</Flex>
<Button
flex={1}
py="$spacing16"
px="$spacing20"
backgroundColor="$accent3"
hoverStyle={{
backgroundColor: undefined,
opacity: 0.8,
}}
pressStyle={{
backgroundColor: undefined,
}}
onPress={onContinue}
>
<Text variant="buttonLabel1" color="$surface1">
<Trans i18nKey="common.button.continue" />
</Text>
</Button>
</Container>
)
}
import { Currency } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
import { useCurrencyInfo } from 'hooks/Tokens'
import { AddHook } from 'pages/Pool/Positions/create/AddHook'
import { useCreatePositionContext } from 'pages/Pool/Positions/create/CreatePositionContext'
import { AdvancedButton, Container } from 'pages/Pool/Positions/create/shared'
import { useCallback, useReducer, useState } from 'react'
import { TamaguiClickableStyle } from 'theme/components'
import { PositionField } from 'types/position'
import { Button, Flex, Text, styled } from 'ui/src'
import { CheckCircleFilled } from 'ui/src/components/icons/CheckCircleFilled'
import { Dollar } from 'ui/src/components/icons/Dollar'
import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron'
import { iconSizes } from 'ui/src/theme'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import { Trans, useTranslation } from 'uniswap/src/i18n'
import { useFormatter } from 'utils/formatNumbers'
const CurrencySelector = ({ currency, onPress }: { currency?: Currency; onPress: () => void }) => {
const { t } = useTranslation()
// TODO: remove when backend returns token logos in graphql response: WEB-4920
const currencyInfo = useCurrencyInfo(currency)
return (
<Button
flex={1}
width="100%"
onPress={onPress}
py="$spacing12"
pr="$spacing12"
pl="$spacing16"
theme="primary"
backgroundColor={currency ? '$surface3' : '$accent3'}
justifyContent="space-between"
gap="$spacing8"
hoverStyle={{
backgroundColor: undefined,
opacity: 0.8,
}}
pressStyle={{
backgroundColor: undefined,
}}
>
<Flex row gap="$spacing8" alignItems="center">
{currency && (
<TokenLogo
size={iconSizes.icon24}
chainId={currency.chainId}
name={currency.name}
symbol={currency.symbol}
url={currencyInfo?.logoUrl}
/>
)}
<Text variant="buttonLabel2" color={currency ? '$neutral1' : '$surface1'}>
{currency ? currency.symbol : t('fiatOnRamp.button.chooseToken')}
</Text>
</Flex>
<RotatableChevron direction="down" color="$neutral2" width={iconSizes.icon24} height={iconSizes.icon24} />
</Button>
)
}
interface FeeTier {
value: number
title: string
selectionPercent: number
}
const FeeTierContainer = styled(Flex, {
flex: 1,
width: '100%',
p: '$spacing12',
gap: '$spacing8',
justifyContent: 'space-between',
borderRadius: '$rounded12',
borderWidth: 1,
borderColor: '$surface3',
...TamaguiClickableStyle,
})
const FeeTier = ({
feeTier,
selected,
onSelect,
}: {
feeTier: FeeTier
selected: boolean
onSelect: (value: number) => void
}) => {
return (
<FeeTierContainer onPress={() => onSelect(feeTier.value)} background={selected ? '$surface3' : '$surface1'}>
<Flex row gap={10} justifyContent="space-between" alignItems="center">
<Text variant="buttonLabel3">{feeTier.value / 10000}%</Text>
{selected && <CheckCircleFilled size={iconSizes.icon20} />}
</Flex>
<Text variant="body4">{feeTier.title}</Text>
<Text variant="body4" color="$neutral2">
{feeTier.selectionPercent}% select
</Text>
</FeeTierContainer>
)
}
export function SelectTokensStep({ onContinue }: { onContinue: () => void }) {
const { formatDelta } = useFormatter()
const { t } = useTranslation()
const {
positionState: {
tokenInputs: { TOKEN0: token0, TOKEN1: token1 },
fee,
},
setPositionState,
derivedPositionInfo,
} = useCreatePositionContext()
const [currencySearchInputState, setCurrencySearchInputState] = useState<PositionField | undefined>(undefined)
const [isShowMoreFeeTiersEnabled, toggleShowMoreFeeTiersEnabled] = useReducer((state) => !state, false)
const continueButtonEnabled = !!derivedPositionInfo.pool
const handleCurrencySelect = useCallback(
(currency: Currency) => {
switch (currencySearchInputState) {
case PositionField.TOKEN0:
case PositionField.TOKEN1:
setPositionState((prevState) => ({
...prevState,
tokenInputs: { ...prevState.tokenInputs, [currencySearchInputState]: currency },
}))
break
default:
break
}
},
[currencySearchInputState, setPositionState],
)
const handleFeeTierSelect = useCallback(
(feeTier: number) => {
setPositionState((prevState) => ({ ...prevState, fee: feeTier }))
},
[setPositionState],
)
const feeTiers = [
{ value: FeeAmount.LOWEST, title: t(`fee.bestForVeryStable`), selectionPercent: 0 },
{ value: FeeAmount.LOW, title: t(`fee.bestForStablePairs`), selectionPercent: 0 },
{ value: FeeAmount.MEDIUM, title: t(`fee.bestForMost`), selectionPercent: 96 },
{ value: FeeAmount.HIGH, title: t(`fee.bestForExotic`), selectionPercent: 4 },
]
return (
<>
<Container>
<Flex gap="$spacing16">
<Flex gap="$spacing12">
<Flex>
<Text variant="subheading1">
<Trans i18nKey="pool.selectPair" />
</Text>
<Text variant="body3" color="$neutral2">
<Trans i18nKey="position.provide.liquidity" />
</Text>
</Flex>
<Flex row gap="$gap16">
<CurrencySelector currency={token0} onPress={() => setCurrencySearchInputState(PositionField.TOKEN0)} />
<CurrencySelector currency={token1} onPress={() => setCurrencySearchInputState(PositionField.TOKEN1)} />
</Flex>
<AddHook />
</Flex>
</Flex>
<Flex gap="$spacing24">
<Flex>
<Text variant="subheading1">
<Trans i18nKey="fee.tier" />
</Text>
<Text variant="body3" color="$neutral2">
<Trans i18nKey="fee.tier.description" />
</Text>
</Flex>
</Flex>
<Flex gap="$spacing8">
<Flex
row
py="$spacing12"
px="$spacing16"
gap="$spacing24"
justifyContent="space-between"
borderRadius="$rounded12"
borderWidth={1}
borderColor="$surface3"
>
<Flex>
<Flex row>
<Text variant="subheading2" color="$neutral1">
<Trans i18nKey="fee.tierExact" values={{ fee: formatDelta(fee / 10_000) }} />
</Text>
</Flex>
<Text variant="body3" color="$neutral2">
<Trans i18nKey="fee.tier.label" />
</Text>
</Flex>
<Button
py="$spacing8"
px="$spacing12"
gap="$gap4"
theme="secondary"
onPress={toggleShowMoreFeeTiersEnabled}
>
<Text variant="buttonLabel4">{isShowMoreFeeTiersEnabled ? t('common.less') : t('common.more')}</Text>
<RotatableChevron
direction={isShowMoreFeeTiersEnabled ? 'up' : 'down'}
color="$neutral2"
width={iconSizes.icon20}
height={iconSizes.icon20}
/>
</Button>
</Flex>
{isShowMoreFeeTiersEnabled && (
<Flex row gap={10}>
{feeTiers.map((feeTier) => (
<FeeTier
key={feeTier.value}
feeTier={feeTier}
selected={feeTier.value === fee}
onSelect={handleFeeTierSelect}
/>
))}
</Flex>
)}
<AdvancedButton title={t('fee.tier.search')} Icon={Dollar} onPress={() => {}} />
</Flex>
<Button
flex={1}
py="$spacing16"
px="$spacing20"
backgroundColor="$accent3"
hoverStyle={{
backgroundColor: undefined,
opacity: 0.8,
}}
pressStyle={{
backgroundColor: undefined,
}}
onPress={onContinue}
disabled={!continueButtonEnabled}
>
<Text variant="buttonLabel1" color="$surface1">
<Trans i18nKey="common.button.continue" />
</Text>
</Button>
</Container>
<CurrencySearchModal
isOpen={currencySearchInputState !== undefined}
onDismiss={() => setCurrencySearchInputState(undefined)}
onCurrencySelect={handleCurrencySelect}
/>
</>
)
}
import { usePool } from 'hooks/usePools'
import { PositionInfo, PositionState } from 'pages/Pool/Positions/create/types'
import { useMemo } from 'react'
export function useDerivedPositionInfo(state: PositionState): PositionInfo {
const pool = usePool(state.tokenInputs.TOKEN0, state.tokenInputs.TOKEN1, state.fee)[1] ?? undefined
return useMemo(
() => ({
pool,
}),
[pool],
)
}
import { Flex, GeneratedIcon, Text, styled } from 'ui/src'
import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled'
import { iconSizes } from 'ui/src/theme'
import { useTranslation } from 'uniswap/src/i18n'
export const Container = styled(Flex, {
gap: 32,
p: '$spacing24',
borderRadius: '$rounded20',
borderWidth: '$spacing1',
borderColor: '$surface3',
maxWidth: 580,
})
export function AdvancedButton({ title, Icon, onPress }: { title: string; Icon: GeneratedIcon; onPress: () => void }) {
const { t } = useTranslation()
return (
<Flex row gap="$spacing8" alignItems="center">
<Flex row gap="$spacing4" alignItems="center">
<Icon size={iconSizes.icon16} color="$neutral2" />
<Text
variant="body3"
color="$neutral2"
textDecorationLine="underline"
textDecorationStyle="dashed"
cursor="pointer"
onPress={onPress}
>
{title}
</Text>
</Flex>
<Text variant="body3" color="$neutral3">
({t('common.advanced')})
</Text>
<InfoCircleFilled size={iconSizes.icon16} color="$neutral3" />
</Flex>
)
}
import { Currency } from '@uniswap/sdk-core'
import { Pool } from '@uniswap/v3-sdk'
import { Dispatch, SetStateAction } from 'react'
import { PositionField } from 'types/position'
export enum PositionFlowStep {
SELECT_TOKENS,
PRICE_RANGE,
DEPOSIT,
}
export interface PositionState {
tokenInputs: { [field in PositionField]?: Currency }
fee: number
hook?: string
}
export interface PositionInfo {
pool?: Pool
}
export type CreatePositionContextType = {
step: PositionFlowStep
setStep: Dispatch<SetStateAction<PositionFlowStep>>
positionState: PositionState
setPositionState: Dispatch<SetStateAction<PositionState>>
derivedPositionInfo: PositionInfo
}
export interface PriceRangeState {
priceInverted: boolean
fullRange: boolean
minPrice: string
maxPrice: string
}
export type PriceRangeContextType = {
priceRangeState: PriceRangeState
setPriceRangeState: Dispatch<SetStateAction<PriceRangeState>>
}
......@@ -15,7 +15,6 @@ import { BlueCard, LightCard } from 'components/Card/cards'
import CurrencyInputPanel from 'components/CurrencyInputPanel'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { AddRemoveTabs } from 'components/NavigationTabs'
import { MinimalPositionCard } from 'components/PositionCard'
import Slider from 'components/Slider'
......@@ -49,6 +48,8 @@ import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { StyledInternalLink, ThemedText } from 'theme/components'
import { Text } from 'ui/src'
import { WRAPPED_NATIVE_CURRENCY } from 'uniswap/src/constants/tokens'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans } from 'uniswap/src/i18n'
......@@ -502,6 +503,10 @@ function RemoveLiquidity() {
liquidityPercentChangeCallback,
)
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
if (!networkSupportsV2) {
return <V2Unsupported />
}
......@@ -710,7 +715,13 @@ function RemoveLiquidity() {
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={accountDrawer.open}>
<ConnectWalletButtonText />
{isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)}
</ButtonLight>
</Trace>
) : (
......
......@@ -8,7 +8,7 @@ import { t } from 'uniswap/src/i18n'
import { isBrowserRouterEnabled } from 'utils/env'
// High-traffic pages (index and /swap) should not be lazy-loaded.
import Landing from 'pages/Landing'
import { CreatePosition } from 'pages/Pool/Positions/create/CreatePosition'
import { NewPosition } from 'pages/LegacyPool/NewPosition'
import Swap from 'pages/Swap'
const NftExplore = lazy(() => import('nft/pages/explore'))
......@@ -202,8 +202,8 @@ export const routes: RouteDefinition[] = [
}),
// Refreshed pool routes
createRouteDefinition({
path: '/positions/create',
getElement: () => <CreatePosition />,
path: '/positions/new',
getElement: () => <NewPosition />,
getTitle: getPositionPageTitle,
getDescription: getPositionPageDescription,
}),
......
import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks'
import { ButtonLight } from 'components/Button/buttons'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { useBuyFormContext } from 'pages/Swap/Buy/BuyFormContext'
import { Button, Flex, SpinningLoader, Text, WidthAnimator } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { useTranslation } from 'uniswap/src/i18n'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import { Trans, useTranslation } from 'uniswap/src/i18n'
import { useAccount } from 'wagmi'
interface BuyFormButtonProps {
......@@ -20,10 +21,20 @@ export function BuyFormButton({ forceDisabled }: BuyFormButtonProps) {
const { inputAmount } = buyFormState
const { notAvailableInThisRegion, quotes, fetchingQuotes, error } = derivedBuyFormInfo
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
if (!account.isConnected) {
return (
<ButtonLight onClick={accountDrawer.open}>
<ConnectWalletButtonText />
{isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)}
</ButtonLight>
)
}
......
......@@ -69,7 +69,7 @@ exports[`PredefinedAmount renders correctly with amount= 300 , currentAmount= "1
<span
class="font_button _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843134974 _fontFamily-299667014 _wordWrap-break-word _fontSize-229441189 _lineHeight-222976542 _fontWeight-233016109"
data-disable-theme="true"
style="color: rgb(191, 191, 191); padding-top: 1px;"
style="color: rgb(206, 206, 206); padding-top: 1px;"
>
$300
</span>
......@@ -148,7 +148,7 @@ exports[`PredefinedAmount renders correctly with amount= 1000 , currentAmount= "
<span
class="font_button _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-843134974 _fontFamily-299667014 _wordWrap-break-word _fontSize-229441189 _lineHeight-222976542 _fontWeight-233016109"
data-disable-theme="true"
style="color: rgb(191, 191, 191); padding-top: 1px;"
style="color: rgb(206, 206, 206); padding-top: 1px;"
>
$1000
</span>
......
......@@ -12,7 +12,6 @@ import {
useCurrentPriceAdjustment,
} from 'components/CurrencyInputPanel/LimitPriceInputPanel/useCurrentPriceAdjustment'
import SwapCurrencyInputPanel from 'components/CurrencyInputPanel/SwapCurrencyInputPanel'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import Column from 'components/deprecated/Column'
import Row from 'components/deprecated/Row'
import { Field } from 'components/swap/constants'
......@@ -41,6 +40,8 @@ import { AlertTriangleFilled } from 'ui/src/components/icons/AlertTriangleFilled
import { colors, validColor } from 'ui/src/theme'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import { Locale } from 'uniswap/src/features/language/constants'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName, InterfacePageNameLocal } from 'uniswap/src/features/telemetry/constants'
......@@ -464,6 +465,10 @@ function SubmitOrderButton({
const account = useAccount()
const { chainId } = useSwapAndLimitContext()
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
if (!isUniswapXSupportedChain(chainId)) {
return (
<ButtonError disabled>
......@@ -475,7 +480,13 @@ function SubmitOrderButton({
if (!account.isConnected) {
return (
<ButtonLight onClick={accountDrawer.open} fontWeight={535} $borderRadius="16px">
<ConnectWalletButtonText />
{isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)}
</ButtonLight>
)
}
......
import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks'
import { ButtonLight, ButtonPrimary } from 'components/Button/buttons'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import Column from 'components/deprecated/Column'
import { useIsSupportedChainId } from 'constants/chains'
import { useAccount } from 'hooks/useAccount'
......@@ -18,6 +17,8 @@ import { SendContextProvider, useSendContext } from 'state/send/SendContext'
import { CurrencyState } from 'state/swap/types'
import { useSwapAndLimitContext } from 'state/swap/useSwapContext'
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { InterfacePageNameLocal } from 'uniswap/src/features/telemetry/constants'
import { Trans } from 'uniswap/src/i18n'
......@@ -186,6 +187,10 @@ function SendFormInner({ disableTokenInputs = false, onCurrencyChange }: SendFor
.catch(() => undefined)
}, [handleModalState, sendCallback, setSendState])
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
return (
<>
<Column gap="xs">
......@@ -198,7 +203,13 @@ function SendFormInner({ disableTokenInputs = false, onCurrencyChange }: SendFor
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={accountDrawer.open} fontWeight={535} $borderRadius="16px">
<ConnectWalletButtonText />
{isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)}
</ButtonLight>
</Trace>
) : !multichainUXEnabled && initialChainId && initialChainId !== account.chainId ? (
......
......@@ -12,7 +12,6 @@ import { GrayCard } from 'components/Card/cards'
import { ConfirmSwapModal } from 'components/ConfirmSwapModal'
import SwapCurrencyInputPanel from 'components/CurrencyInputPanel/SwapCurrencyInputPanel'
import ErrorIcon from 'components/Icons/Error'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import Column, { AutoColumn } from 'components/deprecated/Column'
import Row from 'components/deprecated/Row'
......@@ -53,6 +52,8 @@ import { Text } from 'ui/src'
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains'
import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { WrapType } from 'uniswap/src/features/transactions/types/wrap'
......@@ -507,6 +508,10 @@ export function SwapForm({
// @ts-ignore
const isUsingBlockedExtension = window.ethereum?.['isPocketUniverseZ']
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
return (
<>
<TokenSafetyModal
......@@ -671,7 +676,13 @@ export function SwapForm({
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={accountDrawer.open} fontWeight={535} $borderRadius="16px">
<ConnectWalletButtonText />
{isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)}
</ButtonLight>
</Trace>
) : !multichainUXEnabled && initialChainId && initialChainId !== connectedChainId ? (
......
......@@ -123,7 +123,7 @@ function useCreateTDPContext(): PendingTDPContext | LoadedTDPContext {
const tokenColor =
useSrcColor(
extractedColorSrc,
tokenQuery.data?.token?.name ?? tokenQuery.data?.token?.project?.name,
tokenQuery.data?.token?.project?.name ?? tokenQuery.data?.token?.name,
theme.surface2,
).tokenColor ?? undefined
......
......@@ -132,7 +132,7 @@ Array [
"getElement": [Function],
"getTitle": [Function],
"nestedPaths": Array [],
"path": "/positions/create",
"path": "/positions/new",
},
Object {
"enabled": [Function],
......
......@@ -30,7 +30,7 @@ export const paths = [
'/pools',
'/pools/:tokenId',
'/positions',
'/positions/create',
'/positions/new',
'/add/v2',
'/add',
'/increase',
......
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { useAccount } from 'hooks/useAccount'
import { useTotalSupply } from 'hooks/useTotalSupply'
import { useV2Pair } from 'hooks/useV2Pairs'
......@@ -11,6 +10,8 @@ import { ReactNode, useCallback } from 'react'
import { Field, typeInput } from 'state/burn/actions'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { InterfaceState } from 'state/webReducer'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import { Trans } from 'uniswap/src/i18n'
export function useBurnState(): InterfaceState['burn'] {
......@@ -121,9 +122,19 @@ export function useDerivedBurnInfo(
: undefined,
}
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
let error: ReactNode | undefined
if (!account.isConnected) {
error = <ConnectWalletButtonText />
error = isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)
}
if (!parsedAmounts[Field.LIQUIDITY] || !parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
......
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Position } from '@uniswap/v3-sdk'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { useToken } from 'hooks/Tokens'
import { useAccount } from 'hooks/useAccount'
import { usePool } from 'hooks/usePools'
......@@ -10,6 +9,8 @@ import { selectPercent } from 'state/burn/v3/actions'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { InterfaceState } from 'state/webReducer'
import { PositionDetails } from 'types/position'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import { Trans } from 'uniswap/src/i18n'
import { unwrappedToken } from 'utils/unwrappedToken'
......@@ -74,9 +75,19 @@ export function useDerivedV3BurnInfo(
const outOfRange =
pool && position ? pool.tickCurrent < position.tickLower || pool.tickCurrent > position.tickUpper : false
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
let error: ReactNode | undefined
if (!account.isConnected) {
error = <ConnectWalletButtonText />
error = isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)
}
if (percent === 0) {
error = error ?? <Trans i18nKey="burn.input.enterAPercent.error" />
......
import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { useAccount } from 'hooks/useAccount'
import { useTotalSupply } from 'hooks/useTotalSupply'
import { PairState, useV2Pair } from 'hooks/useV2Pairs'
......@@ -11,6 +10,8 @@ import { useCurrencyBalances } from 'state/connection/hooks'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { Field, typeInput } from 'state/mint/actions'
import { InterfaceState } from 'state/webReducer'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import { Trans } from 'uniswap/src/i18n'
import { logger } from 'utilities/src/logger/logger'
......@@ -182,9 +183,19 @@ export function useDerivedMintInfo(
}
}, [liquidityMinted, totalSupply])
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
let error: ReactNode | undefined
if (!account.isConnected) {
error = <ConnectWalletButtonText />
error = isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)
}
if (pairState === PairState.INVALID) {
......
......@@ -10,7 +10,6 @@ import {
priceToClosestTick,
tickToPrice,
} from '@uniswap/v3-sdk'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { BIG_INT_ZERO } from 'constants/misc'
import { useAccount } from 'hooks/useAccount'
import { PoolState, usePool } from 'hooks/usePools'
......@@ -32,6 +31,8 @@ import {
} from 'state/mint/v3/actions'
import { tryParseTick } from 'state/mint/v3/utils'
import { InterfaceState } from 'state/webReducer'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import { Trans } from 'uniswap/src/i18n'
import { getTickToPrice } from 'utils/getTickToPrice'
......@@ -449,9 +450,19 @@ export function useV3DerivedMintInfo(
tickUpper,
])
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
let errorMessage: ReactNode | undefined
if (!account.isConnected) {
errorMessage = <ConnectWalletButtonText />
errorMessage = isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)
}
if (poolState === PoolState.INVALID) {
......
......@@ -3,7 +3,7 @@ import { parse } from 'qs'
import { queryParametersToCurrencyState, useInitialCurrencyState } from 'state/swap/hooks'
import { ETH_MAINNET } from 'test-utils/constants'
import { renderHook, waitFor } from 'test-utils/render'
import { UNI, nativeOnChain } from 'uniswap/src/constants/tokens'
import { MATIC_POLYGON, UNI } from 'uniswap/src/constants/tokens'
import { UniverseChainId } from 'uniswap/src/types/chains'
jest.mock('uniswap/src/features/gating/hooks', () => {
......@@ -186,7 +186,7 @@ describe('hooks', () => {
{
tokenBalances: [
{
token: nativeOnChain(UniverseChainId.Polygon),
token: MATIC_POLYGON,
denominatedValue: {
value: 1000,
},
......
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils'
import { Field } from 'components/swap/constants'
import { CHAIN_IDS_TO_NAMES, useSupportedChainId } from 'constants/chains'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
......@@ -21,6 +20,8 @@ import { CurrencyState, SerializedCurrencyState, SwapInfo, SwapState } from 'sta
import { useSwapAndLimitContext, useSwapContext } from 'state/swap/useSwapContext'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { useTokenProjects } from 'uniswap/src/features/dataApi/tokenProjects'
import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments'
import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks'
import { Trans } from 'uniswap/src/i18n'
import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains'
import { areCurrencyIdsEqual, currencyId } from 'uniswap/src/utils/currencyId'
......@@ -219,11 +220,22 @@ export function useDerivedSwapInfo(state: SwapState): SwapInfo {
isClassicTrade(trade.trade) && (nativeCurrencyBalanceUSD ?? 0) < (trade.trade.totalGasUseEstimateUSDWithBuffer ?? 0)
const { isDisconnected } = useAccount()
const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs)
const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp
const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount
const inputError = useMemo(() => {
let inputError: ReactNode | undefined
if (!account.isConnected) {
inputError = isDisconnected ? <ConnectWalletButtonText /> : <Trans i18nKey="common.connectingWallet" />
const disconnectedInputError = isSignIn ? (
<Trans i18nKey="nav.signIn.button" />
) : isLogIn ? (
<Trans i18nKey="nav.logIn.button" />
) : (
<Trans i18nKey="common.connectWallet.button" />
)
inputError = isDisconnected ? disconnectedInputError : <Trans i18nKey="common.connectingWallet" />
}
if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) {
......@@ -268,6 +280,8 @@ export function useDerivedSwapInfo(state: SwapState): SwapInfo {
currencyBalances,
trade?.trade,
allowedSlippage,
isSignIn,
isLogIn,
isDisconnected,
nativeCurrency.symbol,
])
......
......@@ -32,8 +32,8 @@ const entryGzipSize = report.reduce(
0,
)
// somewhat arbitrary, based on current size (9/19/2024)
const limit = 2_185_000
// somewhat arbitrary, based on current size (9/17/2024)
const limit = 2_165_000
if (entryGzipSize > limit) {
console.error(`Bundle size has grown too big! Entry JS size is ${entryGzipSize}, over the limit of ${limit}.`)
......
import { ClientOptions, ErrorEvent, EventHint } from '@sentry/types'
import { ProviderRpcError } from '@web3-react/types'
import { wagmiConfig } from 'components/Web3Provider/wagmiConfig'
import { wagmiConfig } from 'components/Web3Provider/wagmi'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
import { getAccount } from 'wagmi/actions'
......
......@@ -15,8 +15,3 @@ export interface PositionDetails {
tokensOwed0: BigNumber
tokensOwed1: BigNumber
}
export enum PositionField {
TOKEN0 = 'TOKEN0',
TOKEN1 = 'TOKEN1',
}
<svg viewBox="0 0 18 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="16px" height="16px" viewBox="0 0 18 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 3C9.55228 3 10 2.55228 10 2C10 1.44772 9.55228 1 9 1C8.44772 1 8 1.44772 8 2C8 2.55228 8.44772 3 9 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 3C16.5523 3 17 2.55228 17 2C17 1.44772 16.5523 1 16 1C15.4477 1 15 1.44772 15 2C15 2.55228 15.4477 3 16 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 3C2.55228 3 3 2.55228 3 2C3 1.44772 2.55228 1 2 1C1.44772 1 1 1.44772 1 2C1 2.55228 1.44772 3 2 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg stroke="currentColor" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 4"><path d="M9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
\ No newline at end of file
......@@ -19,7 +19,6 @@ export function UniversalImage({
fallback,
fastImage = false,
testID,
onLoad,
allowLocalUri = false,
}: UniversalImageProps): JSX.Element | null {
// Allow calculation of fields as needed
......@@ -129,7 +128,6 @@ export function UniversalImage({
style={style?.image}
testID={testID ? `img-${testID}` : undefined}
uri={imageHttpUrl}
onLoad={onLoad}
/>
)
}
......@@ -2,7 +2,7 @@ import { useState } from 'react'
import { Image } from 'react-native'
import { PlainImageProps } from 'ui/src/components/UniversalImage/types'
export function PlainImage({ uri, size, fallback, resizeMode, style, testID, onLoad }: PlainImageProps): JSX.Element {
export function PlainImage({ uri, size, fallback, resizeMode, style, testID }: PlainImageProps): JSX.Element {
const [hasError, setHasError] = useState(false)
if (hasError && fallback) {
......@@ -20,7 +20,6 @@ export function PlainImage({ uri, size, fallback, resizeMode, style, testID, onL
onError={() => {
setHasError(true)
}}
onLoad={onLoad}
/>
)
}
import { PlainImageProps } from 'ui/src/components/UniversalImage/types'
import { PlatformSplitStubError } from 'utilities/src/errors'
import { useState } from 'react'
import { PlainImageProps, UniversalImageResizeMode } from 'ui/src/components/UniversalImage/types'
import { Flex } from 'ui/src/components/layout/Flex'
import { isTestEnv } from 'utilities/src/environment/env'
export function PlainImage(_props: PlainImageProps): JSX.Element {
throw new PlatformSplitStubError('PlainImage')
export function PlainImage({ uri, size, fallback, resizeMode, style, testID }: PlainImageProps): JSX.Element {
const [hasError, setHasError] = useState(false)
// TODO cover all cases better
const objectFit =
resizeMode === UniversalImageResizeMode.Contain || resizeMode === UniversalImageResizeMode.Cover
? resizeMode
: 'contain'
const imgElement = (
<img
height={size.height}
src={uri}
style={{ objectFit, aspectRatio: size.aspectRatio, ...style }}
width={size.width}
onError={() => {
setHasError(true)
}}
/>
)
if (hasError && fallback) {
return fallback
}
// TODO(MOB-3485): remove test run special casing
if (isTestEnv()) {
return <Flex testID={testID}>{imgElement}</Flex>
} else {
return imgElement
}
}
import { useState } from 'react'
import { PlainImageProps, UniversalImageResizeMode } from 'ui/src/components/UniversalImage/types'
import { Flex } from 'ui/src/components/layout/Flex'
import { isTestEnv } from 'utilities/src/environment/env'
export function PlainImage({ uri, size, fallback, resizeMode, style, testID, onLoad }: PlainImageProps): JSX.Element {
const [hasError, setHasError] = useState(false)
// TODO cover all cases better
const objectFit =
resizeMode === UniversalImageResizeMode.Contain || resizeMode === UniversalImageResizeMode.Cover
? resizeMode
: 'contain'
const imgElement = (
<img
height={size.height}
src={uri}
style={{ objectFit, aspectRatio: size.aspectRatio, ...style }}
width={size.width}
onError={() => {
setHasError(true)
}}
onLoad={onLoad}
/>
)
if (hasError && fallback) {
return fallback
}
// TODO(MOB-3485): remove test run special casing
if (isTestEnv()) {
return <Flex testID={testID}>{imgElement}</Flex>
} else {
return imgElement
}
}
......@@ -39,7 +39,6 @@ export interface UniversalImageProps {
fastImage?: boolean
testID?: string
allowLocalUri?: boolean
onLoad?: () => void
}
export interface PlainImageProps {
......@@ -49,7 +48,6 @@ export interface PlainImageProps {
style?: UniversalImageStyle
resizeMode?: UniversalImageResizeMode
testID?: string
onLoad?: () => void
}
export type FastImageWrapperProps = PlainImageProps & {
......
......@@ -45,8 +45,8 @@ export function LabeledCheckbox({
<Flex row alignItems="center" gap={gap} px={px}>
{checkboxPosition === 'start' && <Checkbox checked={checked} size={size} variant={variant} onPress={onPress} />}
{text && (
<Flex grow>
<Flex shrink>{textElement}</Flex>
<Flex grow shrink>
{textElement}
</Flex>
)}
{checkboxPosition === 'end' && <Checkbox checked={checked} variant={variant} onPress={onPress} />}
......
......@@ -6,7 +6,7 @@ import { createIcon } from '../factories/createIcon'
export const [Ellipsis, AnimatedEllipsis] = createIcon({
name: 'Ellipsis',
getIcon: (props) => (
<Svg viewBox="0 0 18 4" fill="none" {...props}>
<Svg width="16" height="16" viewBox="0 0 18 4" fill="none" {...props}>
<Path
d="M9 3C9.55228 3 10 2.55228 10 2C10 1.44772 9.55228 1 9 1C8.44772 1 8 1.44772 8 2C8 2.55228 8.44772 3 9 3Z"
stroke="currentColor"
......
import { Path, Svg } from 'react-native-svg'
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
import { createIcon } from '../factories/createIcon'
export const [TripleDots, AnimatedTripleDots] = createIcon({
name: 'TripleDots',
getIcon: (props) => (
<Svg stroke="currentColor" fill="currentColor" viewBox="0 0 18 4" {...props}>
<Path
d="M9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
/>
<Path
d="M16 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
/>
<Path
d="M2 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
/>
</Svg>
),
})
......@@ -203,6 +203,7 @@ export * from './Trash'
export * from './TrashFilled'
export * from './TrendDown'
export * from './TrendUp'
export * from './TripleDots'
export * from './UniswapLogo'
export * from './UniswapX'
export * from './UserSquare'
......
......@@ -157,7 +157,7 @@ const sporeLight = {
neutral1Hovered: '#131313',
neutral2: '#7D7D7D',
neutral2Hovered: '#6B6B6B',
neutral3: '#BFBFBF',
neutral3: '#CECECE',
neutral3Hovered: '#ADADAD',
surface1: colors.white,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment