ci(release): publish latest release

parent 9be8c36b
diff --git a/dist/esm/connectors/injected.js b/dist/esm/connectors/injected.js
index 26f420d68ed9a12deea30a3dca195e2bcf3b3c44..70fc93a7db7b9f4db10e71edd73ee81bd0e28f1e 100644
--- a/dist/esm/connectors/injected.js
+++ b/dist/esm/connectors/injected.js
@@ -405,6 +405,18 @@ export function injected(parameters = {}) {
onChainChanged(chain) {
console.log('[injected] onChainChanged', chain);
const chainId = Number(chain);
+ if (this.id === 'io.metamask')
+ this.getProvider()
+ .then((provider) =>
+ provider
+ ?.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: numberToHex(chainId) }],
+ })
+ .then(() => {})
+ .catch(() => {}),
+ )
+ .catch(() => {})
config.emitter.emit('change', { chainId });
},
async onConnect(connectInfo) {
diff --git a/dist/index.js b/dist/index.js
index f38d06e2de52b5035560d63d6889dc54d5ca021b..4f8fa194b60c73a901dbce4a742f598b1c95c026 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -62,7 +62,7 @@ class CoinbaseWallet extends types_1.Connector {
return __awaiter(this, void 0, void 0, function* () {
if (this.eagerConnection)
return;
- yield (this.eagerConnection = Promise.resolve().then(() => __importStar(require('@coinbase/wallet-sdk'))).then((m) => {
+ yield (this.eagerConnection = Promise.resolve().then(async () => __importStar(await import('@coinbase/wallet-sdk'))).then((m) => {
const _a = this.options, { url } = _a, options = __rest(_a, ["url"]);
this.coinbaseWallet = new m.default(options);
this.provider = this.coinbaseWallet.makeWeb3Provider(url);
diff --git a/dist/index.js b/dist/index.js
index 015a33c37fe87f13f31559d462351acd7ae9bac7..4cd7cdeb4437f30c1c063c0ffb8fd5692a399dbf 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -68,8 +68,8 @@ class GnosisSafe extends types_1.Connector {
if (this.eagerConnection)
return;
// kick off import early to minimize waterfalls
- const SafeAppProviderPromise = Promise.resolve().then(() => __importStar(require('@safe-global/safe-apps-provider'))).then(({ SafeAppProvider }) => SafeAppProvider);
- yield (this.eagerConnection = Promise.resolve().then(() => __importStar(require('@safe-global/safe-apps-sdk'))).then((m) => __awaiter(this, void 0, void 0, function* () {
+ const SafeAppProviderPromise = Promise.resolve().then(async () => __importStar(await import('@safe-global/safe-apps-provider'))).then(({ SafeAppProvider }) => SafeAppProvider);
+ yield (this.eagerConnection = Promise.resolve().then(async () => __importStar(await import('@safe-global/safe-apps-sdk'))).then((m) => __awaiter(this, void 0, void 0, function* () {
this.sdk = new m.default(this.options);
const safe = yield Promise.race([
this.sdk.safe.getInfo(),
diff --git a/dist/index.js b/dist/index.js
index c8476dd9b01c0599dfc545f4c86432081bd0fcec..c0bfce759654232df771724e59a650c92b392f78 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -54,7 +54,7 @@ class MetaMask extends types_1.Connector {
return __awaiter(this, void 0, void 0, function* () {
if (this.eagerConnection)
return;
- return (this.eagerConnection = Promise.resolve().then(() => __importStar(require('@metamask/detect-provider'))).then((m) => __awaiter(this, void 0, void 0, function* () {
+ return (this.eagerConnection = Promise.resolve().then(async () => __importStar(await import('@metamask/detect-provider'))).then((m) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const provider = yield m.default(this.options);
if (provider) {
diff --git a/dist/index.js b/dist/index.js
index 1a36d14c5d7c9ee55b2eccd11216c8adb6839daf..908b8c57a2d8cd565030e34e15c56caf7d182cfd 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -84,7 +84,7 @@ class WalletConnect extends types_1.Connector {
return __awaiter(this, void 0, void 0, function* () {
const rpcMap = this.rpcMap ? (0, utils_1.getBestUrlMap)(this.rpcMap, this.timeout) : undefined;
const chainProps = this.getChainProps(this.chains, this.optionalChains, desiredChainId);
- const ethProviderModule = yield Promise.resolve().then(() => __importStar(require('@walletconnect/ethereum-provider')));
+ const ethProviderModule = yield Promise.resolve().then(async () => __importStar(await import('@walletconnect/ethereum-provider')));
this.provider = yield ethProviderModule.default.init(Object.assign(Object.assign(Object.assign({}, this.options), chainProps), { rpcMap: yield rpcMap }));
return this.provider
.on('disconnect', this.disconnectListener)
diff --git a/dist/utils.js b/dist/utils.js
index 17539b6f910e65aeaebcc116395dce56ae4ce193..9ea637118844ebbdc55db71009b44849992603d2 100644
--- a/dist/utils.js
+++ b/dist/utils.js
@@ -62,8 +62,8 @@ function getBestUrl(urls, timeout) {
if (urls.length === 1)
return urls[0];
const [HttpConnection, JsonRpcProvider] = yield Promise.all([
- Promise.resolve().then(() => __importStar(require('@walletconnect/jsonrpc-http-connection'))).then(({ HttpConnection }) => HttpConnection),
- Promise.resolve().then(() => __importStar(require('@walletconnect/jsonrpc-provider'))).then(({ JsonRpcProvider }) => JsonRpcProvider),
+ Promise.resolve().then(async () => __importStar(await import('@walletconnect/jsonrpc-http-connection'))).then(({ HttpConnection }) => HttpConnection),
+ Promise.resolve().then(async () => __importStar(await import('@walletconnect/jsonrpc-provider'))).then(({ JsonRpcProvider }) => JsonRpcProvider),
]);
// the below returns the first url for which there's been a successful call, prioritized by index
return new Promise((resolve) => {
diff --git a/lib/browser/eip1193.js b/lib/browser/eip1193.js
index ce028c25a164d8af8d513bc0eae4cf104234f6e8..81ba2f29d379f18c04c57b5a560cc6c4f4b57e0d 100644
--- a/lib/browser/eip1193.js
+++ b/lib/browser/eip1193.js
@@ -70,6 +70,7 @@ class Eip1193 extends eip1193_bridge_1.Eip1193Bridge {
yield _super.send.call(this, method, params);
// Providers will not "rewind" to an older block number nor notice chain changes, so they must be reset.
this.utils.providers.forEach((provider) => provider.reset());
+ this.emit('chainChanged', params[0].chainId);
break;
default:
result = yield _super.send.call(this, method, params);
* @uniswap/web-admins
We are back with some new updates! Here’s the latest:
Token Warnings: See more information about the tokens you’re attempting to swap, enriched with data from Blockaid.
Transaction Failure improvements: See more granular warnings when your slippage is too low, and increase it straight from the swap review screen.
Homescreen improvements: We added labels to the pink Buy/Sell/Receive buttons, and moved the QR code scanner to a button on the top left of the screen.
Other changes:
- Various bug fixes and performance improvements
IPFS hash of the deployment:
- CIDv0: `QmQgATjf3hM9GXkqFXMBe9mH8iJad7JWMaJNzp53pDQV2a`
- CIDv1: `bafybeibcw4gnryq4dypbj63jzx2md33b63cvh6eezail7ijyv4twkf72qm`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
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://bafybeibcw4gnryq4dypbj63jzx2md33b63cvh6eezail7ijyv4twkf72qm.ipfs.dweb.link/
- https://bafybeibcw4gnryq4dypbj63jzx2md33b63cvh6eezail7ijyv4twkf72qm.ipfs.cf-ipfs.com/
- [ipfs://QmQgATjf3hM9GXkqFXMBe9mH8iJad7JWMaJNzp53pDQV2a/](ipfs://QmQgATjf3hM9GXkqFXMBe9mH8iJad7JWMaJNzp53pDQV2a/)
### 5.63.2 (2024-12-19)
### Bug Fixes
* **web:** switch to new marketing events - prod (#14706) ad67ff4
mobile/1.42
\ No newline at end of file
web/5.63.2
\ No newline at end of file
ignores: [
# Dependencies that depcheck thinks are unused but are actually used
"react-native-web",
"jest-environment-jsdom",
"webpack-cli",
'react-native-web',
'jest-environment-jsdom',
'webpack-cli',
# Dependencies that depcheck thinks are missing but are actually present or never used
## Internal packages / workspaces
"src",
"tsconfig",
'src',
'tsconfig',
# Webpack plugins
"@svgr/webpack",
"tamagui-loader",
"esbuild-loader",
"swc-loader",
'@svgr/webpack',
'tamagui-loader',
'esbuild-loader',
'style-loader',
'css-loader',
'swc-loader',
## Testing
"@testing-library/dom",
'@testing-library/dom',
]
......@@ -67,6 +67,7 @@
"clean-webpack-plugin": "4.0.0",
"concurrently": "8.2.2",
"copy-webpack-plugin": "11.0.0",
"css-loader": "6.11.0",
"esbuild-loader": "3.2.0",
"eslint": "8.44.0",
"jest": "29.7.0",
......@@ -77,6 +78,7 @@
"react-refresh": "0.14.0",
"serve": "14.2.4",
"statsig-js": "4.41.0",
"style-loader": "3.3.2",
"swc-loader": "0.2.6",
"tamagui-loader": "1.114.4",
"typescript": "5.3.3",
......
......@@ -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.12.0",
"version": "1.13.0",
"minimum_chrome_version": "116",
"icons": {
"16": "assets/icon16.png",
......
......@@ -31,6 +31,19 @@ const normalizedStories = [
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/
),
},
{
titlePrefix: "",
directory: "../../packages/ui/src",
files: "**/*.mdx",
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.mdx)$/,
// @ts-ignore
req: require.context(
"../../../packages/ui/src",
true,
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.mdx)$/
),
},
];
declare global {
......
......@@ -89,9 +89,9 @@ if (isCI && datadogPropertiesAvailable && !isDetox) {
apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle"
}
def devVersionName = "1.42"
def betaVersionName = "1.42"
def prodVersionName = "1.42"
def devVersionName = "1.43"
def betaVersionName = "1.43"
def prodVersionName = "1.43"
android {
ndkVersion rootProject.ext.ndkVersion
......
......@@ -2204,7 +2204,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2257,7 +2257,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
......@@ -2310,7 +2310,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
......@@ -2363,7 +2363,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
......@@ -2401,7 +2401,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2437,7 +2437,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
......@@ -2472,7 +2472,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
......@@ -2507,7 +2507,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
......@@ -2554,7 +2554,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2600,7 +2600,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
......@@ -2646,7 +2646,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
......@@ -2692,7 +2692,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
......@@ -2734,7 +2734,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2777,7 +2777,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
......@@ -2820,7 +2820,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
......@@ -2863,7 +2863,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
......@@ -2899,7 +2899,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -2937,7 +2937,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3137,7 +3137,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -3181,7 +3181,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
......@@ -3292,7 +3292,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3363,7 +3363,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
......@@ -3474,7 +3474,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3545,7 +3545,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.42;
MARKETING_VERSION = 1.43;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
......@@ -33,7 +33,7 @@
"firestore:deploy:rules": "firebase deploy --only firestore:rules",
"link:assets": "react-native-asset",
"graphql:generate:swift": "cd ios && ./Pods/Apollo/apollo-ios-cli generate",
"check:circular": "../../scripts/check-circular-imports.sh ./src/app/App.tsx 8",
"check:circular": "../../scripts/check-circular-imports.sh ./src/app/App.tsx 2",
"ios": "yarn ios:prebuild && SKIP_BUNDLING=1 react-native run-ios",
"ios:prebuild": "yarn graphql:generate:swift && cd ios/WidgetsCore/MobileSchema && rm -rf !(README.md) && cd ../../.. && yarn graphql:generate:swift && yarn env:local:copy:swift",
"ios:smol": "SKIP_BUNDLING=1 react-native run-ios --simulator=\"iPhone SE (3rd generation)\"",
......
import { DdRumReactNavigationTracking } from '@datadog/mobile-react-navigation'
import {
createNavigationContainerRef,
DefaultTheme,
NavigationContainer as NativeNavigationContainer,
NavigationContainerRefWithCurrent,
......@@ -9,6 +8,7 @@ import { SharedEventName } from '@uniswap/analytics-events'
import React, { FC, PropsWithChildren, useCallback, useState } from 'react'
import { Linking } from 'react-native'
import { useDispatch } from 'react-redux'
import { navigationRef } from 'src/app/navigation/navigationRef'
import { RootParamList } from 'src/app/navigation/types'
import { openDeepLink } from 'src/features/deepLinking/handleDeepLinkSaga'
import { DIRECT_LOG_ONLY_SCREENS } from 'src/features/telemetry/directLogScreens'
......@@ -26,8 +26,6 @@ interface Props {
onReady?: (navigationRef: NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>) => void
}
export const navigationRef = createNavigationContainerRef()
/** Wrapped `NavigationContainer` with telemetry tracing. */
export const NavigationContainer: FC<PropsWithChildren<Props>> = ({ children, onReady }: PropsWithChildren<Props>) => {
const colors = useSporeColors()
......
......@@ -11,8 +11,8 @@ import React, { useEffect } from 'react'
import { DevSettings } from 'react-native'
import { useSelector } from 'react-redux'
import StorybookUIRoot from 'src/../.storybook'
import { navigationRef } from 'src/app/navigation/NavigationContainer'
import { renderHeaderBackButton, renderHeaderBackImage } from 'src/app/navigation/components'
import { navigationRef } from 'src/app/navigation/navigationRef'
import {
AppStackParamList,
AppStackScreenProp,
......
import { createNavigationContainerRef } from '@react-navigation/native'
// this was moved to its own file to avoid circular dependencies
export const navigationRef = createNavigationContainerRef()
import { NavigationAction, NavigationState } from '@react-navigation/core'
import { navigationRef } from 'src/app/navigation/NavigationContainer'
import { navigationRef } from 'src/app/navigation/navigationRef'
import { RootParamList } from 'src/app/navigation/types'
import { logger } from 'utilities/src/logger/logger'
......
import React, { useMemo } from 'react'
import { ScrollView, StyleSheet } from 'react-native'
import { StyleSheet } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { Flex, Text } from 'ui/src'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
import { spacing } from 'ui/src/theme'
......@@ -12,6 +13,11 @@ import { Account } from 'wallet/src/features/wallet/accounts/types'
const ADDRESS_ROW_HEIGHT = 40
interface SortedAddressData {
address: string
balance: number
}
type Portfolio = NonNullable<NonNullable<NonNullable<AccountListQuery['portfolios']>[0]>>
function _AssociatedAccountsList({ accounts }: { accounts: Account[] }): JSX.Element {
......@@ -26,17 +32,27 @@ function _AssociatedAccountsList({ accounts }: { accounts: Account[] }): JSX.Ele
.filter((portfolio): portfolio is Portfolio => Boolean(portfolio))
.map((portfolio) => ({
address: portfolio.ownerAddress,
balance: portfolio.tokensTotalDenominatedValue?.value,
balance: portfolio.tokensTotalDenominatedValue?.value ?? 0,
}))
.sort((a, b) => (b.balance ?? 0) - (a.balance ?? 0))
.sort((a, b) => b.balance - a.balance)
// set max height to around 30% screen size, so we always cut the last visible element
// this way user is aware if there are more elements to see
const accountsScrollViewHeight =
Math.floor((fullHeight * 0.3) / ADDRESS_ROW_HEIGHT) * ADDRESS_ROW_HEIGHT +
ADDRESS_ROW_HEIGHT / 2 +
spacing.spacing12 // 12 is the ScrollView vertical padding
const renderItem = ({ item, index }: { item: SortedAddressData; index: number }): JSX.Element => {
return (
<AssociatedAccountRow
address={item.address}
balance={item.balance}
index={index}
loading={loading}
totalCount={accounts.length}
/>
)
}
return (
<Flex
borderColor="$surface3"
......@@ -46,17 +62,14 @@ function _AssociatedAccountsList({ accounts }: { accounts: Account[] }): JSX.Ele
px="$spacing12"
width="100%"
>
<ScrollView bounces={false} contentContainerStyle={styles.accounts}>
{sortedAddressesByBalance.map(({ address, balance }, index) => (
<AssociatedAccountRow
address={address}
balance={balance}
index={index}
loading={loading}
totalCount={accounts.length}
/>
))}
</ScrollView>
<FlatList
data={sortedAddressesByBalance}
keyExtractor={(item) => item.address}
renderItem={renderItem}
bounces={false}
contentContainerStyle={[styles.accounts, { paddingBottom: spacing.spacing12 }]}
keyboardShouldPersistTaps="handled"
/>
</Flex>
)
}
......
......@@ -2,7 +2,7 @@ import * as ExpoClipboard from 'expo-clipboard'
import { State } from 'react-native-gesture-handler'
import { fireGestureHandler, getByGestureTestId } from 'react-native-gesture-handler/jest-utils'
import { MobileState } from 'src/app/mobileReducer'
import { navigationRef } from 'src/app/navigation/NavigationContainer'
import { navigationRef } from 'src/app/navigation/navigationRef'
import { AccountHeader } from 'src/components/accounts/AccountHeader'
import { fireEvent, render, screen, waitFor, within } from 'src/test/test-utils'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
......
import type { Meta, StoryObj } from '@storybook/react'
import { CopyTextButton } from 'src/components/buttons/CopyTextButton'
import { StorybookTitles } from 'ui/src/storybook'
const meta = {
title: StorybookTitles.Atoms,
title: 'Components/Buttons',
component: CopyTextButton,
} satisfies Meta<typeof CopyTextButton>
......
......@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ListRenderItem, ListRenderItemInfo, StyleSheet } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { FadeIn, FadeOut, useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
import { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
import { useSelector } from 'react-redux'
import { FavoriteTokensGrid } from 'src/components/explore/FavoriteTokensGrid'
import { FavoriteWalletsGrid } from 'src/components/explore/FavoriteWalletsGrid'
......@@ -13,7 +13,7 @@ import { TokenItemData } from 'src/components/explore/TokenItemData'
import { AnimatedBottomSheetFlatList } from 'src/components/layout/AnimatedFlatList'
import { AutoScrollProps } from 'src/components/sortableGrid/types'
import { getTokenMetadataDisplayType } from 'src/features/explore/utils'
import { AnimatedTouchableArea, Flex, Loader, Text, useSporeColors } from 'ui/src'
import { Flex, Loader, Text, TouchableArea, useSporeColors } from 'ui/src'
import { iconSizes, spacing } from 'ui/src/theme'
import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo'
......@@ -191,7 +191,7 @@ function NetworkPillsRow({
const renderItem: ListRenderItem<UniverseChainId> = useCallback(
({ item }: ListRenderItemInfo<UniverseChainId>) => {
return (
<AnimatedTouchableArea entering={FadeIn} exiting={FadeOut} onPress={() => onSelectNetwork(item)}>
<TouchableArea onPress={() => onSelectNetwork(item)}>
<NetworkPill
key={item}
showIcon
......@@ -207,7 +207,7 @@ function NetworkPillsRow({
showBackgroundColor={false}
textVariant="buttonLabel3"
/>
</AnimatedTouchableArea>
</TouchableArea>
)
},
[colors.neutral1.val, onSelectNetwork, selectedNetwork],
......
......@@ -176,6 +176,8 @@ export function SearchResultsSection({
return (
<Flex grow gap="$spacing8" pb="$spacing36">
<AnimatedBottomSheetFlashList
// when switching networks, we want to rerender the list to prevent any layout misalignments
key={selectedChain}
estimatedItemSize={ESTIMATED_ITEM_SIZE}
ListEmptyComponent={
<AnimatedFlex entering={FadeIn} exiting={FadeOut} gap="$spacing8" mx="$spacing20">
......
import React, { useEffect, useState } from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import Svg, { Circle } from 'react-native-svg'
import { BackButtonView } from 'src/components/layout/BackButtonView'
import { SeedPhraseDisplay } from 'src/components/mnemonic/SeedPhraseDisplay'
import { APP_STORE_LINK } from 'src/constants/urls'
import { UpgradeStatus } from 'src/features/forceUpgrade/types'
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { Button, Flex, Image, Text, TouchableArea, isWeb, useSporeColors } from 'ui/src'
import { UNISWAP_LOGO } from 'ui/src/assets'
import { imageSizes } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal'
import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types'
import { NewTag } from 'uniswap/src/components/pill/NewTag'
import { DynamicConfigs, ForceUpgradeConfigKey } from 'uniswap/src/features/gating/configs'
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
......@@ -19,7 +22,7 @@ export function ForceUpgradeModal(): JSX.Element {
const { t } = useTranslation()
const colors = useSporeColors()
const forceUpgradeStatusString = useDynamicConfigValue(
DynamicConfigs.MobileForceUpgrade,
DynamicConfigs.ForceUpgrade,
ForceUpgradeConfigKey.Status,
'' as string,
)
......@@ -64,26 +67,83 @@ export function ForceUpgradeModal(): JSX.Element {
// the force upgrade screen on error, hence we fallback to the global error boundary
return (
<>
<WarningModal
acknowledgeText={t('forceUpgrade.action.confirm')}
<Modal
backgroundColor={colors.surface1.val}
hideHandlebar={upgradeStatus === UpgradeStatus.Required}
isDismissible={upgradeStatus !== UpgradeStatus.Required}
isOpen={isVisible}
modalName={ModalName.ForceUpgradeModal}
severity={WarningSeverity.High}
title={t('forceUpgrade.title')}
isModalOpen={isVisible}
name={ModalName.ForceUpgradeModal}
onClose={onClose}
onAcknowledge={onPressConfirm}
>
<Text color="$neutral2" textAlign="center" variant="body2">
{t('forceUpgrade.description')}
</Text>
{mnemonicId && (
<Text color="$accent1" variant="buttonLabel2" onPress={onPressViewRecovery}>
{t('forceUpgrade.action.recoveryPhrase')}
</Text>
)}
</WarningModal>
<Flex
centered
gap="$spacing24"
pb={isWeb ? '$none' : '$spacing12'}
pt={upgradeStatus === UpgradeStatus.Required ? '$spacing24' : '$spacing12'}
px={isWeb ? '$none' : '$spacing24'}
>
<Flex
centered
width="100%"
height={160}
borderRadius="$rounded16"
borderWidth={1}
borderColor="$surface3"
overflow="hidden"
>
<Flex
centered
borderRadius="$rounded16"
style={{
shadowColor: colors.accent1.val,
elevation: 8,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 20,
}}
borderWidth={1}
borderColor="$surface3"
elevationAndroid={8}
>
<Flex position="relative">
<Image
height={imageSizes.image64}
resizeMode="contain"
source={UNISWAP_LOGO}
width={imageSizes.image64}
/>
<Flex position="absolute" top={-15} right={-8} transform={[{ rotate: '10deg' }]}>
<NewTag exclamation backgroundColor="$accent1" textColor="$white" />
</Flex>
</Flex>
</Flex>
<BackgroundDotPattern />
</Flex>
<Flex gap="$spacing8">
<Text textAlign="center" variant="subheading1">
{t('forceUpgrade.title')}
</Text>
<Text color="$neutral2" textAlign="center" variant="body3">
{t('forceUpgrade.description')}
</Text>
</Flex>
<Flex centered gap="$spacing8" pb={isWeb ? '$none' : '$spacing12'} width="100%">
<Button size="medium" theme="primary" width="100%" onPress={onPressConfirm}>
<Text color="$white" variant="buttonLabel2">
{t('forceUpgrade.action.confirm')}
</Text>
</Button>
{mnemonicId && (
<Button size="medium" theme="secondary" width="100%" onPress={onPressViewRecovery}>
<Text color="$neutral1" variant="buttonLabel2">
{t('forceUpgrade.action.recoveryPhrase')}
</Text>
</Button>
)}
</Flex>
</Flex>
</Modal>
{mnemonicId && showSeedPhrase && (
<Modal fullScreen backgroundColor={colors.surface1.val} name={ModalName.ForceUpgradeModal} onClose={onDismiss}>
<Flex fill gap="$spacing16" px="$spacing24" py="$spacing24">
......@@ -103,3 +163,48 @@ export function ForceUpgradeModal(): JSX.Element {
}
const BACK_BUTTON_SIZE = 24
function BackgroundDotPattern(): JSX.Element {
const colors = useSporeColors()
const dotGrid = useMemo(() => {
return Array.from({ length: 100 }).map((_, row) => {
return Array.from({ length: 100 }).map((__, col) => {
const x = col * 2 + 1
const y = row * 2 + 1
const distX = Math.abs(x - 50)
const distY = Math.abs(y - 50)
const dist = Math.sqrt(distX * distX + distY * distY)
if (dist < 45) {
const size = 0.1 + (45 - dist) / 20
return <Circle key={`${row}-${col}`} cx={`${x}%`} cy={`${y}%`} r={size} fill={colors.pinkThemed.val} />
}
return null
})
})
}, [colors])
return (
<Svg width={400} height={400} style={[styles.backgroundPattern, styles.centered]}>
{dotGrid}
</Svg>
)
}
const styles = StyleSheet.create({
backgroundPattern: {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
zIndex: -1,
},
centered: {
left: '50%',
top: '50%',
transform: [{ translateX: -200 }, { translateY: -200 }],
},
})
import { expectSaga } from 'redux-saga-test-plan'
import { call } from 'redux-saga/effects'
import { navigationRef } from 'src/app/navigation/NavigationContainer'
import { navigationRef } from 'src/app/navigation/navigationRef'
import {
handleDeepLink,
handleUniswapAppDeepLink,
......
......@@ -15,6 +15,7 @@ import {
UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM,
UNISWAP_WALLETCONNECT_URL,
} from 'src/features/deepLinking/constants'
import { handleOffRampReturnLink } from 'src/features/deepLinking/handleOffRampReturnLinkSaga'
import { handleOnRampReturnLink } from 'src/features/deepLinking/handleOnRampReturnLinkSaga'
import { handleSwapLink } from 'src/features/deepLinking/handleSwapLinkSaga'
import { handleTransactionLink } from 'src/features/deepLinking/handleTransactionLinkSaga'
......@@ -207,6 +208,7 @@ export function* handleDeepLink(action: ReturnType<typeof openDeepLink>) {
const screen = url.searchParams.get('screen')
const userAddress = url.searchParams.get('userAddress')
const fiatOnRamp = url.searchParams.get('fiatOnRamp') === 'true'
const fiatOffRamp = url.searchParams.get('fiatOffRamp') === 'true'
const activeAccount = yield* select(selectActiveAccount)
if (!activeAccount) {
......@@ -271,6 +273,8 @@ export function* handleDeepLink(action: ReturnType<typeof openDeepLink>) {
case 'transaction':
if (fiatOnRamp) {
yield* call(handleOnRampReturnLink)
} else if (fiatOffRamp) {
yield* call(handleOffRampReturnLink, url)
} else {
yield* call(handleTransactionLink)
}
......
import { navigate } from 'src/app/navigation/rootNavigation'
import { openModal } from 'src/features/modals/modalSlice'
import { call, put } from 'typed-redux-saga'
import { AssetType, TradeableAsset } from 'uniswap/src/entities/assets'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FiatOffRampMetaData, OffRampTransferDetailsResponse } from 'uniswap/src/features/fiatOnRamp/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TransactionScreen } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext'
import { CurrencyField } from 'uniswap/src/types/currency'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { createTransactionId } from 'uniswap/src/utils/createTransactionId'
import { logger } from 'utilities/src/logger/logger'
import { fetchOffRampTransferDetails } from 'wallet/src/features/fiatOnRamp/api'
import { dismissInAppBrowser } from 'wallet/src/utils/linking'
export function* handleOffRampReturnLink(url: URL) {
try {
yield* call(_handleOffRampReturnLink, url)
} catch (error) {
// TODO: handle error in UI
// Alert.alert(i18n.t('walletConnect.error.general.title'), i18n.t('walletConnect.error.general.message'))
// yield* put(openModal({ name: ModalName.Send, initialState: initialSendState }))
}
}
function* _handleOffRampReturnLink(url: URL) {
const externalTransactionId = url.searchParams.get('externalTransactionId')
if (!externalTransactionId) {
throw new Error('Missing externalTransactionId in fiat offramp deep link')
}
let offRampTransferDetails: OffRampTransferDetailsResponse | undefined
try {
offRampTransferDetails = yield* call(fetchOffRampTransferDetails, externalTransactionId)
} catch (error) {
logger.error(error, {
tags: { file: 'handleOffRampReturnLinkSaga', function: 'handleOffRampReturnLink' },
})
throw new Error('Failed to fetch offramp transfer details')
}
if (!offRampTransferDetails) {
throw new Error('Missing offRampTransferDetails in fiat offramp deep link')
}
const { tokenAddress, baseCurrencyCode, baseCurrencyAmount, depositWalletAddress, logos, provider, chainId } =
offRampTransferDetails
const currencyTradeableAsset: TradeableAsset = {
address: tokenAddress,
chainId: Number(chainId) as UniverseChainId,
type: AssetType.Currency,
}
const fiatOffRampMetaData: FiatOffRampMetaData = {
name: provider,
logoUrl: logos.lightLogo,
// TODO: update activity feed once transaction is submitted
onSubmitCallback: () => {},
moonpayCurrencyCode: baseCurrencyCode,
meldCurrencyCode: baseCurrencyCode,
}
const txnId = createTransactionId()
const initialSendState = {
txId: txnId,
[CurrencyField.INPUT]: currencyTradeableAsset,
[CurrencyField.OUTPUT]: null,
exactCurrencyField: CurrencyField.INPUT,
exactAmountToken: baseCurrencyAmount.toString(),
focusOnCurrencyField: null,
recipient: depositWalletAddress,
isFiatInput: false,
showRecipientSelector: false,
fiatOffRampMetaData,
sendScreen: TransactionScreen.Review,
}
yield* call(navigate, MobileScreens.Home)
yield* put(openModal({ name: ModalName.Send, initialState: initialSendState }))
yield* call(dismissInAppBrowser)
}
......@@ -7,6 +7,7 @@ import { FiatOnRampModalState } from 'src/screens/FiatOnRampModalState'
import { ReceiveCryptoModalState } from 'src/screens/ReceiveCryptoModalState'
import { FORServiceProvider } from 'uniswap/src/features/fiatOnRamp/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TransactionScreen } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext'
import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
......@@ -34,7 +35,7 @@ export interface ModalsState {
[ModalName.RemoveWallet]: AppModalState<RemoveWalletModalState>
[ModalName.RestoreWallet]: AppModalState<undefined>
[ModalName.Scantastic]: AppModalState<ScantasticModalState>
[ModalName.Send]: AppModalState<TransactionState>
[ModalName.Send]: AppModalState<TransactionState & { sendScreen: TransactionScreen }>
[ModalName.Swap]: AppModalState<TransactionState>
[ModalName.TestnetSwitchModal]: AppModalState<TestnetSwitchModalState>
[ModalName.UnitagsIntro]: AppModalState<{
......
......@@ -9,6 +9,7 @@ import { TestnetSwitchModalState } from 'src/features/testnetMode/TestnetSwitchM
import { FiatOnRampModalState } from 'src/screens/FiatOnRampModalState'
import { ReceiveCryptoModalState } from 'src/screens/ReceiveCryptoModalState'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TransactionScreen } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext'
import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { getKeys } from 'utilities/src/primitives/objects'
......@@ -80,7 +81,12 @@ type WalletConnectModalParams = {
type SwapModalParams = { name: typeof ModalName.Swap; initialState?: TransactionState }
type SendModalParams = { name: typeof ModalName.Send; initialState?: TransactionState }
type SendModalParams = {
name: typeof ModalName.Send
initialState?: TransactionState & {
sendScreen?: TransactionScreen
}
}
type UnitagsIntroParams = {
name: typeof ModalName.UnitagsIntro
......
......@@ -115,9 +115,10 @@ describe('useNotificationToggle', () => {
result.current.toggle()
await new Promise(requestAnimationFrame)
})
expect(mockDispatch).toHaveBeenCalled()
expect(result.current.isEnabled).toBe(false)
await waitFor(() => {
expect(mockDispatch).toHaveBeenCalled()
expect(result.current.isEnabled).toBe(false)
})
})
it('handles OS permission prompt flow successfully', async () => {
......@@ -133,9 +134,11 @@ describe('useNotificationToggle', () => {
await new Promise(requestAnimationFrame)
})
expect(mockPermissionPrompt).toHaveBeenCalled()
expect(mockDispatch).toHaveBeenCalled()
expect(result.current.isEnabled).toBe(true)
await waitFor(() => {
expect(mockPermissionPrompt).toHaveBeenCalled()
expect(mockDispatch).toHaveBeenCalled()
expect(result.current.isEnabled).toBe(true)
})
})
it('handles OS permission prompt flow failure', async () => {
......
......@@ -44,16 +44,20 @@ export function SendFlow(): JSX.Element {
onClose={onClose}
>
<SendContextProvider prefilledTransactionState={initialState}>
<CurrentScreen />
<CurrentScreen screenOverride={initialState?.sendScreen} />
</SendContextProvider>
</TransactionModal>
)
}
function CurrentScreen(): JSX.Element {
const { screen } = useTransactionModalContext()
function CurrentScreen({ screenOverride }: { screenOverride?: TransactionScreen }): JSX.Element {
const { screen, setScreen } = useTransactionModalContext()
const { recipient } = useSendContext()
if (screenOverride) {
setScreen(screenOverride)
}
// If no recipient, force full screen recipient select. Need to render this outside of `SendFormScreen` to ensure that
// the modals are rendered correctly, and animations can properly measure the available space for the decimal pad.
if (!recipient) {
......
......@@ -106,7 +106,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element |
refundWalletAddress: activeAccountAddress,
externalCustomerId: activeAccountAddress,
externalSessionId: externalTransactionId,
redirectUrl: `${uniswapUrls.redirectUrlBase}?screen=transaction&fiatOffRamp=true&userAddress=${activeAccountAddress}`,
redirectUrl: `${uniswapUrls.redirectUrlBase}?screen=transaction&fiatOffRamp=true&userAddress=${activeAccountAddress}&externalTransactionId=${externalTransactionId}`,
}
: skipToken,
)
......
......@@ -366,6 +366,14 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
})
const onSelectCurrency = (currency: FORCurrencyOrBalance): void => {
if (isTokenInputMode) {
resetAmount()
} else {
setSelectedQuote(undefined)
// This is done for formatting reasons. The existing value may change if max decimals of new currency is different
onChangeValue(value, 'changeAsset')
}
setShowTokenSelector(false)
if (isSupportedFORCurrency(currency)) {
setQuoteCurrency(currency)
......@@ -427,14 +435,18 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
}
}, [navigateToSwapFlow, unsupportedCurrency])
const onPillToggle = (option: string | number): void => {
setIsOffRamp(option === RampToggle.SELL)
const resetAmount = useCallback(() => {
setValue('')
setFiatAmount(0)
setTokenAmount(0)
valueRef.current = ''
resetSelection({ start: 0 })
setSelectedQuote(undefined)
}, [setValue, setFiatAmount, setTokenAmount, valueRef, resetSelection, setSelectedQuote])
const onPillToggle = (option: string | number): void => {
setIsOffRamp(option === RampToggle.SELL)
resetAmount()
setQuoteCurrency(defaultCurrency)
sendAnalyticsEvent(FiatOffRampEventName.FORBuySellToggled, {
......
......@@ -12,7 +12,7 @@ import {
import React, { PropsWithChildren } from 'react'
import { MobileWalletNavigationProvider } from 'src/app/MobileWalletNavigationProvider'
import type { MobileState } from 'src/app/mobileReducer'
import { navigationRef } from 'src/app/navigation/NavigationContainer'
import { navigationRef } from 'src/app/navigation/navigationRef'
import { store as appStore, persistedReducer } from 'src/app/store'
import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext'
import { Resolvers } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
......
......@@ -13,6 +13,7 @@ ignores: [
'process',
'madge',
# Dependencies that depcheck thinks are missing but are actually present or never used
'stories',
## package.json scripts
'esbuild-register',
## GraphQL
......@@ -30,6 +31,17 @@ ignores: [
'@babel/preset-env',
'eslint-plugin-import',
'terser-webpack-plugin',
## Storybook
'@svgr/webpack',
'ts-loader',
'@chromatic-com/storybook',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-onboarding',
'@storybook/blocks',
'@storybook/preset-create-react-app',
'eslint-plugin-storybook',
'prop-types',
## Testing
'@types/testing-library__cypress',
## i18n
......
......@@ -3,3 +3,4 @@ babel.config.js
jest.config.js
metro.config.js
node_modules
.storybook/stories
......@@ -8,7 +8,7 @@ rulesDirPlugin.RULES_DIR = 'eslint_rules'
module.exports = {
root: true,
extends: ['@uniswap/eslint-config/react'],
extends: ['@uniswap/eslint-config/react', 'plugin:storybook/recommended'],
plugins: ['rulesdir'],
rules: {
......
......@@ -54,3 +54,5 @@ cypress/screenshots
.vercel
.wrangler
*storybook.log
import type { StorybookConfig } from '@storybook/react-webpack5'
import { dirname, join, resolve } from 'path'
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')))
}
const config: StorybookConfig = {
stories: ['../../../packages/ui/**/*.stories.?(ts|tsx)', '../../../packages/ui/**/*.mdx'],
addons: [
getAbsolutePath('@storybook/preset-create-react-app'),
getAbsolutePath('@storybook/addon-onboarding'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-interactions'),
],
framework: {
name: getAbsolutePath('@storybook/react-webpack5'),
options: {},
},
staticDirs: ['../public'],
webpackFinal: (config) => {
// This modifies the existing image rule to exclude `.svg` files
// since we handle those with `@svgr/webpack`.
const imageRule =
config?.module?.rules &&
config.module.rules.find((rule) => {
if (rule && typeof rule !== 'string' && rule.test instanceof RegExp) {
return rule.test.test('.svg')
}
return
})
if (imageRule && typeof imageRule !== 'string') {
imageRule.exclude = /\.svg$/i
}
config?.module?.rules &&
config.module.rules.push({
test: /\.svg$/i,
use: ['@svgr/webpack'],
})
config?.module?.rules &&
config.module.rules.push({
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
})
config.resolve ??= {}
config.resolve = {
...config.resolve,
alias: {
...config?.resolve?.alias,
'react-native$': 'react-native-web',
},
}
config.resolve.modules = [resolve(__dirname, '../src'), 'node_modules']
return config
},
}
export default config
import type { Preview } from '@storybook/react'
import { TamaguiProvider } from '../src/theme/tamaguiProvider'
const preview: Preview = {
decorators: [
(Story) => (
<TamaguiProvider>
{/* 👇 Decorators in Storybook also accept a function. Replace <Story/> with Story() to enable it */}
<Story />
</TamaguiProvider>
),
],
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}
export default preview
......@@ -5,7 +5,7 @@ const { execSync } = require('child_process')
const { readFileSync } = require('fs')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const ModuleScopePlugin = require(path.resolve(__dirname, '..', '..','node_modules/react-scripts/node_modules/react-dev-utils/ModuleScopePlugin'))
const { IgnorePlugin, ProvidePlugin, DefinePlugin } = require('webpack')
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
......
......@@ -30,7 +30,9 @@
"test:cloud": "yarn jest functions --config=functions/jest.config.json",
"cypress:open": "cypress open --browser chrome --e2e",
"cypress:run": "cypress run --browser chrome --e2e",
"deduplicate": "yarn-deduplicate --strategy=highest"
"deduplicate": "yarn-deduplicate --strategy=highest",
"storybook:run": "storybook dev -p 6006",
"storybook:build": "storybook build"
},
"husky": {
"hooks": {
......@@ -70,10 +72,19 @@
},
"devDependencies": {
"@babel/preset-env": "7.23.3",
"@chromatic-com/storybook": "3.2.2",
"@cloudflare/workers-types": "4.20231025.0",
"@craco/craco": "7.1.0",
"@crowdin/cli": "3.14.0",
"@ethersproject/experimental": "5.7.0",
"@storybook/addon-essentials": "8.4.2",
"@storybook/addon-interactions": "8.4.2",
"@storybook/addon-onboarding": "8.4.2",
"@storybook/blocks": "8.4.2",
"@storybook/preset-create-react-app": "8.4.2",
"@storybook/react": "8.4.2",
"@storybook/react-webpack5": "8.4.2",
"@storybook/test": "8.4.2",
"@swc/core": "1.3.72",
"@swc/jest": "0.2.29",
"@swc/plugin-styled-components": "1.5.97",
......@@ -114,12 +125,14 @@
"concurrently": "8.2.2",
"cypress": "12.17.4",
"cypress-hardhat": "2.5.3",
"depcheck": "1.4.7",
"dotenv": "16.0.3",
"dotenv-cli": "7.1.0",
"esbuild-register": "3.6.0",
"eslint": "8.44.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-rulesdir": "0.2.2",
"eslint-plugin-storybook": "0.8.0",
"hardhat": "2.22.16",
"husky": "8.0.3",
"jest": "29.7.0",
......@@ -133,11 +146,13 @@
"path-browserify": "1.0.1",
"postinstall-postinstall": "2.1.0",
"process": "0.11.10",
"prop-types": "15.8.1",
"react-scripts": "5.0.1",
"resize-observer-polyfill": "1.5.1",
"serve": "14.2.4",
"source-map-explorer": "2.5.3",
"start-server-and-test": "2.0.0",
"storybook": "8.4.2",
"swc-loader": "0.2.6",
"terser": "5.24.0",
"terser-webpack-plugin": "5.3.9",
......@@ -172,6 +187,7 @@
"@sentry/core": "7.80.0",
"@sentry/react": "7.80.0",
"@sentry/types": "7.80.0",
"@svgr/webpack": "8.0.1",
"@tamagui/core": "1.114.4",
"@tamagui/portal": "1.114.4",
"@tamagui/react-native-svg": "1.114.4",
......@@ -269,6 +285,7 @@
"styled-components": "5.3.11",
"tamagui": "1.114.4",
"tiny-invariant": "1.3.1",
"ts-loader": "9.5.1",
"typed-redux-saga": "1.5.0",
"ui": "workspace:^",
"uniswap": "workspace:^",
......
......@@ -126,16 +126,4 @@
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://app.uniswap.org/positions/create</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/positions</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
</urlset>
<!DOCTYPE html>
<html translate="no" style="overflow-x: hidden;">
<html translate="no">
<head>
<meta charset="utf-8" />
......@@ -83,6 +83,17 @@
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -10,9 +10,11 @@ import styled, { useTheme } from 'lib/styled-components'
import { Slash } from 'react-feather'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
import { ExternalLink, ThemedText } from 'theme/components'
import { AdaptiveWebModal, Flex } from 'ui/src'
import { Flex } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice'
import { Plural, Trans, t } from 'uniswap/src/i18n'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
......@@ -94,7 +96,7 @@ export function CancelOrdersDialog(
(cancelState === CancellationState.CANCELLED || cancelState === CancellationState.PENDING_CONFIRMATION) &&
cancelTxHash
return (
<AdaptiveWebModal isOpen onClose={onCancel} maxHeight="90vh" p={0}>
<Modal name={ModalName.CancelOrders} isModalOpen onClose={onCancel} maxHeight="90vh" padding={0}>
<Container gap="lg">
<ModalHeader closeModal={onCancel} />
<LogoContainer>{icon}</LogoContainer>
......@@ -116,7 +118,7 @@ export function CancelOrdersDialog(
)}
</Row>
</Container>
</AdaptiveWebModal>
</Modal>
)
} else if (cancelState === CancellationState.REVIEWING_CANCELLATION) {
return (
......
......@@ -32,9 +32,9 @@ import { useOrder } from 'state/signatures/hooks'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
import { Divider, ThemedText } from 'theme/components'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { AdaptiveWebModal } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants'
import { InterfaceEventNameLocal, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans } from 'uniswap/src/i18n'
import { CurrencyField } from 'uniswap/src/types/currency'
......@@ -382,11 +382,12 @@ export function OffchainActivityModal() {
cancelTxHash={cancelTxHash}
/>
)}
<AdaptiveWebModal
<Modal
name={ModalName.OffchainActivity}
maxWidth={375}
isOpen={!!selectedOrderAtomValue?.modalOpen && cancelState === CancellationState.NOT_STARTED}
isModalOpen={!!selectedOrderAtomValue?.modalOpen && cancelState === CancellationState.NOT_STARTED}
onClose={reset}
p={0}
padding={0}
>
<Wrapper data-testid="offchain-activity-modal">
<Row justify="space-between">
......@@ -405,7 +406,7 @@ export function OffchainActivityModal() {
/>
)}
</Wrapper>
</AdaptiveWebModal>
</Modal>
</>
)
}
......@@ -7,42 +7,6 @@ exports[`OrderContent should render without error, filled order 1`] = `
min-width: 0;
}
.c2 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c11 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c6 {
color: #222222;
-webkit-letter-spacing: -0.01em;
......@@ -93,6 +57,42 @@ exports[`OrderContent should render without error, filled order 1`] = `
background-color: #22222212;
}
.c2 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c11 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -463,42 +463,6 @@ exports[`OrderContent should render without error, limit order 1`] = `
min-width: 0;
}
.c2 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c11 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c6 {
color: #222222;
-webkit-letter-spacing: -0.01em;
......@@ -621,6 +585,42 @@ exports[`OrderContent should render without error, limit order 1`] = `
background-color: transparent;
}
.c2 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c11 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -1035,42 +1035,6 @@ exports[`OrderContent should render without error, open order 1`] = `
min-width: 0;
}
.c2 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c11 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c6 {
color: #222222;
-webkit-letter-spacing: -0.01em;
......@@ -1174,6 +1138,42 @@ exports[`OrderContent should render without error, open order 1`] = `
background-color: transparent;
}
.c2 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c11 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......
......@@ -7,6 +7,29 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = `
min-width: 0;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c1 {
width: 100%;
display: -webkit-box;
......@@ -42,29 +65,6 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = `
gap: 4px;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c6 {
display: -webkit-box;
display: -webkit-flex;
......
......@@ -3,7 +3,9 @@ import MobileAppLogo from 'assets/svg/uniswap_app_logo.svg'
import { useConnect } from 'hooks/useConnect'
import { useCallback, useEffect, useState } from 'react'
import { CloseIcon } from 'theme/components'
import { AdaptiveWebModal, Button, Flex, Image, QRCodeDisplay, Separator, Text, useSporeColors } from 'ui/src'
import { Button, Flex, Image, QRCodeDisplay, Separator, Text, useSporeColors } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { useTranslation } from 'uniswap/src/i18n'
import { isWebAndroid, isWebIOS } from 'utilities/src/platform'
......@@ -47,7 +49,7 @@ export default function UniwalletModal() {
const colors = useSporeColors()
return (
<AdaptiveWebModal isOpen={open} onClose={close} p={0}>
<Modal name={ModalName.UniWalletConnect} isModalOpen={open} onClose={close} padding={0}>
<Flex shrink grow p="$spacing20">
<Flex row justifyContent="space-between">
<Text variant="subheading1">{t('account.drawer.modal.scan')}</Text>
......@@ -87,6 +89,6 @@ export default function UniwalletModal() {
</Button>
</Flex>
</Flex>
</AdaptiveWebModal>
</Modal>
)
}
......@@ -39,6 +39,95 @@ exports[`AccountDrawer tests AccountDrawer default styles 1`] = `
flex: 1;
}
.c36 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c37 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c37:hover {
opacity: 0.6;
}
.c37:active {
opacity: 0.4;
}
.c4 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c10 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 16px;
}
.c11 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c26 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.c8 {
width: 100%;
display: -webkit-box;
......@@ -158,95 +247,6 @@ exports[`AccountDrawer tests AccountDrawer default styles 1`] = `
margin: !important;
}
.c36 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c37 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c37:hover {
opacity: 0.6;
}
.c37:active {
opacity: 0.4;
}
.c4 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c10 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 16px;
}
.c11 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c26 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.c32 {
display: -webkit-box;
display: -webkit-flex;
......@@ -730,13 +730,16 @@ exports[`AccountDrawer tests AccountDrawer default styles 1`] = `
class="c6"
data-testid="wallet-modal"
>
<div
class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-column"
/>
<span
class="font_unset _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-pre-wrap _position-absolute _width-1px _height-1px _mt--1px _mr--1px _mb--1px _ml--1px _zIndex--10000 _overflowX-hidden _overflowY-hidden _opacity-1e-8 _pointerEvents-none"
>
<h2
class="is_DialogTitle font_heading _display-inline _boxSizing-border-box _wordWrap-break-word _fontFamily-f-family _color-color _fontWeight-f-weight-me3083741 _fontSize-f-size-medi3736 _lineHeight-f-lineHeigh507465454 _userSelect-auto _whiteSpace-normal _mt-0px _mr-0px _mb-0px _ml-0px"
data-disable-theme="true"
id="title-:r0:"
id="title-:r1:"
role="heading"
/>
</span>
......
......@@ -7,11 +7,13 @@ import { useCallback } from 'react'
import { useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { ThemedText } from 'theme/components'
import { AdaptiveWebModal, Flex, QRCodeDisplay, Text, useSporeColors } from 'ui/src'
import { Flex, QRCodeDisplay, Text, useSporeColors } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos'
import { useAddressColorProps } from 'uniswap/src/features/address/color'
import { useOrderedChainIds } from 'uniswap/src/features/chains/hooks/useOrderedChainIds'
import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/features/chains/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks'
import { Trans } from 'uniswap/src/i18n'
......@@ -35,7 +37,7 @@ export function AddressQRModal({ accountAddress }: { accountAddress: Address })
}, [toggleModal, openReceiveCryptoModal])
return (
<AdaptiveWebModal isOpen={isOpen} onClose={toggleModal} width={420}>
<Modal isModalOpen={isOpen} onClose={toggleModal} maxWidth={420} name={ModalName.AddressQR}>
<Flex pb="$spacing16" gap="$spacing24">
<GetHelpHeader goBack={goBack} closeModal={toggleModal} />
<Flex gap="$spacing12">
......@@ -76,6 +78,6 @@ export function AddressQRModal({ accountAddress }: { accountAddress: Address })
<NetworkLogos chains={orderedChainIds} />
</Flex>
</Flex>
</AdaptiveWebModal>
</Modal>
)
}
import { RowBetween } from 'components/deprecated/Row'
import styled, { DefaultTheme } from 'lib/styled-components'
import { darken } from 'polished'
import { forwardRef } from 'react'
import { ChevronDown } from 'react-feather'
import { ButtonProps as ButtonPropsOriginal, Button as RebassButton } from 'rebass/styled-components'
export { default as LoadingButtonSpinner } from './LoadingButtonSpinner'
......@@ -306,17 +304,6 @@ export function ButtonError({ error, ...rest }: { error?: boolean } & BaseButton
}
}
export function ButtonDropdownLight({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
return (
<ButtonOutlined {...rest} disabled={disabled}>
<RowBetween>
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
<ChevronDown size={24} />
</RowBetween>
</ButtonOutlined>
)
}
export enum ButtonSize {
small,
medium,
......
import { Currency } from '@uniswap/sdk-core'
import { Currency, Percent } from '@uniswap/sdk-core'
import { AxisRight } from 'components/Charts/ActiveLiquidityChart/AxisRight'
import { Brush2 } from 'components/Charts/ActiveLiquidityChart/Brush2'
import { HorizontalArea } from 'components/Charts/ActiveLiquidityChart/HorizontalArea'
......@@ -7,7 +7,9 @@ import { TickTooltip } from 'components/Charts/ActiveLiquidityChart/TickTooltip'
import { ChartEntry } from 'components/LiquidityChartRangeInput/types'
import { max as getMax, scaleLinear } from 'd3'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useSporeColors } from 'ui/src'
import { Flex, Text, useSporeColors } from 'ui/src'
import { opacify } from 'ui/src/theme'
import { useFormatter } from 'utils/formatNumbers'
const xAccessor = (d: ChartEntry) => d.activeLiquidity
const yAccessor = (d: ChartEntry) => d.price0
......@@ -53,6 +55,11 @@ function findClosestElementBinarySearch(data: ChartEntry[], target?: number) {
return closestElement
}
function scaleToInteger(a: number, precision = 18) {
const scaleFactor = Math.pow(10, precision)
return Math.round(a * scaleFactor)
}
/**
* A horizontal version of the active liquidity area chart, which uses the
* x-y coordinate plane to show the data, but with the axes flipped so lower
......@@ -69,6 +76,8 @@ export function ActiveLiquidityChart2({
brushDomain,
onBrushDomainChange,
disableBrushInteraction,
showDiffIndicators,
isMobile,
}: {
id?: string
currency0: Currency
......@@ -80,10 +89,13 @@ export function ActiveLiquidityChart2({
max?: number
}
disableBrushInteraction?: boolean
showDiffIndicators?: boolean
dimensions: { width: number; height: number; contentWidth: number; axisLabelPaneWidth: number }
brushDomain?: [number, number]
onBrushDomainChange: (domain: [number, number], mode: string | undefined) => void
isMobile?: boolean
}) {
const { formatPercent } = useFormatter()
const colors = useSporeColors()
const svgRef = useRef<SVGSVGElement | null>(null)
const [hoverY, setHoverY] = useState<number>()
......@@ -124,6 +136,9 @@ export function ActiveLiquidityChart2({
}
}, [brushDomain, onBrushDomainChange, yScale])
const southHandleInView = brushDomain && yScale(brushDomain[0]) >= 0 && yScale(brushDomain[0]) <= height
const northHandleInView = brushDomain && yScale(brushDomain[1]) >= 0 && yScale(brushDomain[1]) <= height
return (
<>
{hoverY && hoveredTick && (
......@@ -138,6 +153,42 @@ export function ActiveLiquidityChart2({
currency1={currency1}
/>
)}
{showDiffIndicators && (
<>
{southHandleInView && (
<Flex
borderRadius="$rounded12"
backgroundColor="$surface2"
borderColor="$surface3"
borderWidth={1}
p="$padding8"
position="absolute"
left={0}
top={yScale(brushDomain[0]) - 16}
>
<Text variant="body4">
{formatPercent(new Percent(scaleToInteger(brushDomain[0] - current), scaleToInteger(current)))}
</Text>
</Flex>
)}
{northHandleInView && (
<Flex
borderRadius="$rounded12"
backgroundColor="$surface2"
borderColor="$surface3"
borderWidth={1}
p="$padding8"
position="absolute"
left={0}
top={yScale(brushDomain[1]) - 16}
>
<Text variant="body4">
{formatPercent(new Percent(scaleToInteger(brushDomain[1] - current), scaleToInteger(current)))}
</Text>
</Flex>
)}
</>
)}
<svg
ref={svgRef}
width="100%"
......@@ -186,8 +237,8 @@ export function ActiveLiquidityChart2({
xValue={xAccessor}
yValue={yAccessor}
brushDomain={brushDomain}
fill={brushDomain ? colors.neutral1.val : colors.accent1.val}
selectedFill={colors.accent1.val}
fill={opacify(isMobile ? 10 : 100, brushDomain ? colors.neutral1.val : colors.accent1.val)}
selectedFill={opacify(isMobile ? 10 : 100, colors.accent1.val)}
containerHeight={height}
containerWidth={width - axisLabelPaneWidth}
/>
......@@ -209,14 +260,16 @@ export function ActiveLiquidityChart2({
/>
)}
<AxisRight
yScale={yScale}
offset={width - contentWidth}
min={brushDomain?.[0]}
current={current}
max={brushDomain?.[1]}
height={height}
/>
{isMobile ? null : (
<AxisRight
yScale={yScale}
offset={width - contentWidth}
min={brushDomain?.[0]}
current={current}
max={brushDomain?.[1]}
height={height}
/>
)}
</g>
<Brush2
......
import { NumberValue, ScaleLinear, axisRight, Axis as d3Axis, select } from 'd3'
import styled from 'lib/styled-components'
import { useMemo } from 'react'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const StyledGroup = styled.g`
line {
......@@ -12,7 +13,7 @@ const StyledGroup = styled.g`
}
`
const TEXT_Y_OFFSET = 10
const TEXT_Y_OFFSET = 5
const Axis = ({
axisGenerator,
......@@ -61,6 +62,7 @@ export const AxisRight = ({
current?: number
max?: number
}) => {
const { formatNumber } = useFormatter()
const tickValues = useMemo(() => {
const minCoordinate = min ? yScale(min) : undefined
const maxCoordinate = max ? yScale(max) : undefined
......@@ -76,7 +78,18 @@ export const AxisRight = ({
return (
<StyledGroup transform={`translate(${offset}, 0)`}>
<Axis axisGenerator={axisRight(yScale).tickValues(tickValues)} height={height} yScale={yScale} />
<Axis
axisGenerator={axisRight(yScale)
.tickValues(tickValues)
.tickFormat((d) =>
formatNumber({
input: d as number,
type: NumberType.TokenQuantityStats,
}),
)}
height={height}
yScale={yScale}
/>
</StyledGroup>
)
}
......@@ -68,13 +68,17 @@ export const Brush2 = ({
// keep local and external brush extent in sync
// i.e. snap to ticks on brush end
const [brushInProgress, setBrushInProgress] = useState(false)
useEffect(() => {
if (brushInProgress) {
return
}
setLocalBrushExtent(brushExtent)
}, [brushExtent])
}, [brushExtent, brushInProgress])
// initialize the brush
useEffect(() => {
if (!brushRef.current) {
if (!brushRef.current || brushInProgress) {
return
}
......@@ -90,8 +94,14 @@ export const Brush2 = ({
])
.handleSize(30)
.filter(() => interactive)
.filter((event) => {
// Allow interactions only if the event target is part of the brush selection or handles
const target = event.target as SVGElement
return target.classList.contains('selection') || target.classList.contains('handle')
})
.on('brush', (event: D3BrushEvent<unknown>) => {
const { selection } = event
setBrushInProgress(true)
if (!selection) {
setLocalBrushExtent(null)
......@@ -116,6 +126,7 @@ export const Brush2 = ({
setBrushExtent(priceExtent, mode)
}
setLocalBrushExtent(priceExtent)
setBrushInProgress(false)
})
brushBehavior.current(select(brushRef.current))
......@@ -126,6 +137,8 @@ export const Brush2 = ({
.call(brushBehavior.current.move as any, scaledExtent)
}
select(brushRef.current).selectAll('.overlay').attr('cursor', 'default')
// brush linear gradient
select(brushRef.current)
.selectAll('.selection')
......@@ -133,7 +146,7 @@ export const Brush2 = ({
.attr('fill-opacity', '0.1')
.attr('fill', `url(#${id}-gradient-selection)`)
.attr('cursor', 'grab')
}, [brushExtent, id, height, interactive, previousBrushExtent, yScale, width, setBrushExtent])
}, [brushExtent, id, height, interactive, previousBrushExtent, yScale, width, setBrushExtent, brushInProgress])
// respond to yScale changes only
useEffect(() => {
......
......@@ -3,7 +3,6 @@ import { ScaleLinear } from 'd3'
import styled from 'lib/styled-components'
const Bar = styled.rect<{ fill?: string }>`
opacity: 0.5;
stroke: ${({ fill, theme }) => fill ?? theme.accent1};
fill: ${({ fill, theme }) => fill ?? theme.accent1};
`
......
......@@ -12,7 +12,6 @@ import {
} from 'components/Charts/ChartModel'
import { PriceChartData } from 'components/Charts/PriceChart'
import { PriceChartType, formatTickMarks } from 'components/Charts/utils'
import { MissingDataIcon } from 'components/Table/icons'
import { DataQuality } from 'components/Tokens/TokenDetails/ChartSection/util'
import { usePoolPriceChartData } from 'hooks/usePoolPriceChartData'
import { useTheme } from 'lib/styled-components'
......@@ -25,12 +24,11 @@ import {
} from 'pages/Pool/Positions/create/utils'
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
import { opacify } from 'theme/utils'
import { Flex, FlexProps, Shine, TamaguiElement, Text, assertWebElement } from 'ui/src'
import { Flex, FlexProps, Shine, TamaguiElement, assertWebElement } from 'ui/src'
import { LoadingPriceCurve } from 'ui/src/components/icons/LoadingPriceCurve'
import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { useTranslation } from 'uniswap/src/i18n'
const CHART_HEIGHT = 52
export const CHART_WIDTH = 224
......@@ -115,7 +113,7 @@ export class LPPriceChartModel extends ChartModel<PriceChartData> {
Math.pow(10, params.positionPriceLower.baseCurrency.decimals),
),
)
?.toSignificant(params.positionPriceLower.baseCurrency.decimals) ?? 0,
?.toSignificant(params.positionPriceLower.baseCurrency.decimals || 6) ?? 0,
)
this.positionRangeMax =
typeof params.positionPriceUpper === 'number'
......@@ -128,7 +126,7 @@ export class LPPriceChartModel extends ChartModel<PriceChartData> {
Math.pow(10, params.positionPriceUpper.baseCurrency.decimals),
),
)
?.toSignificant(params.positionPriceUpper.baseCurrency.decimals) ?? 0,
?.toSignificant(params.positionPriceUpper.baseCurrency.decimals || 6) ?? 0,
)
if (isEffectivelyInfinity(this.positionRangeMin)) {
......@@ -392,7 +390,6 @@ export function LiquidityPositionRangeChart({
grow = false,
}: LiquidityPositionRangeChartProps) {
const theme = useTheme()
const { t } = useTranslation()
const isV2 = version === ProtocolVersion.V2
const isV3 = version === ProtocolVersion.V3
const isV4 = version === ProtocolVersion.V4
......@@ -477,14 +474,7 @@ export function LiquidityPositionRangeChart({
overflow="hidden"
>
{priceData.loading && <LiquidityPositionRangeChartLoader size={chartWidth} />}
{dataUnavailable && (
<Flex row alignItems="center" gap="$gap12">
<MissingDataIcon height={36} width={36} />
<Text variant="body3" color="$neutral2">
{t('common.dataUnavailable')}
</Text>
</Flex>
)}
{dataUnavailable && <LoadingPriceCurve size={chartWidth} color="$neutral2" />}
{shouldRenderChart && (
<Flex width={grow ? chartWidth : width} $md={{ width: grow ? chartWidth : '100%' }}>
<Chart Model={LPPriceChartModel} params={chartParams} height={CHART_HEIGHT} />
......
import { isMobileWeb } from 'utilities/src/platform'
const RIGHT_AXIS_WIDTH = 64
const CHART_CONTAINER_WIDTH = 452 + RIGHT_AXIS_WIDTH
const LIQUIDITY_CHART_WIDTH = 68
const INTER_CHART_PADDING = 12
const CHART_HEIGHT = 164
const BOTTOM_AXIS_HEIGHT = 28
const loadedPriceChartWidth = CHART_CONTAINER_WIDTH - LIQUIDITY_CHART_WIDTH - INTER_CHART_PADDING - RIGHT_AXIS_WIDTH
const desktopSizes = {
rightAxisWidth: RIGHT_AXIS_WIDTH,
chartContainerWidth: CHART_CONTAINER_WIDTH,
liquidityChartWidth: LIQUIDITY_CHART_WIDTH,
interChartPadding: INTER_CHART_PADDING,
chartHeight: CHART_HEIGHT,
bottomAxisHeight: BOTTOM_AXIS_HEIGHT,
loadedPriceChartWidth,
}
const mobileSizes = {
rightAxisWidth: 0,
chartContainerWidth: 290,
liquidityChartWidth: 48,
interChartPadding: 0,
chartHeight: CHART_HEIGHT,
bottomAxisHeight: BOTTOM_AXIS_HEIGHT,
loadedPriceChartWidth: 290,
}
export function useRangeInputSizes(parentWidth?: number) {
return isMobileWeb
? {
...mobileSizes,
chartContainerWidth: parentWidth ?? mobileSizes.chartContainerWidth,
loadedPriceChartWidth: parentWidth ?? mobileSizes.loadedPriceChartWidth,
}
: desktopSizes
}
......@@ -2,8 +2,10 @@ import { InterfaceModalName } from '@uniswap/analytics-events'
import { AutoColumn } from 'components/deprecated/Column'
import styled from 'lib/styled-components'
import { PropsWithChildren } from 'react'
import { AdaptiveWebModal, HeightAnimator } from 'ui/src'
import { HeightAnimator } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
const Content = styled(AutoColumn)`
background-color: ${({ theme }) => theme.surface1};
......@@ -20,7 +22,7 @@ export function SwapModal({
}>) {
return (
<Trace modal={InterfaceModalName.CONFIRM_SWAP}>
<AdaptiveWebModal isOpen onClose={onDismiss} maxHeight="90vh" p={0}>
<Modal name={ModalName.SwapReview} isModalOpen onClose={onDismiss} maxHeight="90vh" padding={0}>
<HeightAnimator
open={true}
width="100%"
......@@ -35,7 +37,7 @@ export function SwapModal({
>
<Content>{children}</Content>
</HeightAnimator>
</AdaptiveWebModal>
</Modal>
</Trace>
)
}
......@@ -50,41 +50,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
min-height: 24px;
}
.c13 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
gap: 8px;
}
.c15 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
......@@ -120,6 +85,41 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
opacity: 0.4;
}
.c13 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
gap: 8px;
}
.c15 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -372,41 +372,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
min-height: 24px;
}
.c13 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
gap: 8px;
}
.c15 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
......@@ -442,6 +407,41 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
opacity: 0.4;
}
.c13 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
gap: 8px;
}
.c15 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -714,6 +714,22 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
min-height: 24px;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c15 {
width: 100%;
display: -webkit-box;
......@@ -749,22 +765,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
justify-content: center;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -1003,6 +1003,22 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
min-width: 0;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
width: 100%;
display: -webkit-box;
......@@ -1021,22 +1037,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
gap: 8px;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -1272,41 +1272,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
min-height: 24px;
}
.c13 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
gap: 8px;
}
.c15 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
......@@ -1342,6 +1307,41 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
opacity: 0.4;
}
.c13 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
gap: 8px;
}
.c15 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -1614,6 +1614,22 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
min-height: 24px;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c15 {
width: 100%;
display: -webkit-box;
......@@ -1649,22 +1665,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
justify-content: center;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......
......@@ -22,6 +22,7 @@ import { useSwapTransactionStatus } from 'state/transactions/hooks'
import { ThemeProvider } from 'theme'
import { FadePresence } from 'theme/components/FadePresence'
import { UniswapXOrderStatus } from 'types/uniswapx'
// eslint-disable-next-line no-restricted-imports
import { ADAPTIVE_MODAL_ANIMATION_DURATION } from 'ui/src/components/modal/AdaptiveWebModal'
import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
......
......@@ -2,7 +2,9 @@ import Column from 'components/deprecated/Column'
import styled, { useTheme } from 'lib/styled-components'
import { Slash } from 'react-feather'
import { CopyHelper, ExternalLink, ThemedText } from 'theme/components'
import { AdaptiveWebModal, Flex, Text } from 'ui/src'
import { Flex, Text } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { Trans } from 'uniswap/src/i18n'
const ContentWrapper = styled(Column)`
......@@ -19,7 +21,7 @@ interface ConnectedAccountBlockedProps {
export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedProps) {
const theme = useTheme()
return (
<AdaptiveWebModal isOpen={props.isOpen} onClose={Function.prototype()} p={0}>
<Modal name={ModalName.AccountBlocked} isModalOpen={props.isOpen} onClose={Function.prototype()} padding={0}>
<ContentWrapper>
<Slash size="22px" color={theme.neutral2} />
<ThemedText.DeprecatedLargeHeader lineHeight={2} marginBottom={1} marginTop={1}>
......@@ -55,6 +57,6 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr
/>
</ThemedText.DeprecatedMain>
</ContentWrapper>
</AdaptiveWebModal>
</Modal>
)
}
......@@ -343,7 +343,7 @@ exports[`LimitPriceInputPanel should render correct subheader with inputCurrency
<h2
class="is_DialogTitle font_heading _display-inline _boxSizing-border-box _wordWrap-break-word _fontFamily-f-family _color-color _fontWeight-f-weight-me3083741 _fontSize-f-size-medi3736 _lineHeight-f-lineHeigh507465454 _userSelect-auto _whiteSpace-normal _mt-0px _mr-0px _mb-0px _ml-0px"
data-disable-theme="true"
id="title-:r1:"
id="title-:r3:"
role="heading"
/>
</span>
......@@ -696,7 +696,7 @@ exports[`LimitPriceInputPanel should render the component with no currencies sel
<h2
class="is_DialogTitle font_heading _display-inline _boxSizing-border-box _wordWrap-break-word _fontFamily-f-family _color-color _fontWeight-f-weight-me3083741 _fontSize-f-size-medi3736 _lineHeight-f-lineHeigh507465454 _userSelect-auto _whiteSpace-normal _mt-0px _mr-0px _mb-0px _ml-0px"
data-disable-theme="true"
id="title-:r0:"
id="title-:r1:"
role="heading"
/>
</span>
......
......@@ -6,7 +6,8 @@ import styled, { DefaultTheme } from 'lib/styled-components'
import { ReactNode } from 'react'
import { Gap } from 'theme'
import { ThemedText } from 'theme/components'
import { AdaptiveWebModal } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
export const Container = styled(ColumnCenter)`
background-color: ${({ theme }) => theme.surface1};
......@@ -160,11 +161,11 @@ export function DialogContent({ icon, title, description, body, buttonsConfig }:
*/
export function Dialog(props: DialogProps) {
return (
<AdaptiveWebModal isOpen={props.isVisible} onClose={props.onCancel} p={0}>
<Modal name={ModalName.Dialog} isModalOpen={props.isVisible} onClose={props.onCancel} padding={0}>
<Container gap="lg">
<DialogHeader closeModal={props.onCancel} closeDataTestId="Dialog-closeButton" />
<DialogContent {...props} />
</Container>
</AdaptiveWebModal>
</Modal>
)
}
......@@ -8,7 +8,7 @@ import { X } from 'react-feather'
import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { BREAKPOINTS } from 'theme'
import { AdaptiveWebModal } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/features/chains/types'
import {
DynamicConfigKeys,
......@@ -19,6 +19,7 @@ import {
import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/gating/flags'
import { useFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/gating/hooks'
import { Statsig } from 'uniswap/src/features/gating/sdk/statsig'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
const Wrapper = styled(Column)`
padding: 20px 16px;
......@@ -203,7 +204,7 @@ export default function FeatureFlagModal() {
const closeModal = useCloseModal()
return (
<AdaptiveWebModal isOpen={open} onClose={closeModal} p={0}>
<Modal name={ModalName.FeatureFlags} isModalOpen={open} onClose={closeModal} padding={0}>
<Wrapper>
<Header>
<Row width="100%" justify="space-between">
......@@ -292,6 +293,6 @@ export default function FeatureFlagModal() {
</FlagsColumn>
<SaveButton onClick={() => window.location.reload()}>Reload</SaveButton>
</Wrapper>
</AdaptiveWebModal>
</Modal>
)
}
......@@ -71,15 +71,18 @@ export default function FeeSelector({
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
// get pool data on-chain for latest states
const pools = usePools([
[currencyA, currencyB, FeeAmount.LOWEST],
[currencyA, currencyB, FeeAmount.LOW_200],
[currencyA, currencyB, FeeAmount.LOW_300],
[currencyA, currencyB, FeeAmount.LOW_400],
[currencyA, currencyB, FeeAmount.LOW],
[currencyA, currencyB, FeeAmount.MEDIUM],
[currencyA, currencyB, FeeAmount.HIGH],
])
const pools = usePools(
[
[currencyA, currencyB, FeeAmount.LOWEST],
[currencyA, currencyB, FeeAmount.LOW_200],
[currencyA, currencyB, FeeAmount.LOW_300],
[currencyA, currencyB, FeeAmount.LOW_400],
[currencyA, currencyB, FeeAmount.LOW],
[currencyA, currencyB, FeeAmount.MEDIUM],
[currencyA, currencyB, FeeAmount.HIGH],
],
chainId,
)
const poolsByFeeTier: Record<FeeAmount, PoolState> = useMemo(
() =>
......
export const MOONPAY_SUPPORTED_CURRENCY_CODES = [
'eth',
'eth_arbitrum',
'eth_optimism',
'eth_polygon',
'eth_base',
'weth',
'wbtc',
'matic_polygon',
'polygon',
'usdc_arbitrum',
'usdc_optimism',
'usdc_polygon',
'usdc_base',
'usdc',
'usdt',
] as const
export type MoonpaySupportedCurrencyCode = (typeof MOONPAY_SUPPORTED_CURRENCY_CODES)[number]
import Circle from 'assets/images/blue-loader.svg'
import { MOONPAY_SUPPORTED_CURRENCY_CODES } from 'components/FiatOnrampModal/constants'
import { getDefaultCurrencyCode, parsePathParts } from 'components/FiatOnrampModal/utils'
import { useAccount } from 'hooks/useAccount'
import styled, { useTheme } from 'lib/styled-components'
import { useCallback, useEffect, useState } from 'react'
import { useHref } from 'react-router-dom'
import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { CustomLightSpinner, ThemedText } from 'theme/components'
import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { AdaptiveWebModal } from 'ui/src'
import { useUrlContext } from 'uniswap/src/contexts/UrlContext'
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { Trans } from 'uniswap/src/i18n'
import { logger } from 'utilities/src/logger/logger'
import { getChainIdFromChainUrlParam } from 'utils/chainParams'
const MOONPAY_DARK_BACKGROUND = '#1c1c1e'
const Wrapper = styled.div<{ isDarkMode: boolean }>`
// #1c1c1e is the background color for the darkmode moonpay iframe as of 2/16/2023
background-color: ${({ isDarkMode, theme }) => (isDarkMode ? MOONPAY_DARK_BACKGROUND : theme.white)};
border-radius: 20px;
box-shadow: ${({ theme }) => theme.deprecated_deepShadow};
display: flex;
flex-flow: column nowrap;
margin: 0;
flex: 1 1;
min-width: 375px;
position: relative;
width: 100%;
`
const ErrorText = styled(ThemedText.BodyPrimary)`
color: ${({ theme }) => theme.critical};
margin: auto !important;
text-align: center;
width: 90%;
`
const StyledIframe = styled.iframe<{ isDarkMode: boolean }>`
// #1c1c1e is the background color for the darkmode moonpay iframe as of 2/16/2023
background-color: ${({ isDarkMode, theme }) => (isDarkMode ? MOONPAY_DARK_BACKGROUND : theme.white)};
border-radius: 12px;
bottom: 0;
left: 0;
height: calc(100% - 16px);
margin: 8px;
padding: 0;
position: absolute;
right: 0;
top: 0;
width: calc(100% - 16px);
`
const StyledSpinner = styled(CustomLightSpinner)`
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0;
`
const MoonpayTextWrapper = styled.div`
position: absolute;
bottom: 20px;
margin: auto;
left: 0;
right: 0;
width: 100%;
text-align: center;
`
export default function FiatOnrampModal() {
const account = useAccount()
const theme = useTheme()
const isDarkMode = useIsDarkMode()
const closeModal = useCloseModal()
const fiatOnrampModalOpen = useModalIsOpen(ApplicationModal.FIAT_ONRAMP)
const { chainId, tokenAddress } = parsePathParts(location.pathname)
const chainInfo = chainId ? getChainInfo(chainId) : undefined
const { useParsedQueryString } = useUrlContext()
const parsedChainName = useParsedQueryString().chain
const queryChainId = typeof parsedChainName === 'string' ? getChainIdFromChainUrlParam(parsedChainName) : undefined
const queryChainInfo = queryChainId ? getChainInfo(queryChainId) : undefined
const accountChainInfo = account.chainId ? getChainInfo(account.chainId) : undefined
const [signedIframeUrl, setSignedIframeUrl] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const swapUrl = useHref('/swap')
const fetchSignedIframeUrl = useCallback(async () => {
if (!account.isConnected) {
setError('Please connect an account before making a purchase.')
return
}
setLoading(true)
setError(null)
try {
const signedIframeUrlFetchEndpoint = process.env.REACT_APP_MOONPAY_LINK as string
const res = await fetch(signedIframeUrlFetchEndpoint, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify({
theme: isDarkMode ? 'dark' : 'light',
colorCode: theme.accent1,
defaultCurrencyCode: getDefaultCurrencyCode(
tokenAddress,
chainInfo?.backendChain.chain ?? queryChainInfo?.backendChain.chain ?? accountChainInfo?.backendChain.chain,
),
redirectUrl: swapUrl,
walletAddresses: JSON.stringify(
MOONPAY_SUPPORTED_CURRENCY_CODES.reduce(
(acc, currencyCode) => ({
...acc,
[currencyCode]: account.address,
}),
{},
),
),
}),
})
const { url } = await res.json()
setSignedIframeUrl(url)
} catch (e) {
logger.info('FiatOnrampModal', 'fetchSingedIframeUrl', 'there was an error fetching the link', e)
setError(e.toString())
} finally {
setLoading(false)
}
}, [
account.address,
account.isConnected,
accountChainInfo?.backendChain.chain,
chainInfo?.backendChain.chain,
isDarkMode,
queryChainInfo?.backendChain.chain,
swapUrl,
theme.accent1,
tokenAddress,
])
useEffect(() => {
fetchSignedIframeUrl()
}, [fetchSignedIframeUrl])
return (
<AdaptiveWebModal isOpen={fiatOnrampModalOpen} onClose={() => closeModal()}>
<Wrapper data-testid="fiat-onramp-modal" isDarkMode={isDarkMode}>
{error ? (
<>
<ThemedText.MediumHeader>
<Trans i18nKey="moonpay.rampIframe" />
</ThemedText.MediumHeader>
<ErrorText>
<Trans i18nKey="common.error.somethingWrong" />
<br />
{error}
</ErrorText>
</>
) : loading ? (
<StyledSpinner src={Circle} alt="loading spinner" size="90px" />
) : (
<StyledIframe
src={signedIframeUrl ?? ''}
frameBorder="0"
title="fiat-onramp-iframe"
isDarkMode={isDarkMode}
/>
)}
</Wrapper>
<MoonpayTextWrapper>
<ThemedText.BodySmall color="neutral3">
<Trans i18nKey="moonpay.poweredBy" />
</ThemedText.BodySmall>
</MoonpayTextWrapper>
</AdaptiveWebModal>
)
}
import { WETH9 } from '@uniswap/sdk-core'
import { getDefaultCurrencyCode, parsePathParts } from 'components/FiatOnrampModal/utils'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import {
MATIC_MAINNET,
USDC_ARBITRUM,
USDC_MAINNET,
USDC_OPTIMISM,
USDC_POLYGON,
USDT,
WBTC,
WETH_POLYGON,
} from 'uniswap/src/constants/tokens'
import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
describe('getDefaultCurrencyCode', () => {
it('NATIVE/arbitrum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(NATIVE_CHAIN_ID, Chain.Arbitrum)).toBe('eth_arbitrum')
})
it('NATIVE/optimism should return the correct currency code', () => {
expect(getDefaultCurrencyCode(NATIVE_CHAIN_ID, Chain.Optimism)).toBe('eth_optimism')
})
it('WETH/polygon should return the correct currency code', () => {
expect(getDefaultCurrencyCode(WETH_POLYGON.address, Chain.Polygon)).toBe('eth_polygon')
})
it('WETH/ethereum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(WETH9[UniverseChainId.Mainnet].address, Chain.Ethereum)).toBe('weth')
})
it('WBTC/ethereum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(WBTC.address, Chain.Ethereum)).toBe('wbtc')
})
it('NATIVE/polygon should return the correct currency code', () => {
expect(getDefaultCurrencyCode(NATIVE_CHAIN_ID, Chain.Polygon)).toBe('matic_polygon')
})
it('MATIC/ethereum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(MATIC_MAINNET.address, Chain.Ethereum)).toBe('polygon')
})
it('USDC/arbitrum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, Chain.Arbitrum)).toBe('usdc_arbitrum')
})
it('USDC/optimism should return the correct currency code', () => {
expect(getDefaultCurrencyCode(USDC_OPTIMISM.address, Chain.Optimism)).toBe('usdc_optimism')
})
it('USDC/polygon should return the correct currency code', () => {
expect(getDefaultCurrencyCode(USDC_POLYGON.address, Chain.Polygon)).toBe('usdc_polygon')
})
it('native/ethereum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(NATIVE_CHAIN_ID, Chain.Ethereum)).toBe('eth')
})
it('usdc/ethereum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(USDC_MAINNET.address, Chain.Ethereum)).toBe('usdc')
})
it('usdt/ethereum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(USDT.address, Chain.Ethereum)).toBe('usdt')
})
it('chain/token mismatch should default to eth', () => {
expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, Chain.Ethereum)).toBe('eth')
expect(getDefaultCurrencyCode(USDC_OPTIMISM.address, Chain.Ethereum)).toBe('eth')
expect(getDefaultCurrencyCode(USDC_POLYGON.address, Chain.Ethereum)).toBe('eth')
expect(getDefaultCurrencyCode(MATIC_MAINNET.address, Chain.Arbitrum)).toBe('eth')
})
})
describe('parseLocation', () => {
it('should parse the URL correctly', () => {
expect(parsePathParts('/tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599')).toEqual({
chainId: UniverseChainId.Mainnet,
tokenAddress: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599',
})
expect(parsePathParts('tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599')).toEqual({
chainId: UniverseChainId.Mainnet,
tokenAddress: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599',
})
expect(parsePathParts('/swap')).toEqual({
chainId: undefined,
tokenAddress: undefined,
})
})
})
import { WETH9 } from '@uniswap/sdk-core'
import { MoonpaySupportedCurrencyCode } from 'components/FiatOnrampModal/constants'
import {
MATIC_MAINNET,
USDC_ARBITRUM,
USDC_BASE,
USDC_MAINNET,
USDC_OPTIMISM,
USDC_POLYGON,
USDT,
WBTC,
WETH_POLYGON,
} from 'uniswap/src/constants/tokens'
import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { GqlChainId, UniverseChainId } from 'uniswap/src/features/chains/types'
import { getChainIdFromChainUrlParam } from 'utils/chainParams'
type MoonpaySupportedChain = Chain.Ethereum | Chain.Polygon | Chain.Arbitrum | Chain.Optimism | Chain.Base
const moonPaySupportedChains = [Chain.Ethereum, Chain.Polygon, Chain.Arbitrum, Chain.Optimism, Chain.Base]
const isMoonpaySupportedChain = (chain?: Chain): chain is MoonpaySupportedChain =>
!!chain && moonPaySupportedChains.includes(chain)
const CURRENCY_CODES: {
[K in MoonpaySupportedChain]: {
[key: string]: MoonpaySupportedCurrencyCode
native: MoonpaySupportedCurrencyCode
}
} = {
[Chain.Ethereum]: {
[WETH9[UniverseChainId.Mainnet]?.address.toLowerCase()]: 'weth',
[USDC_MAINNET.address.toLowerCase()]: 'usdc',
[USDT.address.toLowerCase()]: 'usdt',
[WBTC.address.toLowerCase()]: 'wbtc',
[MATIC_MAINNET.address.toLowerCase()]: 'polygon',
native: 'eth',
},
[Chain.Arbitrum]: {
[USDC_ARBITRUM.address.toLowerCase()]: 'usdc_arbitrum',
native: 'eth_arbitrum',
},
[Chain.Optimism]: {
[USDC_OPTIMISM.address.toLowerCase()]: 'usdc_optimism',
native: 'eth_optimism',
},
[Chain.Polygon]: {
[USDC_POLYGON.address.toLowerCase()]: 'usdc_polygon',
[WETH_POLYGON.address.toLowerCase()]: 'eth_polygon',
native: 'matic_polygon',
},
[Chain.Base]: {
[USDC_BASE.address.toLowerCase()]: 'usdc_base',
native: 'eth_base',
},
}
export function getDefaultCurrencyCode(address?: string, gqlChain?: GqlChainId): MoonpaySupportedCurrencyCode {
if (!gqlChain) {
return 'eth'
}
if (!address) {
return isMoonpaySupportedChain(gqlChain) ? CURRENCY_CODES[gqlChain]?.native : 'eth'
}
if (isMoonpaySupportedChain(gqlChain)) {
const code = CURRENCY_CODES[gqlChain]?.[address.toLowerCase()]
return code ?? 'eth'
}
return 'eth'
}
/**
* You should use useParams() from react-router-dom instead of this function if possible.
* This function is only used in the case where we need to parse the path outside the scope of the router.
*/
export function parsePathParts(pathname: string) {
const pathParts = pathname.split('/')
// Matches the /tokens/<network>/<tokenAddress> path.
const chainSlug = pathParts.length > 2 ? pathParts[pathParts.length - 2] : undefined
const chainId = getChainIdFromChainUrlParam(chainSlug)
const tokenAddress = pathParts.length > 2 ? pathParts[pathParts.length - 1] : undefined
return { chainId, tokenAddress }
}
......@@ -2,13 +2,14 @@ import { LiquidityModalHeader } from 'components/Liquidity/LiquidityModalHeader'
import { WebUniswapProvider } from 'components/Web3Provider/WebUniswapContext'
import { act, fireEvent, render } from 'test-utils/render'
import { TransactionSettingsContextProvider } from 'uniswap/src/features/transactions/settings/contexts/TransactionSettingsContext'
import { TransactionSettingKey } from 'uniswap/src/features/transactions/settings/slice'
describe('LiquidityModalHeader', () => {
it('should render with given title and call close callback', () => {
const onClose = jest.fn()
const { getByText, getByTestId } = render(
<WebUniswapProvider>
<TransactionSettingsContextProvider>
<TransactionSettingsContextProvider settingKey={TransactionSettingKey.Swap}>
<LiquidityModalHeader title="Test Title" closeModal={onClose} />
</TransactionSettingsContextProvider>
,
......
......@@ -7,7 +7,7 @@ import { MouseoverTooltip } from 'components/Tooltip'
import { useScreenSize } from 'hooks/screenSize/useScreenSize'
import { TextLoader } from 'pages/Pool/Positions/shared'
import { Dispatch, SetStateAction } from 'react'
import { ClickableTamaguiStyle } from 'theme/components'
import { ClickableTamaguiStyle, EllipsisTamaguiStyle } from 'theme/components'
import { Flex, Text, styled } from 'ui/src'
import { ArrowUpDown } from 'ui/src/components/icons/ArrowUpDown'
import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled'
......@@ -168,8 +168,17 @@ export function LiquidityPositionFeeStats({
<SecondaryText flexShrink={0}>
<Trans i18nKey="common.max" />
</SecondaryText>
<SecondaryText color="$neutral1">
{maxPrice} {tokenASymbol} / {tokenBSymbol}
<SecondaryText color="$neutral1" display="flex" alignItems="center" gap="$gap4">
<span
style={{
...EllipsisTamaguiStyle,
}}
>
{maxPrice}
</span>
<span>
{tokenASymbol} / {tokenBSymbol}
</span>
</SecondaryText>
<Flex
height="100%"
......
......@@ -17,6 +17,7 @@ const InnerTile = styled(Flex, {
})
interface LiquidityPositionPriceRangeTileProps {
token1: Currency
priceOrdering: PriceOrdering
token0CurrentPrice: Price<Currency, Currency>
token1CurrentPrice: Price<Currency, Currency>
......@@ -26,6 +27,7 @@ interface LiquidityPositionPriceRangeTileProps {
}
export function LiquidityPositionPriceRangeTile({
token1,
priceOrdering,
token0CurrentPrice,
token1CurrentPrice,
......@@ -56,9 +58,7 @@ export function LiquidityPositionPriceRangeTile({
throw new Error('LiquidityPositionPriceRangeTile: Currency symbols are required')
}
const { minPrice, maxPrice, currentPrice, tokenASymbol, tokenBSymbol } = useGetRangeDisplay({
token0CurrentPrice,
token1CurrentPrice,
const { minPrice, maxPrice, tokenASymbol, tokenBSymbol } = useGetRangeDisplay({
priceOrdering,
feeTier,
tickLower,
......@@ -66,6 +66,19 @@ export function LiquidityPositionPriceRangeTile({
pricesInverted,
})
const currentPrice = useMemo(() => {
const { base } = priceOrdering
if (!base) {
return undefined
}
if (!pricesInverted) {
return base?.equals(token1) ? token1CurrentPrice : token0CurrentPrice
}
return base?.equals(token1) ? token0CurrentPrice : token1CurrentPrice
}, [priceOrdering, token0CurrentPrice, token1CurrentPrice, token1, pricesInverted])
return (
<Flex backgroundColor="$surface2" borderRadius="$rounded12" p="$padding12" width="100%" gap="$gap12">
<Flex row width="100%" justifyContent="space-between" alignItems="center">
......
This diff is collapsed.
This diff is collapsed.
......@@ -165,7 +165,6 @@ exports[`disable nft on searchbar should render text without nfts 1`] = `
style="--placeholderTextColor: #7D7D7D;"
tabindex="0"
value=""
virtualkeyboardpolicy="auto"
/>
</span>
<div
......
export const PRIVACY_SHARING_OPT_OUT_STORAGE_KEY = 'optOutPrivacySharing'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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