ci(release): publish latest release

parent ae6fb88a
* @uniswap/web-admins
IPFS hash of the deployment: We are back with another round of updates. Here’s what’s new:
- CIDv0: `QmPfhCQw4e63hhx4FqWnroACYRcyZHw6f5Nvo6DXHwkEGr`
- CIDv1: `bafybeiatxsersz6wkzf75lzpkhxoglubrcqv7uqy6nubdvvjyanidm4vam`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). Launched uni.eth usernames! This is a readable username that makes it easy to receive crypto and build your web3 profile. Claim your username in the app.
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://bafybeiatxsersz6wkzf75lzpkhxoglubrcqv7uqy6nubdvvjyanidm4vam.ipfs.dweb.link/
- https://bafybeiatxsersz6wkzf75lzpkhxoglubrcqv7uqy6nubdvvjyanidm4vam.ipfs.cf-ipfs.com/
- [ipfs://QmPfhCQw4e63hhx4FqWnroACYRcyZHw6f5Nvo6DXHwkEGr/](ipfs://QmPfhCQw4e63hhx4FqWnroACYRcyZHw6f5Nvo6DXHwkEGr/)
### 5.17.2 (2024-03-08)
### Bug Fixes
* **web:** [hotfix] fiatCurrency is undefined (#6811) (#6814) a78d4cc
Other changes:
- Polish around recovery phrase settings and notifications
- Various bug fixes and performance improvements
web/5.17.2 mobile/1.22
\ No newline at end of file \ No newline at end of file
...@@ -125,17 +125,17 @@ android { ...@@ -125,17 +125,17 @@ android {
dev { dev {
isDefault(true) isDefault(true)
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionName "1.23" versionName "1.22"
dimension "variant" dimension "variant"
} }
beta { beta {
applicationIdSuffix ".beta" applicationIdSuffix ".beta"
versionName "1.23" versionName "1.22"
dimension "variant" dimension "variant"
} }
prod { prod {
dimension "variant" dimension "variant"
versionName "1.23" versionName "1.22"
} }
} }
......
...@@ -43,4 +43,11 @@ allprojects { ...@@ -43,4 +43,11 @@ allprojects {
codegenDir = rootProject.file("../../../node_modules/react-native-codegen/") codegenDir = rootProject.file("../../../node_modules/react-native-codegen/")
} }
} }
repositories {
maven {
// expo-camera bundles a custom com.google.android:cameraview
url "$rootDir/../../../node_modules/expo-camera/android/maven"
}
}
} }
...@@ -656,6 +656,8 @@ PODS: ...@@ -656,6 +656,8 @@ PODS:
- ExpoModulesCore - ExpoModulesCore
- ZXingObjC/OneD - ZXingObjC/OneD
- ZXingObjC/PDF417 - ZXingObjC/PDF417
- EXCamera (13.4.4):
- ExpoModulesCore
- EXFileSystem (15.3.0): - EXFileSystem (15.3.0):
- ExpoModulesCore - ExpoModulesCore
- EXFont (11.1.1): - EXFont (11.1.1):
...@@ -1128,6 +1130,9 @@ PODS: ...@@ -1128,6 +1130,9 @@ PODS:
- react-native-appsflyer (6.10.3): - react-native-appsflyer (6.10.3):
- AppsFlyerFramework (= 6.10.1) - AppsFlyerFramework (= 6.10.1)
- React - React
- react-native-compat (2.11.2):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- react-native-context-menu-view (1.6.0): - react-native-context-menu-view (1.6.0):
- React - React
- react-native-get-random-values (1.8.0): - react-native-get-random-values (1.8.0):
...@@ -1353,6 +1358,7 @@ DEPENDENCIES: ...@@ -1353,6 +1358,7 @@ DEPENDENCIES:
- EXApplication (from `../../../node_modules/expo-application/ios`) - EXApplication (from `../../../node_modules/expo-application/ios`)
- EXAV (from `../../../node_modules/expo-av/ios`) - EXAV (from `../../../node_modules/expo-av/ios`)
- EXBarCodeScanner (from `../../../node_modules/expo-barcode-scanner/ios`) - EXBarCodeScanner (from `../../../node_modules/expo-barcode-scanner/ios`)
- EXCamera (from `../../../node_modules/expo-camera/ios`)
- EXFileSystem (from `../../../node_modules/expo-file-system/ios`) - EXFileSystem (from `../../../node_modules/expo-file-system/ios`)
- EXFont (from `../../../node_modules/expo-font/ios`) - EXFont (from `../../../node_modules/expo-font/ios`)
- EXImageLoader (from `../../../node_modules/expo-image-loader/ios`) - EXImageLoader (from `../../../node_modules/expo-image-loader/ios`)
...@@ -1394,6 +1400,7 @@ DEPENDENCIES: ...@@ -1394,6 +1400,7 @@ DEPENDENCIES:
- React-jsinspector (from `../../../node_modules/react-native/ReactCommon/jsinspector`) - React-jsinspector (from `../../../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../../../node_modules/react-native/ReactCommon/logger`) - React-logger (from `../../../node_modules/react-native/ReactCommon/logger`)
- react-native-appsflyer (from `../../../node_modules/react-native-appsflyer`) - react-native-appsflyer (from `../../../node_modules/react-native-appsflyer`)
- "react-native-compat (from `../../../node_modules/@walletconnect/react-native-compat`)"
- react-native-context-menu-view (from `../../../node_modules/react-native-context-menu-view`) - react-native-context-menu-view (from `../../../node_modules/react-native-context-menu-view`)
- react-native-get-random-values (from `../../../node_modules/react-native-get-random-values`) - react-native-get-random-values (from `../../../node_modules/react-native-get-random-values`)
- react-native-image-picker (from `../../../node_modules/react-native-image-picker`) - react-native-image-picker (from `../../../node_modules/react-native-image-picker`)
...@@ -1493,6 +1500,8 @@ EXTERNAL SOURCES: ...@@ -1493,6 +1500,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/expo-av/ios" :path: "../../../node_modules/expo-av/ios"
EXBarCodeScanner: EXBarCodeScanner:
:path: "../../../node_modules/expo-barcode-scanner/ios" :path: "../../../node_modules/expo-barcode-scanner/ios"
EXCamera:
:path: "../../../node_modules/expo-camera/ios"
EXFileSystem: EXFileSystem:
:path: "../../../node_modules/expo-file-system/ios" :path: "../../../node_modules/expo-file-system/ios"
EXFont: EXFont:
...@@ -1565,6 +1574,8 @@ EXTERNAL SOURCES: ...@@ -1565,6 +1574,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native/ReactCommon/logger" :path: "../../../node_modules/react-native/ReactCommon/logger"
react-native-appsflyer: react-native-appsflyer:
:path: "../../../node_modules/react-native-appsflyer" :path: "../../../node_modules/react-native-appsflyer"
react-native-compat:
:path: "../../../node_modules/@walletconnect/react-native-compat"
react-native-context-menu-view: react-native-context-menu-view:
:path: "../../../node_modules/react-native-context-menu-view" :path: "../../../node_modules/react-native-context-menu-view"
react-native-get-random-values: react-native-get-random-values:
...@@ -1671,6 +1682,7 @@ SPEC CHECKSUMS: ...@@ -1671,6 +1682,7 @@ SPEC CHECKSUMS:
EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903
EXAV: f393dfc0b28214d62855a31e06eb21d426d6e2da EXAV: f393dfc0b28214d62855a31e06eb21d426d6e2da
EXBarCodeScanner: 296dd50f6c03928d1d71d37ea17473b304cfdb00 EXBarCodeScanner: 296dd50f6c03928d1d71d37ea17473b304cfdb00
EXCamera: 6e6e79bf01a2b8190268d93297d8e79a843d5ede
EXFileSystem: 0b4a2c08c2dc92146849772145d61c1773144283 EXFileSystem: 0b4a2c08c2dc92146849772145d61c1773144283
EXFont: 6ea3800df746be7233208d80fe379b8ed74f4272 EXFont: 6ea3800df746be7233208d80fe379b8ed74f4272
EXImageLoader: 03063370bc06ea1825713d3f55fe0455f7c88d04 EXImageLoader: 03063370bc06ea1825713d3f55fe0455f7c88d04
...@@ -1727,6 +1739,7 @@ SPEC CHECKSUMS: ...@@ -1727,6 +1739,7 @@ SPEC CHECKSUMS:
React-jsinspector: 7e58fe86c7cc442fd11da0c9d8bef12a8d63f771 React-jsinspector: 7e58fe86c7cc442fd11da0c9d8bef12a8d63f771
React-logger: a3f6ca0d018749852a2a6f07c154bfc6fcd4195a React-logger: a3f6ca0d018749852a2a6f07c154bfc6fcd4195a
react-native-appsflyer: 153e96b97ecc0c26b14fc79911675e51cfd35b36 react-native-appsflyer: 153e96b97ecc0c26b14fc79911675e51cfd35b36
react-native-compat: e31d4e4ba7db54f2649c4b34b9b594029b51b5e2
react-native-context-menu-view: d3b3e77985d5b05674a70f8e7eafe404dfa5bbcc react-native-context-menu-view: d3b3e77985d5b05674a70f8e7eafe404dfa5bbcc
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
react-native-image-picker: 1569cfade34b3a011191ce262423e6ce2f8db5a1 react-native-image-picker: 1569cfade34b3a011191ce262423e6ce2f8db5a1
......
...@@ -2450,7 +2450,7 @@ ...@@ -2450,7 +2450,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2496,7 +2496,7 @@ ...@@ -2496,7 +2496,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
...@@ -2542,7 +2542,7 @@ ...@@ -2542,7 +2542,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
...@@ -2588,7 +2588,7 @@ ...@@ -2588,7 +2588,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
...@@ -2630,7 +2630,7 @@ ...@@ -2630,7 +2630,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2673,7 +2673,7 @@ ...@@ -2673,7 +2673,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
...@@ -2716,7 +2716,7 @@ ...@@ -2716,7 +2716,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
...@@ -2759,7 +2759,7 @@ ...@@ -2759,7 +2759,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
...@@ -2795,7 +2795,7 @@ ...@@ -2795,7 +2795,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -2833,7 +2833,7 @@ ...@@ -2833,7 +2833,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3003,7 +3003,7 @@ ...@@ -3003,7 +3003,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -3047,7 +3047,7 @@ ...@@ -3047,7 +3047,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
...@@ -3143,7 +3143,7 @@ ...@@ -3143,7 +3143,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3214,7 +3214,7 @@ ...@@ -3214,7 +3214,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
...@@ -3310,7 +3310,7 @@ ...@@ -3310,7 +3310,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3381,7 +3381,7 @@ ...@@ -3381,7 +3381,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.22;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
...@@ -5,8 +5,7 @@ import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js ...@@ -5,8 +5,7 @@ import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js
import 'core-js' // necessary so setImmediate works in tests import 'core-js' // necessary so setImmediate works in tests
import { localizeMock as mockRNLocalize } from 'react-native-localize/mock' import { localizeMock as mockRNLocalize } from 'react-native-localize/mock'
import { AppearanceSettingType } from 'wallet/src/features/appearance/slice' import { AppearanceSettingType } from 'wallet/src/features/appearance/slice'
import { initializeTranslation } from 'wallet/src/i18n/i18n' import { MockLocalizationContext } from 'wallet/src/test/utils'
import { mockLocalizationContext } from 'wallet/src/test/mocks/utils'
// avoids polluting console in test runs, while keeping important log levels // avoids polluting console in test runs, while keeping important log levels
global.console = { global.console = {
...@@ -19,9 +18,6 @@ global.console = { ...@@ -19,9 +18,6 @@ global.console = {
// error: jest.fn(), // error: jest.fn(),
} }
// Uses real translations for tests
initializeTranslation()
// Mock Sentry crash reporting // Mock Sentry crash reporting
jest.mock('@sentry/react-native', () => ({ jest.mock('@sentry/react-native', () => ({
init: () => jest.fn(), init: () => jest.fn(),
...@@ -87,7 +83,7 @@ jest.mock('@react-navigation/elements', () => ({ ...@@ -87,7 +83,7 @@ jest.mock('@react-navigation/elements', () => ({
require('react-native-reanimated').setUpTests() require('react-native-reanimated').setUpTests()
jest.mock('wallet/src/features/language/LocalizationContext', () => mockLocalizationContext) jest.mock('wallet/src/features/language/LocalizationContext', () => MockLocalizationContext)
jest.mock('react-native/Libraries/Share/Share', () => ({ jest.mock('react-native/Libraries/Share/Share', () => ({
share: jest.fn(), share: jest.fn(),
...@@ -115,6 +111,22 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({ ...@@ -115,6 +111,22 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({
getInitialURL: jest.fn(), getInitialURL: jest.fn(),
})) }))
jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate hook can use it without a warning being shown
useTranslation: () => {
return {
t: (str) => str,
i18n: {
changeLanguage: () => new Promise(jest.fn()),
},
}
},
initReactI18next: {
type: '3rdParty',
init: jest.fn(),
},
}))
// Mock the appearance hook for all tests // Mock the appearance hook for all tests
const mockAppearanceSetting = AppearanceSettingType.System const mockAppearanceSetting = AppearanceSettingType.System
jest.mock('wallet/src/features/appearance/hooks', () => { jest.mock('wallet/src/features/appearance/hooks', () => {
......
...@@ -94,6 +94,7 @@ ...@@ -94,6 +94,7 @@
"expo-av": "13.4.1", "expo-av": "13.4.1",
"expo-barcode-scanner": "12.7.0", "expo-barcode-scanner": "12.7.0",
"expo-blur": "12.2.2", "expo-blur": "12.2.2",
"expo-camera": "13.4.4",
"expo-haptics": "12.0.1", "expo-haptics": "12.0.1",
"expo-linear-gradient": "12.3.0", "expo-linear-gradient": "12.3.0",
"expo-linking": "4.0.1", "expo-linking": "4.0.1",
...@@ -143,7 +144,6 @@ ...@@ -143,7 +144,6 @@
"rive-react-native": "6.1.1", "rive-react-native": "6.1.1",
"statsig-react-native": "4.11.0", "statsig-react-native": "4.11.0",
"typed-redux-saga": "1.5.0", "typed-redux-saga": "1.5.0",
"uniswap": "workspace:^",
"utilities": "workspace:^", "utilities": "workspace:^",
"wallet": "workspace:^" "wallet": "workspace:^"
}, },
......
...@@ -72,8 +72,8 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element { ...@@ -72,8 +72,8 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element {
<Flex centered grow gap="$spacing36"> <Flex centered grow gap="$spacing36">
<Image source={DEAD_LUNI} style={styles.errorImage} /> <Image source={DEAD_LUNI} style={styles.errorImage} />
<Flex centered gap="$spacing8"> <Flex centered gap="$spacing8">
<Text variant="subheading1">{t('errors.crash.title')}</Text> <Text variant="subheading1">{t('Uh oh!')}</Text>
<Text variant="body2">{t('errors.crash.message')}</Text> <Text variant="body2">{t('Something crashed.')}</Text>
</Flex> </Flex>
{error.message && __DEV__ && <Text variant="body2">{error.message}</Text>} {error.message && __DEV__ && <Text variant="body2">{error.message}</Text>}
</Flex> </Flex>
...@@ -82,7 +82,7 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element { ...@@ -82,7 +82,7 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element {
onPress={(): void => { onPress={(): void => {
RNRestart.Restart() RNRestart.Restart()
}}> }}>
{t('errors.crash.restart')} {t('Restart app')}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -6,7 +6,6 @@ import { closeModal, openModal } from 'src/features/modals/modalSlice' ...@@ -6,7 +6,6 @@ import { closeModal, openModal } from 'src/features/modals/modalSlice'
import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex' import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { import {
NavigateToNftItemArgs,
NavigateToSwapFlowArgs, NavigateToSwapFlowArgs,
WalletNavigationProvider, WalletNavigationProvider,
} from 'wallet/src/contexts/WalletNavigationContext' } from 'wallet/src/contexts/WalletNavigationContext'
...@@ -17,7 +16,6 @@ export function MobileWalletNavigationProvider({ children }: PropsWithChildren): ...@@ -17,7 +16,6 @@ export function MobileWalletNavigationProvider({ children }: PropsWithChildren):
const navigateToAccountActivityList = useNavigateToHomepageTab(HomeScreenTabIndex.Activity) const navigateToAccountActivityList = useNavigateToHomepageTab(HomeScreenTabIndex.Activity)
const navigateToAccountTokenList = useNavigateToHomepageTab(HomeScreenTabIndex.Tokens) const navigateToAccountTokenList = useNavigateToHomepageTab(HomeScreenTabIndex.Tokens)
const navigateToBuyOrReceiveWithEmptyWallet = useNavigateToBuyOrReceiveWithEmptyWallet() const navigateToBuyOrReceiveWithEmptyWallet = useNavigateToBuyOrReceiveWithEmptyWallet()
const navigateToNftDetails = useNavigateToNftDetails()
const navigateToSwapFlow = useNavigateToSwapFlow() const navigateToSwapFlow = useNavigateToSwapFlow()
const navigateToTokenDetails = useNavigateToTokenDetails() const navigateToTokenDetails = useNavigateToTokenDetails()
...@@ -26,7 +24,6 @@ export function MobileWalletNavigationProvider({ children }: PropsWithChildren): ...@@ -26,7 +24,6 @@ export function MobileWalletNavigationProvider({ children }: PropsWithChildren):
navigateToAccountActivityList={navigateToAccountActivityList} navigateToAccountActivityList={navigateToAccountActivityList}
navigateToAccountTokenList={navigateToAccountTokenList} navigateToAccountTokenList={navigateToAccountTokenList}
navigateToBuyOrReceiveWithEmptyWallet={navigateToBuyOrReceiveWithEmptyWallet} navigateToBuyOrReceiveWithEmptyWallet={navigateToBuyOrReceiveWithEmptyWallet}
navigateToNftDetails={navigateToNftDetails}
navigateToSwapFlow={navigateToSwapFlow} navigateToSwapFlow={navigateToSwapFlow}
navigateToTokenDetails={navigateToTokenDetails}> navigateToTokenDetails={navigateToTokenDetails}>
{children} {children}
...@@ -67,23 +64,6 @@ function useNavigateToTokenDetails(): (currencyId: string) => void { ...@@ -67,23 +64,6 @@ function useNavigateToTokenDetails(): (currencyId: string) => void {
) )
} }
function useNavigateToNftDetails(): (args: NavigateToNftItemArgs) => void {
const navigation = useAppStackNavigation()
return useCallback(
({ owner, address, tokenId, isSpam, fallbackData }: NavigateToNftItemArgs): void => {
navigation.navigate(Screens.NFTItem, {
owner,
address,
tokenId,
isSpam,
fallbackData,
})
},
[navigation]
)
}
function useNavigateToBuyOrReceiveWithEmptyWallet(): () => void { function useNavigateToBuyOrReceiveWithEmptyWallet(): () => void {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
......
...@@ -97,25 +97,7 @@ import { ...@@ -97,25 +97,7 @@ import {
} from 'wallet/src/features/wallet/accounts/types' } from 'wallet/src/features/wallet/accounts/types'
import { initialWalletState, SwapProtectionSetting } from 'wallet/src/features/wallet/slice' import { initialWalletState, SwapProtectionSetting } from 'wallet/src/features/wallet/slice'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { import { account, fiatOnRampTxDetailsFailed, txDetailsConfirmed } from 'wallet/src/test/fixtures'
fiatPurchaseTransactionInfo,
signerMnemonicAccount,
transactionDetails,
} from 'wallet/src/test/fixtures'
const account = signerMnemonicAccount()
const txDetailsConfirmed = transactionDetails({
status: TransactionStatus.Success,
})
const fiatOnRampTxDetailsFailed = transactionDetails({
status: TransactionStatus.Failed,
typeInfo: fiatPurchaseTransactionInfo({
explorerUrl:
'https://buy-sandbox.moonpay.com/transaction_receipt?transactionId=d6c32bb5-7cd9-4c22-8f46-6bbe786c599f',
id: 'd6c32bb5-7cd9-4c22-8f46-6bbe786c599f',
}),
})
// helps with object assignment // helps with object assignment
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
......
...@@ -6,11 +6,11 @@ import { MobileState } from 'src/app/reducer' ...@@ -6,11 +6,11 @@ import { MobileState } from 'src/app/reducer'
import { initialModalState } from 'src/features/modals/modalSlice' import { initialModalState } from 'src/features/modals/modalSlice'
import { render } from 'src/test/test-utils' import { render } from 'src/test/test-utils'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { ACCOUNT } from 'wallet/src/test/fixtures' import { mockWalletPreloadedState } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState, noOpFunction } from 'wallet/src/test/mocks' import { noOpFunction } from 'wallet/src/test/utils'
const preloadedState = { const preloadedState = {
...mockWalletPreloadedState(ACCOUNT), ...mockWalletPreloadedState,
modals: { modals: {
...initialModalState, ...initialModalState,
[ModalName.AccountSwitcher]: { isOpen: true }, [ModalName.AccountSwitcher]: { isOpen: true },
......
...@@ -190,19 +190,17 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -190,19 +190,17 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
if (!cloudStorageAvailable) { if (!cloudStorageAvailable) {
Alert.alert( Alert.alert(
isAndroid ? t('Google Drive not available') : t('iCloud Drive not available'),
isAndroid isAndroid
? t('account.cloud.error.unavailable.title.android') ? t(
: t('account.cloud.error.unavailable.title.ios'), 'Please verify that you are logged in to a Google account with Google Drive enabled on this device and try again.'
isAndroid )
? t('account.cloud.error.unavailable.message.android') : t(
: t('account.cloud.error.unavailable.message.ios'), 'Please verify that you are logged in to an Apple ID with iCloud Drive enabled on this device and try again.'
),
[ [
{ { text: t('Go to settings'), onPress: openSettings, style: 'default' },
text: t('account.cloud.error.unavailable.button.settings'), { text: t('Not now'), style: 'cancel' },
onPress: openSettings,
style: 'default',
},
{ text: t('account.cloud.error.unavailable.button.cancel'), style: 'cancel' },
] ]
) )
return return
...@@ -226,7 +224,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -226,7 +224,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
borderBottomColor="$surface3" borderBottomColor="$surface3"
borderBottomWidth={1} borderBottomWidth={1}
p="$spacing16"> p="$spacing16">
<Text variant="body1">{t('account.wallet.button.create')}</Text> <Text variant="body1">{t('Create a new wallet')}</Text>
</Flex> </Flex>
), ),
}, },
...@@ -235,7 +233,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -235,7 +233,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
onPress: onPressAddViewOnlyWallet, onPress: onPressAddViewOnlyWallet,
render: () => ( render: () => (
<Flex alignItems="center" p="$spacing16"> <Flex alignItems="center" p="$spacing16">
<Text variant="body1">{t('account.wallet.button.addViewOnly')}</Text> <Text variant="body1">{t('Add a view-only wallet')}</Text>
</Flex> </Flex>
), ),
}, },
...@@ -244,7 +242,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -244,7 +242,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
onPress: onPressImportWallet, onPress: onPressImportWallet,
render: () => ( render: () => (
<Flex alignItems="center" borderTopColor="$surface3" borderTopWidth={1} p="$spacing16"> <Flex alignItems="center" borderTopColor="$surface3" borderTopWidth={1} p="$spacing16">
<Text variant="body1">{t('account.wallet.button.import')}</Text> <Text variant="body1">{t('Import a new wallet')}</Text>
</Flex> </Flex>
), ),
}, },
...@@ -257,9 +255,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -257,9 +255,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
render: () => ( render: () => (
<Flex alignItems="center" borderTopColor="$surface3" borderTopWidth={1} p="$spacing16"> <Flex alignItems="center" borderTopColor="$surface3" borderTopWidth={1} p="$spacing16">
<Text variant="body1"> <Text variant="body1">
{isAndroid {isAndroid ? t('Restore from Google Drive') : t('Restore from iCloud')}
? t('account.cloud.button.restore.android')
: t('account.cloud.button.restore.ios')}
</Text> </Text>
</Flex> </Flex>
), ),
...@@ -299,7 +295,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -299,7 +295,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
testID={ElementName.WalletSettings} testID={ElementName.WalletSettings}
theme="secondary" theme="secondary"
onPress={onManageWallet}> onPress={onManageWallet}>
{t('account.wallet.button.manage')} {t('Manage wallet')}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
...@@ -314,7 +310,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -314,7 +310,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
<Icons.Plus color="$neutral2" size="$icon.12" strokeWidth={2} /> <Icons.Plus color="$neutral2" size="$icon.12" strokeWidth={2} />
</Flex> </Flex>
<Text color="$neutral2" variant="buttonLabel3"> <Text color="$neutral2" variant="buttonLabel3">
{t('account.wallet.button.add')} {t('Add wallet')}
</Text> </Text>
</Flex> </Flex>
</TouchableArea> </TouchableArea>
......
...@@ -46,9 +46,11 @@ export function ViewOnlyExplainerModal(): JSX.Element { ...@@ -46,9 +46,11 @@ export function ViewOnlyExplainerModal(): JSX.Element {
<WalletImage height="100%" preserveAspectRatio="xMidYMid slice" width="100%" /> <WalletImage height="100%" preserveAspectRatio="xMidYMid slice" width="100%" />
</Flex> </Flex>
<Flex alignItems="center" gap="$spacing4"> <Flex alignItems="center" gap="$spacing4">
<Text variant="subheading1">{t('account.wallet.viewOnly.title')}</Text> <Text variant="subheading1">{t('This wallet is view-only')}</Text>
<Text color="$neutral2" textAlign="center" variant="body2"> <Text color="$neutral2" textAlign="center" variant="body2">
{t('account.wallet.viewOnly.description')} {t(
'To swap, buy, send, and receive tokens, you need to import this wallet’s recovery phrase.'
)}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
...@@ -59,7 +61,7 @@ export function ViewOnlyExplainerModal(): JSX.Element { ...@@ -59,7 +61,7 @@ export function ViewOnlyExplainerModal(): JSX.Element {
px={40} px={40}
theme="primary" theme="primary"
onPress={onPressImportWallet}> onPress={onPressImportWallet}>
{t('account.wallet.viewOnly.button')} {t('Import wallet')}
</Button> </Button>
<Button <Button
alignSelf="center" alignSelf="center"
...@@ -69,7 +71,7 @@ export function ViewOnlyExplainerModal(): JSX.Element { ...@@ -69,7 +71,7 @@ export function ViewOnlyExplainerModal(): JSX.Element {
px={40} px={40}
theme="secondary" theme="secondary"
onPress={onClose}> onPress={onClose}>
{t('common.button.later')} {t('Maybe later')}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -6,7 +6,7 @@ import { MobileState } from 'src/app/reducer' ...@@ -6,7 +6,7 @@ import { MobileState } from 'src/app/reducer'
import { initialModalState } from 'src/features/modals/modalSlice' import { initialModalState } from 'src/features/modals/modalSlice'
import { renderWithProviders } from 'src/test/render' import { renderWithProviders } from 'src/test/render'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks' import { mockWalletPreloadedState } from 'wallet/src/test/fixtures'
const preloadedState = { const preloadedState = {
...mockWalletPreloadedState, ...mockWalletPreloadedState,
......
...@@ -174,7 +174,7 @@ const SwapFAB = memo(function _SwapFAB({ activeScale = 0.96 }: SwapTabBarButtonP ...@@ -174,7 +174,7 @@ const SwapFAB = memo(function _SwapFAB({ activeScale = 0.96 }: SwapTabBarButtonP
color="$sporeWhite" color="$sporeWhite"
numberOfLines={1} numberOfLines={1}
variant="buttonLabel2"> variant="buttonLabel2">
{t('common.button.swap')} {t('Swap')}
</Text> </Text>
</AnimatedFlex> </AnimatedFlex>
</TapGestureHandler> </TapGestureHandler>
...@@ -256,7 +256,7 @@ function ExploreTabBarButton({ activeScale = 0.98 }: ExploreTabBarButtonProps): ...@@ -256,7 +256,7 @@ function ExploreTabBarButton({ activeScale = 0.98 }: ExploreTabBarButtonProps):
pr="$spacing48" pr="$spacing48"
style={{ lineHeight: fonts.body1.lineHeight }} style={{ lineHeight: fonts.body1.lineHeight }}
variant="body1"> variant="body1">
{t('common.input.search')} {t('Search')}
</Text> </Text>
</Flex> </Flex>
</BlurView> </BlurView>
......
import { PersistState } from 'redux-persist'
import { apolloClient } from 'src/data/usePersistedApolloClient' import { apolloClient } from 'src/data/usePersistedApolloClient'
import { appRatingWatcherSaga } from 'src/features/appRating/saga' import { appRatingWatcherSaga } from 'src/features/appRating/saga'
import { cloudBackupsManagerSaga } from 'src/features/CloudBackup/saga' import { cloudBackupsManagerSaga } from 'src/features/CloudBackup/saga'
...@@ -8,7 +9,7 @@ import { telemetrySaga } from 'src/features/telemetry/saga' ...@@ -8,7 +9,7 @@ import { telemetrySaga } from 'src/features/telemetry/saga'
import { restoreMnemonicCompleteWatcher } from 'src/features/wallet/saga' import { restoreMnemonicCompleteWatcher } from 'src/features/wallet/saga'
import { walletConnectSaga } from 'src/features/walletConnect/saga' import { walletConnectSaga } from 'src/features/walletConnect/saga'
import { signWcRequestSaga } from 'src/features/walletConnect/signWcRequestSaga' import { signWcRequestSaga } from 'src/features/walletConnect/signWcRequestSaga'
import { spawn } from 'typed-redux-saga' import { delay, select, spawn } from 'typed-redux-saga'
import { appLanguageWatcherSaga } from 'wallet/src/features/language/saga' import { appLanguageWatcherSaga } from 'wallet/src/features/language/saga'
import { import {
swapActions, swapActions,
...@@ -44,6 +45,8 @@ import { ...@@ -44,6 +45,8 @@ import {
} from 'wallet/src/features/wallet/import/importAccountSaga' } from 'wallet/src/features/wallet/import/importAccountSaga'
import { getMonitoredSagaReducers, MonitoredSaga } from 'wallet/src/state/saga' import { getMonitoredSagaReducers, MonitoredSaga } from 'wallet/src/state/saga'
const REHYDRATION_STATUS_POLLING_INTERVAL = 50
// All regular sagas must be included here // All regular sagas must be included here
const sagas = [ const sagas = [
appLanguageWatcherSaga, appLanguageWatcherSaga,
...@@ -96,6 +99,18 @@ export const monitoredSagas: Record<string, MonitoredSaga> = { ...@@ -96,6 +99,18 @@ export const monitoredSagas: Record<string, MonitoredSaga> = {
export const monitoredSagaReducers = getMonitoredSagaReducers(monitoredSagas) export const monitoredSagaReducers = getMonitoredSagaReducers(monitoredSagas)
export function* mobileSaga() { export function* mobileSaga() {
// wait until redux-persist has finished rehydration
while (true) {
if (
yield* select(
(state: { _persist?: PersistState }): boolean | undefined => state._persist?.rehydrated
)
) {
break
}
yield* delay(REHYDRATION_STATUS_POLLING_INTERVAL)
}
for (const s of sagas) { for (const s of sagas) {
yield* spawn(s) yield* spawn(s)
} }
......
import { ImpactFeedbackStyle } from 'expo-haptics' import { ImpactFeedbackStyle } from 'expo-haptics'
import { memo, useMemo } from 'react' import { memo, useMemo } from 'react'
import { I18nManager } from 'react-native' import { I18nManager } from 'react-native'
import { SharedValue } from 'react-native-reanimated' import { SharedValue, useDerivedValue } from 'react-native-reanimated'
import { LineChart, LineChartProvider } from 'react-native-wagmi-charts' import { LineChart, LineChartProvider } from 'react-native-wagmi-charts'
import PriceExplorerAnimatedNumber from 'src/components/PriceExplorer/PriceExplorerAnimatedNumber' import PriceExplorerAnimatedNumber from 'src/components/PriceExplorer/PriceExplorerAnimatedNumber'
import { PriceExplorerError } from 'src/components/PriceExplorer/PriceExplorerError' import { PriceExplorerError } from 'src/components/PriceExplorer/PriceExplorerError'
...@@ -24,7 +24,7 @@ type PriceTextProps = { ...@@ -24,7 +24,7 @@ type PriceTextProps = {
loading: boolean loading: boolean
relativeChange?: SharedValue<number> relativeChange?: SharedValue<number>
numberOfDigits: PriceNumberOfDigits numberOfDigits: PriceNumberOfDigits
spotPrice?: number spotPrice?: SharedValue<number>
} }
function PriceTextSection({ loading, numberOfDigits, spotPrice }: PriceTextProps): JSX.Element { function PriceTextSection({ loading, numberOfDigits, spotPrice }: PriceTextProps): JSX.Element {
...@@ -34,9 +34,6 @@ function PriceTextSection({ loading, numberOfDigits, spotPrice }: PriceTextProps ...@@ -34,9 +34,6 @@ function PriceTextSection({ loading, numberOfDigits, spotPrice }: PriceTextProps
return ( return (
<Flex mx={mx}> <Flex mx={mx}>
{/* Specify maxWidth to allow text scaling. onLayout was sometimes called after more
than 5 seconds which is not acceptable so we have to provide the approximate width
of the PriceText component explicitly. */}
<PriceExplorerAnimatedNumber <PriceExplorerAnimatedNumber
currency={currency} currency={currency}
numberOfDigits={numberOfDigits} numberOfDigits={numberOfDigits}
...@@ -84,14 +81,15 @@ export const PriceExplorer = memo(function PriceExplorer({ ...@@ -84,14 +81,15 @@ export const PriceExplorer = memo(function PriceExplorer({
return { lastPricePoint: lastPoint, convertedPriceHistory: priceHistory } return { lastPricePoint: lastPoint, convertedPriceHistory: priceHistory }
}, [data, conversionRate]) }, [data, conversionRate])
const convertedSpotValue = useDerivedValue(() => conversionRate * (data?.spot?.value?.value ?? 0))
const convertedSpot = useMemo((): TokenSpotData | undefined => { const convertedSpot = useMemo((): TokenSpotData | undefined => {
return ( return (
data?.spot && { data?.spot && {
...data?.spot, ...data?.spot,
value: { value: conversionRate * (data?.spot?.value?.value ?? 0) }, value: convertedSpotValue,
} }
) )
}, [data, conversionRate]) }, [data, convertedSpotValue])
if ( if (
!loading && !loading &&
...@@ -135,7 +133,7 @@ export const PriceExplorer = memo(function PriceExplorer({ ...@@ -135,7 +133,7 @@ export const PriceExplorer = memo(function PriceExplorer({
loading={loading} loading={loading}
numberOfDigits={numberOfDigits} numberOfDigits={numberOfDigits}
relativeChange={convertedSpot?.relativeChange} relativeChange={convertedSpot?.relativeChange}
spotPrice={convertedSpot?.value.value} spotPrice={convertedSpot?.value}
/> />
{content} {content}
<TimeRangeGroup setDuration={setDuration} /> <TimeRangeGroup setDuration={setDuration} />
......
...@@ -40,6 +40,19 @@ const getEmphasizedNumberColor = ( ...@@ -40,6 +40,19 @@ const getEmphasizedNumberColor = (
return emphasizedColor return emphasizedColor
} }
const shouldUseSeparator = (
index: number,
commaIndex: number,
decimalPlaceIndex: number
): boolean => {
'worklet'
return (
(index - commaIndex) % 4 === 0 &&
index - commaIndex < 0 &&
index > commaIndex - decimalPlaceIndex
)
}
const NumbersMain = ({ const NumbersMain = ({
color, color,
backgroundColor, backgroundColor,
...@@ -163,11 +176,7 @@ const RollNumber = ({ ...@@ -163,11 +176,7 @@ const RollNumber = ({
// need it in case the current value is eg $999.00 but maximum value in chart is more than $1,000.00 // need it in case the current value is eg $999.00 but maximum value in chart is more than $1,000.00
// so it can hide the comma to avoid something like $,999.00 // so it can hide the comma to avoid something like $,999.00
const animatedWrapperSeparatorStyle = useAnimatedStyle(() => { const animatedWrapperSeparatorStyle = useAnimatedStyle(() => {
const isSeparator = if (!shouldUseSeparator(index, commaIndex, decimalPlace.value)) {
(index - commaIndex) % 4 === 0 &&
index - commaIndex < 0 &&
index > commaIndex - decimalPlace.value
if (!isSeparator) {
return { return {
width: withTiming(0), width: withTiming(0),
} }
...@@ -202,11 +211,7 @@ const RollNumber = ({ ...@@ -202,11 +211,7 @@ const RollNumber = ({
) )
} }
if ( if ((index - commaIndex) % 4 === 0 && index - commaIndex < 0) {
(index - commaIndex) % 4 === 0 &&
index - commaIndex < 0 &&
index > commaIndex - decimalPlace.value
) {
return ( return (
<Animated.View style={animatedWrapperSeparatorStyle}> <Animated.View style={animatedWrapperSeparatorStyle}>
<Animated.Text <Animated.Text
......
...@@ -29,9 +29,9 @@ export function PriceExplorerError({ ...@@ -29,9 +29,9 @@ export function PriceExplorerError({
justifyContent="center" justifyContent="center"
overflow="hidden"> overflow="hidden">
<BaseCard.ErrorState <BaseCard.ErrorState
description={t('token.priceExplorer.error.description')} description={t('Something went wrong.')}
retryButtonLabel={showRetry ? t('common.button.retry') : undefined} retryButtonLabel={showRetry ? t('Retry') : undefined}
title={t('token.priceExplorer.error.title')} title={t('Couldn’t load price chart')}
onRetry={onRetry} onRetry={onRetry}
/> />
</Flex> </Flex>
......
...@@ -2,7 +2,7 @@ import React from 'react' ...@@ -2,7 +2,7 @@ import React from 'react'
import * as charts from 'react-native-wagmi-charts' import * as charts from 'react-native-wagmi-charts'
import { DatetimeText, PriceText, RelativeChangeText } from 'src/components/PriceExplorer/Text' import { DatetimeText, PriceText, RelativeChangeText } from 'src/components/PriceExplorer/Text'
import { render, within } from 'src/test/test-utils' import { render, within } from 'src/test/test-utils'
import { amounts } from 'wallet/src/test/fixtures' import { Amounts } from 'wallet/src/test/gqlFixtures'
jest.mock('react-native-wagmi-charts') jest.mock('react-native-wagmi-charts')
const mockedUseLineChartPrice = charts.useLineChartPrice as jest.Mock const mockedUseLineChartPrice = charts.useLineChartPrice as jest.Mock
...@@ -12,7 +12,7 @@ const mockedUseLineChartDatetime = charts.useLineChartDatetime as jest.Mock ...@@ -12,7 +12,7 @@ const mockedUseLineChartDatetime = charts.useLineChartDatetime as jest.Mock
describe(PriceText, () => { describe(PriceText, () => {
it('renders without error', () => { it('renders without error', () => {
mockedUseLineChartPrice.mockReturnValue({ value: '' }) mockedUseLineChartPrice.mockReturnValue({ value: '' })
mockedUseLineChart.mockReturnValue({ data: [{ timestamp: 0, value: amounts.md().value }] }) mockedUseLineChart.mockReturnValue({ data: [{ timestamp: 0, value: Amounts.md.value }] })
const tree = render(<PriceText loading={false} />) const tree = render(<PriceText loading={false} />)
...@@ -21,7 +21,7 @@ describe(PriceText, () => { ...@@ -21,7 +21,7 @@ describe(PriceText, () => {
it('renders without error less than a dollar', () => { it('renders without error less than a dollar', () => {
mockedUseLineChartPrice.mockReturnValue({ value: '' }) mockedUseLineChartPrice.mockReturnValue({ value: '' })
mockedUseLineChart.mockReturnValue({ data: [{ timestamp: 0, value: amounts.xs().value }] }) mockedUseLineChart.mockReturnValue({ data: [{ timestamp: 0, value: Amounts.xs.value }] })
const tree = render(<PriceText loading={false} />) const tree = render(<PriceText loading={false} />)
...@@ -39,7 +39,7 @@ describe(PriceText, () => { ...@@ -39,7 +39,7 @@ describe(PriceText, () => {
it('shows active price when scrubbing', async () => { it('shows active price when scrubbing', async () => {
mockedUseLineChartPrice.mockReturnValue({ mockedUseLineChartPrice.mockReturnValue({
value: { value: amounts.sm().value.toString() }, value: { value: Amounts.sm.value.toString() },
}) })
const tree = render(<PriceText loading={false} />) const tree = render(<PriceText loading={false} />)
...@@ -48,7 +48,7 @@ describe(PriceText, () => { ...@@ -48,7 +48,7 @@ describe(PriceText, () => {
const wholePart = await within(animatedText).findByTestId('wholePart') const wholePart = await within(animatedText).findByTestId('wholePart')
const decimalPart = await within(animatedText).findByTestId('decimalPart') const decimalPart = await within(animatedText).findByTestId('decimalPart')
expect(wholePart.props.text).toBe(`$${amounts.sm().value}`) expect(wholePart.props.text).toBe(`$${Amounts.sm.value}`)
expect(decimalPart.props.text).toBe(`.00`) expect(decimalPart.props.text).toBe(`.00`)
}) })
}) })
......
...@@ -11,25 +11,9 @@ export const CURSOR_SIZE = CURSOR_INNER_SIZE + 6 ...@@ -11,25 +11,9 @@ export const CURSOR_SIZE = CURSOR_INNER_SIZE + 6
export const LINE_WIDTH = 1 export const LINE_WIDTH = 1
export const TIME_RANGES = [ export const TIME_RANGES = [
[ [HistoryDuration.Hour, i18n.t('1H'), ElementName.TimeFrame1H],
HistoryDuration.Hour, [HistoryDuration.Day, i18n.t('1D'), ElementName.TimeFrame1D],
i18n.t('token.priceExplorer.timeRangeLabel.hour'), [HistoryDuration.Week, i18n.t('1W'), ElementName.TimeFrame1W],
ElementName.TimeFrame1H, [HistoryDuration.Month, i18n.t('1M'), ElementName.TimeFrame1M],
], [HistoryDuration.Year, i18n.t('1Y'), ElementName.TimeFrame1Y],
[HistoryDuration.Day, i18n.t('token.priceExplorer.timeRangeLabel.day'), ElementName.TimeFrame1D],
[
HistoryDuration.Week,
i18n.t('token.priceExplorer.timeRangeLabel.week'),
ElementName.TimeFrame1W,
],
[
HistoryDuration.Month,
i18n.t('token.priceExplorer.timeRangeLabel.month'),
ElementName.TimeFrame1M,
],
[
HistoryDuration.Year,
i18n.t('token.priceExplorer.timeRangeLabel.year'),
ElementName.TimeFrame1Y,
],
] as const ] as const
...@@ -111,8 +111,9 @@ describe(useLineChartPrice, () => { ...@@ -111,8 +111,9 @@ describe(useLineChartPrice, () => {
}) })
it('returns currentSpot if it is provided', async () => { it('returns currentSpot if it is provided', async () => {
const { result, rerender } = renderHookWithProviders(useLineChartPrice, { const spotPrice = makeMutable(1)
initialProps: [1], const { result } = renderHookWithProviders(useLineChartPrice, {
initialProps: [spotPrice],
}) })
expect(result.current).toEqual({ expect(result.current).toEqual({
...@@ -121,9 +122,7 @@ describe(useLineChartPrice, () => { ...@@ -121,9 +122,7 @@ describe(useLineChartPrice, () => {
shouldAnimate: expect.objectContaining({ value: true }), shouldAnimate: expect.objectContaining({ value: true }),
}) })
await act(() => { spotPrice.value = 2
rerender([2])
})
await waitFor(() => { await waitFor(() => {
expect(result.current).toEqual({ expect(result.current).toEqual({
...@@ -150,7 +149,7 @@ describe(useLineChartPrice, () => { ...@@ -150,7 +149,7 @@ describe(useLineChartPrice, () => {
it('returns active cursor price even if currentSpot and data are provided', async () => { it('returns active cursor price even if currentSpot and data are provided', async () => {
mockCursorPrice('3') mockCursorPrice('3')
const { result } = renderHookWithProviders(useLineChartPrice, { const { result } = renderHookWithProviders(useLineChartPrice, {
initialProps: [4], initialProps: [makeMutable(4)],
}) })
expect(result.current).toEqual({ expect(result.current).toEqual({
...@@ -163,7 +162,7 @@ describe(useLineChartPrice, () => { ...@@ -163,7 +162,7 @@ describe(useLineChartPrice, () => {
it('updates returned active cursor price when it changes', async () => { it('updates returned active cursor price when it changes', async () => {
mockCursorPrice('1') mockCursorPrice('1')
const { result } = renderHookWithProviders(useLineChartPrice, { const { result } = renderHookWithProviders(useLineChartPrice, {
initialProps: [4], initialProps: [makeMutable(4)],
}) })
expect(result.current).toEqual( expect(result.current).toEqual(
......
...@@ -26,7 +26,9 @@ export type ValueAndFormattedWithAnimation = ValueAndFormatted & { ...@@ -26,7 +26,9 @@ export type ValueAndFormattedWithAnimation = ValueAndFormatted & {
* Wrapper around react-native-wagmi-chart#useLineChartPrice * Wrapper around react-native-wagmi-chart#useLineChartPrice
* @returns latest price when not scrubbing and active price when scrubbing * @returns latest price when not scrubbing and active price when scrubbing
*/ */
export function useLineChartPrice(currentSpot?: number): ValueAndFormattedWithAnimation { export function useLineChartPrice(
currentSpot?: SharedValue<number>
): ValueAndFormattedWithAnimation {
const { value: activeCursorPrice } = useRNWagmiChartLineChartPrice({ const { value: activeCursorPrice } = useRNWagmiChartLineChartPrice({
// do not round // do not round
precision: 18, precision: 18,
...@@ -55,7 +57,7 @@ export function useLineChartPrice(currentSpot?: number): ValueAndFormattedWithAn ...@@ -55,7 +57,7 @@ export function useLineChartPrice(currentSpot?: number): ValueAndFormattedWithAn
shouldAnimate.value = true shouldAnimate.value = true
// show spot price when chart not scrubbing, or if not available, show the last price in the chart // show spot price when chart not scrubbing, or if not available, show the last price in the chart
return currentSpot ?? data[data.length - 1]?.value ?? 0 return currentSpot?.value ?? data[data.length - 1]?.value ?? 0
}) })
const priceFormatted = useDerivedValue(() => { const priceFormatted = useDerivedValue(() => {
const { symbol, code } = currencyInfo const { symbol, code } = currencyInfo
......
...@@ -95,20 +95,22 @@ export function useTokenPriceHistory( ...@@ -95,20 +95,22 @@ export function useTokenPriceHistory(
}, [priceHistory]) }, [priceHistory])
const numberOfDigits = useMemo(() => { const numberOfDigits = useMemo(() => {
const max = maxBy(priceHistory, 'value') const maxPriceInHistory = maxBy(priceHistory, 'value')?.value
const convertedMaxValue = convertFiatAmount(max?.value).amount // If there is neither max price in history nor current price, return last number of digits
if (!maxPriceInHistory && price === undefined) {
return lastNumberOfDigits.current
}
const maxPrice = Math.max(maxPriceInHistory || 0, price || 0)
const convertedMaxValue = convertFiatAmount(maxPrice).amount
if (max) { const newNumberOfDigits = {
const newNumberOfDigits = { left: String(convertedMaxValue).split('.')[0]?.length || 10,
left: String(convertedMaxValue).split('.')[0]?.length || 10, right: Number(String(convertedMaxValue.toFixed(10)).split('.')[0]) > 0 ? 2 : 10,
right: Number(String(convertedMaxValue.toFixed(10)).split('.')[0]) > 0 ? 2 : 10,
}
lastNumberOfDigits.current = newNumberOfDigits
return newNumberOfDigits
} }
lastNumberOfDigits.current = newNumberOfDigits
return lastNumberOfDigits.current return newNumberOfDigits
}, [convertFiatAmount, priceHistory]) }, [convertFiatAmount, priceHistory, price])
const retry = useCallback(async () => { const retry = useCallback(async () => {
await refetch({ contract: currencyIdToContractInput(currencyId) }) await refetch({ contract: currencyIdToContractInput(currencyId) })
......
import { BarCodeScanner, BarCodeScannerResult } from 'expo-barcode-scanner' import { BarCodeScanner, BarCodeScannerResult } from 'expo-barcode-scanner'
import { Camera, CameraType } from 'expo-camera'
import { PermissionStatus } from 'expo-modules-core' import { PermissionStatus } from 'expo-modules-core'
import React, { memo, useCallback, useMemo, useState } from 'react' import React, { memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
...@@ -50,7 +51,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -50,7 +51,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const colors = useSporeColors() const colors = useSporeColors()
const dimensions = useDeviceDimensions() const dimensions = useDeviceDimensions()
const [permissionResponse, requestPermissionResponse] = BarCodeScanner.usePermissions() const [permissionResponse, requestPermissionResponse] = Camera.useCameraPermissions()
const permissionStatus = permissionResponse?.status const permissionStatus = permissionResponse?.status
const [isReadingImageFile, setIsReadingImageFile] = useState(false) const [isReadingImageFile, setIsReadingImageFile] = useState(false)
...@@ -94,7 +95,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -94,7 +95,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
)[0] )[0]
if (!result) { if (!result) {
Alert.alert(t('qrScanner.error.none')) Alert.alert(t('No QR code found'))
setIsReadingImageFile(false) setIsReadingImageFile(false)
return return
} }
...@@ -109,12 +110,16 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -109,12 +110,16 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
} }
if (permissionStatus === PermissionStatus.DENIED) { if (permissionStatus === PermissionStatus.DENIED) {
Alert.alert(t('qrScanner.error.camera.title'), t('qrScanner.error.camera.message'), [ Alert.alert(
{ text: t('common.navigation.systemSettings'), onPress: openSettings }, t('Camera is disabled'),
{ t('To scan a code, allow Camera access in system settings'),
text: t('common.button.notNow'), [
}, { text: t('Go to settings'), onPress: openSettings },
]) {
text: t('Not now'),
},
]
)
} }
}, [permissionStatus, requestPermissionResponse, t]) }, [permissionStatus, requestPermissionResponse, t])
...@@ -136,10 +141,12 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -136,10 +141,12 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
overflow="hidden" overflow="hidden"
width={dimensions.fullWidth}> width={dimensions.fullWidth}>
{permissionStatus === PermissionStatus.GRANTED && !isReadingImageFile && ( {permissionStatus === PermissionStatus.GRANTED && !isReadingImageFile && (
<BarCodeScanner <Camera
barCodeTypes={[BarCodeScanner.Constants.BarCodeType.qr]} barCodeScannerSettings={{
barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
}}
style={StyleSheet.absoluteFillObject} style={StyleSheet.absoluteFillObject}
type={BarCodeScanner.Constants.Type.back} type={CameraType.back}
onBarCodeScanned={shouldFreezeCamera ? undefined : handleBarCodeScanned} onBarCodeScanned={shouldFreezeCamera ? undefined : handleBarCodeScanned}
/> />
)} )}
...@@ -173,7 +180,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -173,7 +180,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
width="100%" width="100%"
onLayout={(event: LayoutChangeEvent): void => setInfoLayout(event.nativeEvent.layout)}> onLayout={(event: LayoutChangeEvent): void => setInfoLayout(event.nativeEvent.layout)}>
<Text color="$neutral1" variant="heading3"> <Text color="$neutral1" variant="heading3">
{t('qrScanner.title')} {t('Scan a QR code')}
</Text> </Text>
</Flex> </Flex>
{!shouldFreezeCamera ? ( {!shouldFreezeCamera ? (
...@@ -201,9 +208,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -201,9 +208,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
</Flex> </Flex>
<Flex style={{ marginTop: LOADER_SIZE + spacing.spacing24 }} /> <Flex style={{ marginTop: LOADER_SIZE + spacing.spacing24 }} />
<Text color="$neutral1" textAlign="center" variant="body1"> <Text color="$neutral1" textAlign="center" variant="body1">
{isWalletConnectModal {isWalletConnectModal ? t('Connecting...') : t('Loading...')}
? t('qrScanner.status.connecting')
: t('qrScanner.status.loading')}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
...@@ -266,7 +271,11 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -266,7 +271,11 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
icon={<Icons.Global color="$neutral2" />} icon={<Icons.Global color="$neutral2" />}
theme="secondary" theme="secondary"
onPress={props.onPressConnections}> onPress={props.onPressConnections}>
{t('qrScanner.button.connections', { count: props.numConnections })} {props.numConnections === 1
? t('1 app connected')
: t('{{numConnections}} apps connected', {
numConnections: props.numConnections,
})}
</Button> </Button>
)} )}
</Flex> </Flex>
......
...@@ -61,7 +61,7 @@ export function WalletQRCode({ address }: Props): JSX.Element | null { ...@@ -61,7 +61,7 @@ export function WalletQRCode({ address }: Props): JSX.Element | null {
/> />
<Text color="$neutral2" lineHeight={20} textAlign="center" variant="body3"> <Text color="$neutral2" lineHeight={20} textAlign="center" variant="body3">
{t('qrScanner.wallet.title')} {t('You can send tokens on all of our supported networks to this address.')}
</Text> </Text>
<TouchableArea onPress={(): void => setShowModal(true)}> <TouchableArea onPress={(): void => setShowModal(true)}>
<Flex row gap="$spacing4"> <Flex row gap="$spacing4">
...@@ -78,8 +78,10 @@ export function WalletQRCode({ address }: Props): JSX.Element | null { ...@@ -78,8 +78,10 @@ export function WalletQRCode({ address }: Props): JSX.Element | null {
{showModal && ( {showModal && (
<WarningModal <WarningModal
backgroundIconColor={colors.surface1.val} backgroundIconColor={colors.surface1.val}
caption={t('qrScanner.wallet.networks.description')} caption={t(
closeText={t('common.button.close')} 'Uniswap Wallet supports tokens on Ethereum, Polygon, Arbitrum, Optimism, Base, and BNB Chain. Right now, we only support NFTs on Ethereum.'
)}
closeText={t('Close')}
icon={ icon={
<NetworkLogos <NetworkLogos
centered centered
...@@ -89,7 +91,7 @@ export function WalletQRCode({ address }: Props): JSX.Element | null { ...@@ -89,7 +91,7 @@ export function WalletQRCode({ address }: Props): JSX.Element | null {
/> />
} }
modalName={ModalName.QRCodeNetworkInfo} modalName={ModalName.QRCodeNetworkInfo}
title={t('qrScanner.wallet.networks.title')} title={t('Supported Networks')}
onClose={(): void => setShowModal(false)}> onClose={(): void => setShowModal(false)}>
<LearnMoreLink url={uniswapUrls.helpArticleUrls.supportedNetworks} /> <LearnMoreLink url={uniswapUrls.helpArticleUrls.supportedNetworks} />
</WarningModal> </WarningModal>
......
...@@ -44,14 +44,18 @@ export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.E ...@@ -44,14 +44,18 @@ export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.E
onSelectRecipient(supportedURI.value) onSelectRecipient(supportedURI.value)
onClose() onClose()
} else { } else {
Alert.alert(t('qrScanner.recipient.error.title'), t('qrScanner.recipient.error.message'), [ Alert.alert(
{ t('Invalid QR Code'),
text: t('common.button.tryAgain'), t('Make sure that you’re scanning a valid Ethereum address QR code before trying again.'),
onPress: (): void => { [
setShouldFreezeCamera(false) {
text: t('Try again'),
onPress: (): void => {
setShouldFreezeCamera(false)
},
}, },
}, ]
]) )
} }
} }
...@@ -103,8 +107,8 @@ export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.E ...@@ -103,8 +107,8 @@ export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.E
)} )}
<Text color="$neutral1" variant="buttonLabel2"> <Text color="$neutral1" variant="buttonLabel2">
{currentScreenState === ScannerModalState.ScanQr {currentScreenState === ScannerModalState.ScanQr
? t('qrScanner.recipient.action.show') ? t('Show my QR code')
: t('qrScanner.recipient.action.scan')} : t('Scan a QR code')}
</Text> </Text>
</Flex> </Flex>
</TouchableArea> </TouchableArea>
......
...@@ -75,22 +75,22 @@ export function _RecipientSelect({ ...@@ -75,22 +75,22 @@ export function _RecipientSelect({
mt="$spacing16" mt="$spacing16"
px="$spacing24"> px="$spacing24">
<Flex row> <Flex row>
<Text variant="subheading1">{t('qrScanner.recipient.label.send')}</Text> <Text variant="subheading1">{t('Send')}</Text>
</Flex> </Flex>
<SearchBar <SearchBar
autoFocus autoFocus
backgroundColor="$surface2" backgroundColor="$surface2"
endAdornment={<QRScannerIconButton onPress={onPressQRScanner} />} endAdornment={<QRScannerIconButton onPress={onPressQRScanner} />}
placeholder={t('qrScanner.recipient.input.placeholder')} placeholder={t('Search ENS or address')}
value={pattern ?? ''} value={pattern ?? ''}
onBack={recipient ? onToggleShowRecipientSelector : undefined} onBack={recipient ? onToggleShowRecipientSelector : undefined}
onChangeText={onChangePattern} onChangeText={onChangePattern}
/> />
{noResults ? ( {noResults ? (
<Flex centered gap="$spacing12" mt="$spacing24" px="$spacing24"> <Flex centered gap="$spacing12" mt="$spacing24" px="$spacing24">
<Text variant="buttonLabel2">{t('qrScanner.recipient.results.empty')}</Text> <Text variant="buttonLabel2">{t('No results found')}</Text>
<Text color="$neutral3" textAlign="center" variant="body1"> <Text color="$neutral3" textAlign="center" variant="body1">
{t('qrScanner.recipient.results.error')} {t('The address you typed either does not exist or is spelled incorrectly.')}
</Text> </Text>
</Flex> </Flex>
) : ( ) : (
......
...@@ -8,34 +8,20 @@ import { useRecipients } from 'wallet/src/components/RecipientSearch/hooks' ...@@ -8,34 +8,20 @@ import { useRecipients } from 'wallet/src/components/RecipientSearch/hooks'
import { ChainId } from 'wallet/src/constants/chains' import { ChainId } from 'wallet/src/constants/chains'
import { SearchableRecipient } from 'wallet/src/features/address/types' import { SearchableRecipient } from 'wallet/src/features/address/types'
import { TransactionStateMap } from 'wallet/src/features/transactions/slice' import { TransactionStateMap } from 'wallet/src/features/transactions/slice'
import { TransactionStatus } from 'wallet/src/features/transactions/types' import { SendTokenTransactionInfo } from 'wallet/src/features/transactions/types'
import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice'
import { import {
account,
account2,
SAMPLE_SEED_ADDRESS_1, SAMPLE_SEED_ADDRESS_1,
SAMPLE_SEED_ADDRESS_2, SAMPLE_SEED_ADDRESS_2,
sendTokenTransactionInfo, sendTxDetailsConfirmed,
signerMnemonicAccount, sendTxDetailsFailed,
transactionDetails, sendTxDetailsPending,
} from 'wallet/src/test/fixtures' } from 'wallet/src/test/fixtures'
expect.extend({ toIncludeSameMembers }) expect.extend({ toIncludeSameMembers })
const sendTxDetailsPending = transactionDetails({
status: TransactionStatus.Pending,
typeInfo: sendTokenTransactionInfo(),
addedTime: 1487076708000,
})
const sendTxDetailsConfirmed = transactionDetails({
status: TransactionStatus.Success,
typeInfo: sendTokenTransactionInfo(),
addedTime: 1487076708000,
})
const sendTxDetailsFailed = transactionDetails({
status: TransactionStatus.Failed,
typeInfo: sendTokenTransactionInfo(),
addedTime: 1487076710000,
})
/** /**
* Tests interaction of mobile state with useRecipients hook * Tests interaction of mobile state with useRecipients hook
*/ */
...@@ -72,8 +58,8 @@ const getPreloadedState = (props?: PreloadedStateProps): PreloadedState<MobileSt ...@@ -72,8 +58,8 @@ const getPreloadedState = (props?: PreloadedStateProps): PreloadedState<MobileSt
} }
} }
const activeAccount = signerMnemonicAccount() const activeAccount = account
const inactiveAccount = signerMnemonicAccount() const inactiveAccount = account2
const validatedAddressRecipient: SearchableRecipient = { const validatedAddressRecipient: SearchableRecipient = {
address: SAMPLE_SEED_ADDRESS_1, address: SAMPLE_SEED_ADDRESS_1,
} }
...@@ -89,15 +75,15 @@ const recentRecipientsSectionResult = { ...@@ -89,15 +75,15 @@ const recentRecipientsSectionResult = {
title: 'Recent', title: 'Recent',
data: [ data: [
{ {
address: sendTxDetailsFailed.typeInfo.recipient, address: (sendTxDetailsFailed.typeInfo as SendTokenTransactionInfo).recipient,
name: '', name: '',
}, },
{ {
address: sendTxDetailsConfirmed.typeInfo.recipient, address: (sendTxDetailsConfirmed.typeInfo as SendTokenTransactionInfo).recipient,
name: '', name: '',
}, },
{ {
address: sendTxDetailsPending.typeInfo.recipient, address: (sendTxDetailsPending.typeInfo as SendTokenTransactionInfo).recipient,
name: '', name: '',
}, },
], ],
...@@ -176,7 +162,9 @@ describe(useRecipients, () => { ...@@ -176,7 +162,9 @@ describe(useRecipients, () => {
expect(result.current.searchableRecipientOptions).toEqual( expect(result.current.searchableRecipientOptions).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ {
data: expect.objectContaining({ address: SAMPLE_SEED_ADDRESS_1 }), data: {
address: SAMPLE_SEED_ADDRESS_1,
},
key: SAMPLE_SEED_ADDRESS_1, key: SAMPLE_SEED_ADDRESS_1,
}, },
]) ])
...@@ -215,7 +203,7 @@ describe(useRecipients, () => { ...@@ -215,7 +203,7 @@ describe(useRecipients, () => {
title: 'Recent', title: 'Recent',
data: [ data: [
{ {
address: sendTxDetailsPending.typeInfo.recipient, address: (sendTxDetailsPending.typeInfo as SendTokenTransactionInfo).recipient,
name: '', name: '',
}, },
], ],
...@@ -243,15 +231,15 @@ describe(useRecipients, () => { ...@@ -243,15 +231,15 @@ describe(useRecipients, () => {
// This method doesn't check the order of the elements // This method doesn't check the order of the elements
expect(section.data).toIncludeSameMembers([ expect(section.data).toIncludeSameMembers([
{ {
address: sendTxDetailsPending.typeInfo.recipient, address: (sendTxDetailsPending.typeInfo as SendTokenTransactionInfo).recipient,
name: '', name: '',
}, },
{ {
address: sendTxDetailsConfirmed.typeInfo.recipient, address: (sendTxDetailsConfirmed.typeInfo as SendTokenTransactionInfo).recipient,
name: '', name: '',
}, },
{ {
address: sendTxDetailsFailed.typeInfo.recipient, address: (sendTxDetailsFailed.typeInfo as SendTokenTransactionInfo).recipient,
name: '', name: '',
}, },
]) ])
......
...@@ -29,7 +29,9 @@ export function RemoveLastMnemonicWalletFooter({ ...@@ -29,7 +29,9 @@ export function RemoveLastMnemonicWalletFooter({
text={ text={
<Flex> <Flex>
<Text color="$neutral2" variant="body3"> <Text color="$neutral2" variant="body3">
{t('account.wallet.remove.check')} {t(
'I backed up my recovery phrase and understand that Uniswap Labs can’t help me recover my wallets if I failed to do so.'
)}
</Text> </Text>
</Flex> </Flex>
} }
...@@ -44,7 +46,7 @@ export function RemoveLastMnemonicWalletFooter({ ...@@ -44,7 +46,7 @@ export function RemoveLastMnemonicWalletFooter({
testID={ElementName.Confirm} testID={ElementName.Confirm}
theme="detrimental" theme="detrimental"
onPress={onPress}> onPress={onPress}>
{!inProgress ? t('account.wallet.button.remove') : undefined} {!inProgress ? t('Remove wallet') : undefined}
</Button> </Button>
</Flex> </Flex>
</> </>
......
...@@ -189,7 +189,7 @@ export function RemoveWalletModal(): JSX.Element | null { ...@@ -189,7 +189,7 @@ export function RemoveWalletModal(): JSX.Element | null {
<AnimatedFlex style={animatedCancelButtonSpanStyles} /> <AnimatedFlex style={animatedCancelButtonSpanStyles} />
) : ( ) : (
<Button fill disabled={inProgress} theme="outline" onPress={onClose}> <Button fill disabled={inProgress} theme="outline" onPress={onClose}>
{t('common.button.cancel')} {t('Cancel')}
</Button> </Button>
)} )}
<Button <Button
......
...@@ -9,7 +9,7 @@ import WalletIcon from 'ui/src/assets/icons/wallet-filled.svg' ...@@ -9,7 +9,7 @@ import WalletIcon from 'ui/src/assets/icons/wallet-filled.svg'
import { ThemeNames } from 'ui/src/theme' import { ThemeNames } from 'ui/src/theme'
import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types' import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types'
import { useDisplayName } from 'wallet/src/features/wallet/hooks' import { useDisplayName } from 'wallet/src/features/wallet/hooks'
import { getCloudProviderName } from 'wallet/src/utils/platform' import { isAndroid } from 'wallet/src/utils/platform'
export enum RemoveWalletStep { export enum RemoveWalletStep {
Warning = 'warning', Warning = 'warning',
...@@ -49,19 +49,21 @@ export const useModalContent = ({ ...@@ -49,19 +49,21 @@ export const useModalContent = ({
if (isRemovingRecoveryPhrase && !isReplacing && currentStep === RemoveWalletStep.Warning) { if (isRemovingRecoveryPhrase && !isReplacing && currentStep === RemoveWalletStep.Warning) {
return { return {
title: ( title: (
<Text color="$neutral1" variant="body1"> <Trans t={t}>
<Trans i18nKey="account.seedPhrase.remove.initial.title"> <Text color="$neutral1" variant="body1">
You’re removing You’re removing{' '}
<Text color="$statusCritical" variant="body1"> <Text color="$statusCritical" variant="body1">
{{ walletName: displayName?.name }} {{ wallet: displayName?.name }}
</Text> </Text>
</Trans> </Text>
</Text> </Trans>
),
description: t(
'This will remove your wallet from this device along with your recovery phrase.'
), ),
description: t('account.seedPhrase.remove.initial.description'),
Icon: TrashIcon, Icon: TrashIcon,
iconColorLabel: 'statusCritical', iconColorLabel: 'statusCritical',
actionButtonLabel: t('common.button.continue'), actionButtonLabel: t('Continue'),
actionButtonTheme: 'detrimental', actionButtonTheme: 'detrimental',
} }
} }
...@@ -69,11 +71,13 @@ export const useModalContent = ({ ...@@ -69,11 +71,13 @@ export const useModalContent = ({
// 1st speed bump when replacing recovery phrase // 1st speed bump when replacing recovery phrase
if (isRemovingRecoveryPhrase && isReplacing && currentStep === RemoveWalletStep.Warning) { if (isRemovingRecoveryPhrase && isReplacing && currentStep === RemoveWalletStep.Warning) {
return { return {
title: t('account.wallet.button.import'), title: t('Import a new wallet'),
description: t('account.seedPhrase.remove.import.description'), description: t(
'You can only store one recovery phrase at a time. To continue importing a new one, you’ll need to remove your current recovery phrase and any associated wallets from this device.'
),
Icon: WalletIcon, Icon: WalletIcon,
iconColorLabel: 'neutral2', iconColorLabel: 'neutral2',
actionButtonLabel: t('common.button.continue'), actionButtonLabel: t('Continue'),
actionButtonTheme: 'secondary', actionButtonTheme: 'secondary',
} }
} }
...@@ -82,19 +86,25 @@ export const useModalContent = ({ ...@@ -82,19 +86,25 @@ export const useModalContent = ({
if (isRemovingRecoveryPhrase && currentStep === RemoveWalletStep.Final) { if (isRemovingRecoveryPhrase && currentStep === RemoveWalletStep.Final) {
return { return {
title: ( title: (
<Text color="$neutral1" variant="body1"> <Trans t={t}>
<Trans i18nKey="account.seedPhrase.remove.final.title"> <Text color="$neutral1" variant="body1">
You’re removing your You’re removing your{' '}
<Text color="$neutral1" variant="body1"> <Text color="$neutral1" variant="body1">
recovery phrase recovery phrase
</Text> </Text>
</Trans> </Text>
</Text> </Trans>
), ),
description: ( description: isAndroid ? (
<Trans i18nKey="account.seedPhrase.remove.final.description"> <Trans t={t}>
Make sure you’ve written down your recovery phrase or backed it up on Make sure you’ve written down your recovery phrase or backed it up on Google Drive.{' '}
{{ cloudProviderName: getCloudProviderName() }}. <Text color="$statusCritical" maxFontSizeMultiplier={1.4} variant="body3">
You will not be able to access your funds otherwise.
</Text>
</Trans>
) : (
<Trans t={t}>
Make sure you’ve written down your recovery phrase or backed it up on iCloud.{' '}
<Text color="$statusCritical" maxFontSizeMultiplier={1.4} variant="body3"> <Text color="$statusCritical" maxFontSizeMultiplier={1.4} variant="body3">
You will not be able to access your funds otherwise. You will not be able to access your funds otherwise.
</Text> </Text>
...@@ -109,32 +119,34 @@ export const useModalContent = ({ ...@@ -109,32 +119,34 @@ export const useModalContent = ({
if (account?.type === AccountType.SignerMnemonic && currentStep === RemoveWalletStep.Final) { if (account?.type === AccountType.SignerMnemonic && currentStep === RemoveWalletStep.Final) {
const associatedAccountNames = concatListOfAccountNames( const associatedAccountNames = concatListOfAccountNames(
associatedAccounts.filter((aa) => aa.address !== account?.address), associatedAccounts.filter((aa) => aa.address !== account?.address),
', ' t('and')
) )
return { return {
title: ( title: (
<Text color="$neutral1" variant="body1"> <Trans t={t}>
<Trans i18nKey="account.seedPhrase.remove.initial.title"> <Text color="$neutral1" variant="body1">
You’re removing You’re removing{' '}
<Text color="$statusCritical" variant="body1"> <Text color="$statusCritical" variant="body1">
{{ walletName: displayName?.name }} {{ wallet: displayName?.name }}
</Text> </Text>
</Trans>
</Text>
),
description: (
<Trans i18nKey="account.seedPhrase.remove.mnemonic.description">
It shares the same recovery phrase as
<Text color="$neutral1" variant="body3">
{{ walletNames: associatedAccountNames }}
</Text> </Text>
. Your recovery phrase will remain stored until you delete all remaining wallets.
</Trans> </Trans>
), ),
description: (
<Text color="$neutral2" variant="body3">
<Trans t={t}>
It shares the same recovery phrase as{' '}
<Text color="$neutral1" variant="body3">
{{ wallets: associatedAccountNames }}
</Text>
. Your recovery phrase will remain stored until you delete all remaining wallets.
</Trans>
</Text>
),
Icon: TrashIcon, Icon: TrashIcon,
iconColorLabel: 'statusCritical', iconColorLabel: 'statusCritical',
actionButtonLabel: t('common.button.remove'), actionButtonLabel: t('Remove'),
actionButtonTheme: 'detrimental', actionButtonTheme: 'detrimental',
} }
} }
...@@ -143,19 +155,21 @@ export const useModalContent = ({ ...@@ -143,19 +155,21 @@ export const useModalContent = ({
if (account?.type === AccountType.Readonly && currentStep === RemoveWalletStep.Final) { if (account?.type === AccountType.Readonly && currentStep === RemoveWalletStep.Final) {
return { return {
title: ( title: (
<Text color="$neutral1" variant="body1"> <Trans t={t}>
<Trans i18nKey="account.seedPhrase.remove.initial.title"> <Text color="$neutral1" variant="body1">
You’re removing You’re removing{' '}
<Text color="$neutral2" variant="body1"> <Text color="$neutral2" variant="body1">
{{ walletName: displayName?.name }} {{ wallet: displayName?.name }}
</Text> </Text>
</Trans> </Text>
</Text> </Trans>
),
description: t(
'You can always add back view-only wallets by entering the wallet’s address.'
), ),
description: t('account.wallet.remove.viewOnly'),
Icon: TrashIcon, Icon: TrashIcon,
iconColorLabel: 'neutral2', iconColorLabel: 'neutral2',
actionButtonLabel: t('common.button.remove'), actionButtonLabel: t('Remove'),
actionButtonTheme: 'secondary', actionButtonTheme: 'secondary',
} }
} }
......
...@@ -51,17 +51,19 @@ export function RestoreWalletModal(): JSX.Element | null { ...@@ -51,17 +51,19 @@ export function RestoreWalletModal(): JSX.Element | null {
/> />
</Flex> </Flex>
<Text textAlign="center" variant="body1"> <Text textAlign="center" variant="body1">
{t('account.wallet.button.restore')} {t('Restore wallet')}
</Text> </Text>
<Text color="$neutral2" textAlign="center" variant="body2"> <Text color="$neutral2" textAlign="center" variant="body2">
{t('account.wallet.restore.description')} {t(
'Because you’re on a new device, you’ll need to restore your recovery phrase. This will allow you to swap and send tokens.'
)}
</Text> </Text>
<Flex centered row gap="$spacing12" pt="$spacing12"> <Flex centered row gap="$spacing12" pt="$spacing12">
<Button fill theme="tertiary" onPress={onDismiss}> <Button fill theme="tertiary" onPress={onDismiss}>
{t('common.button.dismiss')} {t('Dismiss')}
</Button> </Button>
<Button fill testID={ElementName.RestoreWallet} theme="primary" onPress={onRestore}> <Button fill testID={ElementName.RestoreWallet} theme="primary" onPress={onRestore}>
{t('common.button.restore')} {t('Restore')}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -7,7 +7,6 @@ import { ...@@ -7,7 +7,6 @@ import {
} from 'wallet/src/components/modals/WarningModal/WarningModal' } from 'wallet/src/components/modals/WarningModal/WarningModal'
import { WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types' import { WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { isAndroid } from 'wallet/src/utils/platform'
type Props = { type Props = {
isTouchIdDevice: boolean isTouchIdDevice: boolean
...@@ -21,19 +20,18 @@ export function BiometricAuthWarningModal({ ...@@ -21,19 +20,18 @@ export function BiometricAuthWarningModal({
onClose, onClose,
}: Props): JSX.Element { }: Props): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const biometricsMethod = useBiometricName(isTouchIdDevice) const authenticationTypeName = useBiometricName(isTouchIdDevice)
return ( return (
<WarningModal <WarningModal
caption={ caption={t(
isAndroid 'If you don’t turn on {{authenticationTypeName}}, anyone who gains access to your device can open Uniswap Wallet and make transactions.',
? t('settings.setting.biometrics.warning.message.android') { authenticationTypeName }
: t('settings.setting.biometrics.warning.message.ios', { biometricsMethod }) )}
} closeText={t('Back')}
closeText={t('common.button.back')} confirmText={t('Skip')}
confirmText={t('common.button.skip')}
modalName={ModalName.FaceIDWarning} modalName={ModalName.FaceIDWarning}
severity={WarningSeverity.Low} severity={WarningSeverity.Low}
title={t('settings.setting.biometrics.warning.title')} title={t('Are you sure?')}
onClose={onClose} onClose={onClose}
onConfirm={onConfirm} onConfirm={onConfirm}
/> />
......
...@@ -164,7 +164,7 @@ export const TokenBalanceListInner = forwardRef< ...@@ -164,7 +164,7 @@ export const TokenBalanceListInner = forwardRef<
const ListHeaderComponent = useMemo(() => { const ListHeaderComponent = useMemo(() => {
return hasError ? ( return hasError ? (
<AnimatedFlex entering={FadeInDown} exiting={FadeOut} px="$spacing24" py="$spacing8"> <AnimatedFlex entering={FadeInDown} exiting={FadeOut} px="$spacing24" py="$spacing8">
<BaseCard.InlineErrorState title={t('home.tokens.error.fetch')} onRetry={refetch} /> <BaseCard.InlineErrorState title={t('Failed to fetch token balances')} onRetry={refetch} />
</AnimatedFlex> </AnimatedFlex>
) : null ) : null
}, [hasError, refetch, t]) }, [hasError, refetch, t])
...@@ -204,8 +204,8 @@ export const TokenBalanceListInner = forwardRef< ...@@ -204,8 +204,8 @@ export const TokenBalanceListInner = forwardRef<
) : ( ) : (
<Flex fill grow justifyContent="center" style={containerProps?.emptyContainerStyle}> <Flex fill grow justifyContent="center" style={containerProps?.emptyContainerStyle}>
<BaseCard.ErrorState <BaseCard.ErrorState
retryButtonLabel={t('common.button.retry')} retryButtonLabel="Retry"
title={t('home.tokens.error.load')} title={t('Couldn’t load token balances')}
onRetry={(): void | undefined => refetch?.()} onRetry={(): void | undefined => refetch?.()}
/> />
</Flex> </Flex>
......
...@@ -68,7 +68,7 @@ export function TokenBalances({ ...@@ -68,7 +68,7 @@ export function TokenBalances({
{hasOtherChainBalances && otherChainBalances ? ( {hasOtherChainBalances && otherChainBalances ? (
<Flex gap="$spacing8"> <Flex gap="$spacing8">
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
{t('token.balances.other')} {t('Balances on other networks')}
</Text> </Text>
<Flex gap="$spacing12"> <Flex gap="$spacing12">
{otherChainBalances.map((balance) => { {otherChainBalances.map((balance) => {
...@@ -106,9 +106,7 @@ export function CurrentChainBalance({ ...@@ -106,9 +106,7 @@ export function CurrentChainBalance({
<Flex row> <Flex row>
<Flex fill gap="$spacing8"> <Flex fill gap="$spacing8">
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
{isReadonly {isReadonly ? t('{{owner}}’s balance', { owner: displayName }) : t('Your balance')}
? t('token.balances.viewOnly', { ownerAddress: displayName })
: t('token.balances.main')}
</Text> </Text>
<Flex fill gap="$spacing4"> <Flex fill gap="$spacing4">
<Text variant="heading3"> <Text variant="heading3">
......
...@@ -58,13 +58,13 @@ export function TokenDetailsActionButtons({ ...@@ -58,13 +58,13 @@ export function TokenDetailsActionButtons({
px="$spacing16"> px="$spacing16">
<CTAButton <CTAButton
element={ElementName.Buy} element={ElementName.Buy}
title={t('common.button.buy')} title={t('Buy')}
tokenColor={tokenColor} tokenColor={tokenColor}
onPress={onPressBuy} onPress={onPressBuy}
/> />
<CTAButton <CTAButton
element={ElementName.Sell} element={ElementName.Sell}
title={t('common.button.sell')} title={t('Sell')}
tokenColor={tokenColor} tokenColor={tokenColor}
onPress={onPressSell} onPress={onPressSell}
/> />
......
...@@ -35,7 +35,7 @@ export function TokenDetailsLinks({ ...@@ -35,7 +35,7 @@ export function TokenDetailsLinks({
<View style={{ marginHorizontal: -14 }}> <View style={{ marginHorizontal: -14 }}>
<Flex gap="$spacing8"> <Flex gap="$spacing8">
<Text color="$neutral2" mx="$spacing16" variant="subheading2"> <Text color="$neutral2" mx="$spacing16" variant="subheading2">
{t('token.links.title')} {t('Links')}
</Text> </Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false}> <ScrollView horizontal showsHorizontalScrollIndicator={false}>
<Flex row gap="$spacing8" px="$spacing16"> <Flex row gap="$spacing8" px="$spacing16">
...@@ -51,7 +51,7 @@ export function TokenDetailsLinks({ ...@@ -51,7 +51,7 @@ export function TokenDetailsLinks({
Icon={GlobeIcon} Icon={GlobeIcon}
buttonType={LinkButtonType.Link} buttonType={LinkButtonType.Link}
element={ElementName.TokenLinkWebsite} element={ElementName.TokenLinkWebsite}
label={t('token.links.website')} label={t('Website')}
value={homepageUrl} value={homepageUrl}
/> />
)} )}
...@@ -60,7 +60,7 @@ export function TokenDetailsLinks({ ...@@ -60,7 +60,7 @@ export function TokenDetailsLinks({
Icon={TwitterIcon} Icon={TwitterIcon}
buttonType={LinkButtonType.Link} buttonType={LinkButtonType.Link}
element={ElementName.TokenLinkTwitter} element={ElementName.TokenLinkTwitter}
label={t('token.links.twitter')} label={t('Twitter')}
value={getTwitterLink(twitterName)} value={getTwitterLink(twitterName)}
/> />
)} )}
...@@ -68,7 +68,7 @@ export function TokenDetailsLinks({ ...@@ -68,7 +68,7 @@ export function TokenDetailsLinks({
<LinkButton <LinkButton
buttonType={LinkButtonType.Copy} buttonType={LinkButtonType.Copy}
element={ElementName.Copy} element={ElementName.Copy}
label={t('token.links.contract')} label={t('Contract')}
value={address} value={address}
/> />
)} )}
......
...@@ -57,7 +57,7 @@ export function TokenDetailsMarketData({ ...@@ -57,7 +57,7 @@ export function TokenDetailsMarketData({
return ( return (
<Flex gap="$spacing8"> <Flex gap="$spacing8">
<StatsRow <StatsRow
label={t('token.stats.marketCap')} label={t('Market Cap')}
statsIcon={ statsIcon={
<Icons.ChartPie color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} /> <Icons.ChartPie color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} />
}> }>
...@@ -66,7 +66,7 @@ export function TokenDetailsMarketData({ ...@@ -66,7 +66,7 @@ export function TokenDetailsMarketData({
</Text> </Text>
</StatsRow> </StatsRow>
<StatsRow <StatsRow
label={t('token.stats.fullyDilutedValuation')} label={t('Fully Diluted Valuation')}
statsIcon={ statsIcon={
<Icons.ChartPie color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} /> <Icons.ChartPie color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} />
}> }>
...@@ -75,7 +75,7 @@ export function TokenDetailsMarketData({ ...@@ -75,7 +75,7 @@ export function TokenDetailsMarketData({
</Text> </Text>
</StatsRow> </StatsRow>
<StatsRow <StatsRow
label={t('token.stats.volume')} label={t('24h Volume')}
statsIcon={ statsIcon={
<Icons.ChartBar color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} /> <Icons.ChartBar color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} />
}> }>
...@@ -84,7 +84,7 @@ export function TokenDetailsMarketData({ ...@@ -84,7 +84,7 @@ export function TokenDetailsMarketData({
</Text> </Text>
</StatsRow> </StatsRow>
<StatsRow <StatsRow
label={t('token.stats.priceHighYear')} label={t('52W High')}
statsIcon={ statsIcon={
<Icons.TrendUp color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} /> <Icons.TrendUp color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} />
}> }>
...@@ -93,7 +93,7 @@ export function TokenDetailsMarketData({ ...@@ -93,7 +93,7 @@ export function TokenDetailsMarketData({
</Text> </Text>
</StatsRow> </StatsRow>
<StatsRow <StatsRow
label={t('token.stats.priceLowYear')} label={t('52W Low')}
statsIcon={ statsIcon={
<Icons.TrendDown color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} /> <Icons.TrendDown color={tokenColor ?? defaultTokenColor} size={iconSizes.icon16} />
}> }>
...@@ -147,7 +147,7 @@ export function TokenDetailsStats({ ...@@ -147,7 +147,7 @@ export function TokenDetailsStats({
<Flex gap="$spacing4"> <Flex gap="$spacing4">
{name && ( {name && (
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
{t('token.stats.section.about', { token: name })} {t('About {{ token }}', { token: name })}
</Text> </Text>
)} )}
<Flex gap="$spacing16"> <Flex gap="$spacing16">
...@@ -177,7 +177,7 @@ export function TokenDetailsStats({ ...@@ -177,7 +177,7 @@ export function TokenDetailsStats({
</Text> </Text>
</Flex> </Flex>
<Text color="$blue400" variant="buttonLabel4"> <Text color="$blue400" variant="buttonLabel4">
{t('token.stats.translation.original')} {t('Show original')}
</Text> </Text>
</Flex> </Flex>
) : ( ) : (
...@@ -185,7 +185,7 @@ export function TokenDetailsStats({ ...@@ -185,7 +185,7 @@ export function TokenDetailsStats({
<Flex row alignItems="center" gap="$spacing12"> <Flex row alignItems="center" gap="$spacing12">
<Icons.Language color="$neutral2" size="$icon.20" /> <Icons.Language color="$neutral2" size="$icon.20" />
<Text color="$neutral2" variant="body3"> <Text color="$neutral2" variant="body3">
{t('token.stats.translation.translate', { {t('Translate to {{ language }}', {
language: currentLanguageInfo.displayName, language: currentLanguageInfo.displayName,
})} })}
</Text> </Text>
...@@ -199,7 +199,7 @@ export function TokenDetailsStats({ ...@@ -199,7 +199,7 @@ export function TokenDetailsStats({
)} )}
<Flex gap="$spacing4"> <Flex gap="$spacing4">
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
{t('token.stats.title')} {t('Stats')}
</Text> </Text>
<TokenDetailsMarketData <TokenDetailsMarketData
fullyDilutedValuation={fullyDilutedValuation} fullyDilutedValuation={fullyDilutedValuation}
......
import { useCrossChainBalances, useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' import { useCrossChainBalances, useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { act, renderHook, waitFor } from 'src/test/test-utils' import { act, renderHook, waitFor } from 'src/test/test-utils'
import { USDBC_BASE, USDC_ARBITRUM } from 'wallet/src/constants/tokens'
import { Chain } from 'wallet/src/data/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils' import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
import { import { SAMPLE_CURRENCY_ID_1, mockWalletPreloadedState } from 'wallet/src/test/fixtures'
SAMPLE_CURRENCY_ID_1, import { Portfolio, Portfolio2, PortfolioBalancesById } from 'wallet/src/test/gqlFixtures'
portfolio,
portfolioBalances,
tokenBalance,
usdcArbitrumToken,
usdcBaseToken,
} from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
const mockedNavigation = { const mockedNavigation = {
navigate: jest.fn(), navigate: jest.fn(),
...@@ -32,7 +28,7 @@ describe(useCrossChainBalances, () => { ...@@ -32,7 +28,7 @@ describe(useCrossChainBalances, () => {
describe('currentChainBalance', () => { describe('currentChainBalance', () => {
it('returns null if there are no balances for the specified currency', async () => { it('returns null if there are no balances for the specified currency', async () => {
const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), { const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), {
preloadedState: mockWalletPreloadedState(), preloadedState: mockWalletPreloadedState,
}) })
await act(() => undefined) await act(() => undefined)
...@@ -45,25 +41,19 @@ describe(useCrossChainBalances, () => { ...@@ -45,25 +41,19 @@ describe(useCrossChainBalances, () => {
}) })
it('returns balance if there is at least one for the specified currency', async () => { it('returns balance if there is at least one for the specified currency', async () => {
const Portfolio = portfolio() const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), {
const currentChainBalance = portfolioBalances({ portfolio: Portfolio })[0]! preloadedState: mockWalletPreloadedState,
resolvers: {
const { result } = renderHook( Query: {
() => useCrossChainBalances(currentChainBalance.currencyInfo.currencyId, null), portfolios: () => [Portfolio],
{
preloadedState: mockWalletPreloadedState(),
resolvers: {
Query: {
portfolios: () => [Portfolio],
},
}, },
} },
) })
await waitFor(() => { await waitFor(() => {
expect(result.current).toEqual( expect(result.current).toEqual(
expect.objectContaining({ expect.objectContaining({
currentChainBalance, currentChainBalance: PortfolioBalancesById[SAMPLE_CURRENCY_ID_1],
}) })
) )
}) })
...@@ -71,9 +61,15 @@ describe(useCrossChainBalances, () => { ...@@ -71,9 +61,15 @@ describe(useCrossChainBalances, () => {
}) })
describe('otherChainBalances', () => { describe('otherChainBalances', () => {
// Current chain balance will be determined by the following currency id
const currencyId1 = `${fromGraphQLChain(Chain.Base)}-${USDBC_BASE.address.toLocaleLowerCase()}`
const currencyId2 = `${fromGraphQLChain(
Chain.Arbitrum
)}-${USDC_ARBITRUM.address.toLocaleLowerCase()}`
it('returns null if there are no bridged currencies', async () => { it('returns null if there are no bridged currencies', async () => {
const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), { const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), {
preloadedState: mockWalletPreloadedState(), preloadedState: mockWalletPreloadedState,
}) })
await act(() => undefined) await act(() => undefined)
...@@ -86,35 +82,25 @@ describe(useCrossChainBalances, () => { ...@@ -86,35 +82,25 @@ describe(useCrossChainBalances, () => {
}) })
it('does not include current chain balance in other chain balances', async () => { it('does not include current chain balance in other chain balances', async () => {
const tokenBalances = [ const bridgeInfo: { chain: Chain; address?: string }[] = [
tokenBalance({ token: usdcBaseToken() }), { chain: Chain.Base, address: USDBC_BASE.address.toLocaleLowerCase() },
tokenBalance({ token: usdcArbitrumToken() }), { chain: Chain.Arbitrum, address: USDC_ARBITRUM.address.toLocaleLowerCase() },
] ]
const { result } = renderHook(() => useCrossChainBalances(currencyId1, bridgeInfo), {
const bridgeInfo = tokenBalances.map((balance) => ({ preloadedState: mockWalletPreloadedState,
chain: balance.token.chain, resolvers: {
address: balance.token?.address, Query: {
})) portfolios: () => [Portfolio2],
const Portfolio = portfolio({ tokenBalances })
const [currentChainBalance, ...otherChainBalances] = portfolioBalances({
portfolio: Portfolio,
})
const { result } = renderHook(
() => useCrossChainBalances(currentChainBalance!.currencyInfo.currencyId, bridgeInfo),
{
preloadedState: mockWalletPreloadedState(),
resolvers: {
Query: {
portfolios: () => [Portfolio],
},
}, },
} },
) })
await waitFor(() => { await waitFor(() => {
expect(result.current).toEqual( expect(result.current).toEqual(
expect.objectContaining({ currentChainBalance, otherChainBalances }) expect.objectContaining({
currentChainBalance: PortfolioBalancesById[currencyId1],
otherChainBalances: [PortfolioBalancesById[currencyId2]],
})
) )
}) })
}) })
......
...@@ -71,8 +71,8 @@ function _TokenFiatOnRampList({ ...@@ -71,8 +71,8 @@ function _TokenFiatOnRampList({
return ( return (
<Flex centered grow> <Flex centered grow>
<BaseCard.ErrorState <BaseCard.ErrorState
retryButtonLabel={t('common.button.retry')} retryButtonLabel="Retry"
title={t('fiatOnRamp.error.load')} title={t('Couldn’t load tokens to buy')}
onRetry={onRetry} onRetry={onRetry}
/> />
</Flex> </Flex>
......
...@@ -50,7 +50,7 @@ export function ConnectedDappsList({ backButton, sessions }: ConnectedDappsProps ...@@ -50,7 +50,7 @@ export function ConnectedDappsList({ backButton, sessions }: ConnectedDappsProps
</Flex> </Flex>
<Flex alignItems="center" flexBasis="70%"> <Flex alignItems="center" flexBasis="70%">
<Text color="$neutral1" numberOfLines={1} variant="body1"> <Text color="$neutral1" numberOfLines={1} variant="body1">
{t('walletConnect.dapps.manage.title')} {t('Manage connections')}
</Text> </Text>
</Flex> </Flex>
<Flex alignItems="flex-end" flexBasis="15%"> <Flex alignItems="flex-end" flexBasis="15%">
...@@ -101,10 +101,10 @@ export function ConnectedDappsList({ backButton, sessions }: ConnectedDappsProps ...@@ -101,10 +101,10 @@ export function ConnectedDappsList({ backButton, sessions }: ConnectedDappsProps
paddingTop: fullHeight / 5, paddingTop: fullHeight / 5,
}}> }}>
<Text color="$neutral1" variant="subheading1"> <Text color="$neutral1" variant="subheading1">
{t('walletConnect.dapps.manage.empty.title')} {t('No apps connected')}
</Text> </Text>
<Text color="$neutral2" textAlign="center" variant="body2"> <Text color="$neutral2" textAlign="center" variant="body2">
{t('walletConnect.dapps.empty.description')} {t('Connect to an app by scanning a code via WalletConnect')}
</Text> </Text>
</Flex> </Flex>
)} )}
......
import { getSdkError } from '@walletconnect/utils' import { getSdkError } from '@walletconnect/utils'
import React from 'react' import React from 'react'
import { Trans, useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import 'react-native-reanimated' import 'react-native-reanimated'
import { useAppDispatch } from 'src/app/hooks' import { useAppDispatch } from 'src/app/hooks'
import { DappHeaderIcon } from 'src/components/WalletConnect/DappHeaderIcon' import { DappHeaderIcon } from 'src/components/WalletConnect/DappHeaderIcon'
import { wcWeb3Wallet } from 'src/features/walletConnect/saga' import { wcWeb3Wallet } from 'src/features/walletConnect/saga'
import { WalletConnectSession, removeSession } from 'src/features/walletConnect/walletConnectSlice' import { removeSession, WalletConnectSession } from 'src/features/walletConnect/walletConnectSlice'
import { Button, Flex, Text } from 'ui/src' import { Button, Flex, Text } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -66,10 +66,8 @@ export function DappConnectedNetworkModal({ ...@@ -66,10 +66,8 @@ export function DappConnectedNetworkModal({
<Flex alignItems="center" gap="$spacing8"> <Flex alignItems="center" gap="$spacing8">
<DappHeaderIcon dapp={dapp} /> <DappHeaderIcon dapp={dapp} />
<Text textAlign="center" variant="buttonLabel2"> <Text textAlign="center" variant="buttonLabel2">
<Trans i18nKey="walletConnect.dapps.connection"> <Text variant="body1">{t('Connected to ')}</Text>
<Text variant="body1">Connected to</Text> {dapp.name || dapp.url}
{{ dappNameOrUrl: dapp.name || dapp.url }}
</Trans>
</Text> </Text>
<Text color="$accent1" numberOfLines={1} textAlign="center" variant="buttonLabel4"> <Text color="$accent1" numberOfLines={1} textAlign="center" variant="buttonLabel4">
{dapp.url} {dapp.url}
...@@ -103,10 +101,10 @@ export function DappConnectedNetworkModal({ ...@@ -103,10 +101,10 @@ export function DappConnectedNetworkModal({
</Flex> </Flex>
<Flex centered row gap="$spacing16"> <Flex centered row gap="$spacing16">
<Button fill theme="secondary" onPress={onClose}> <Button fill theme="secondary" onPress={onClose}>
{t('common.button.close')} {t('Close')}
</Button> </Button>
<Button fill theme="detrimental" onPress={onDisconnect}> <Button fill theme="detrimental" onPress={onDisconnect}>
{t('common.button.disconnect')} {t('Disconnect')}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -63,9 +63,7 @@ export function DappConnectionItem({ ...@@ -63,9 +63,7 @@ export function DappConnectionItem({
} }
} }
const menuActions = [ const menuActions = [{ title: t('Disconnect'), systemIcon: 'trash', destructive: true }]
{ title: t('common.button.disconnect'), systemIcon: 'trash', destructive: true },
]
const onPress = async (e: NativeSyntheticEvent<ContextMenuOnPressNativeEvent>): Promise<void> => { const onPress = async (e: NativeSyntheticEvent<ContextMenuOnPressNativeEvent>): Promise<void> => {
if (e.nativeEvent.index === 0) { if (e.nativeEvent.index === 0) {
......
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import React from 'react' import React from 'react'
import { Trans } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { truncateDappName } from 'src/components/WalletConnect/ScanSheet/util' import { truncateDappName } from 'src/components/WalletConnect/ScanSheet/util'
import { WalletConnectRequest } from 'src/features/walletConnect/walletConnectSlice' import { WalletConnectRequest } from 'src/features/walletConnect/walletConnectSlice'
import { Text } from 'ui/src' import { Text } from 'ui/src'
...@@ -16,6 +16,7 @@ export function HeaderText({ ...@@ -16,6 +16,7 @@ export function HeaderText({
permitAmount?: number permitAmount?: number
permitCurrency?: Currency | null permitCurrency?: Currency | null
}): JSX.Element { }): JSX.Element {
const { t } = useTranslation()
const { dapp, type: method } = request const { dapp, type: method } = request
if (permitCurrency) { if (permitCurrency) {
...@@ -26,54 +27,39 @@ export function HeaderText({ ...@@ -26,54 +27,39 @@ export function HeaderText({
})?.toExact() })?.toExact()
return readablePermitAmount ? ( return readablePermitAmount ? (
<Text textAlign="center" variant="heading3"> <Trans t={t}>
<Trans i18nKey="qrScanner.request.withAmount"> <Text textAlign="center" variant="heading3">
Allow {{ dappName: dapp.name }} to use up to Allow {dapp.name} to use up to
<Text fontWeight="bold"> {{ amount: readablePermitAmount }} </Text> <Text fontWeight="bold"> {readablePermitAmount} </Text>
{{ currencySymbol: permitCurrency?.symbol }}? {permitCurrency?.symbol}?
</Trans> </Text>
</Text> </Trans>
) : ( ) : (
<Text textAlign="center" variant="heading3"> <Trans t={t}>
<Trans i18nKey="qrScanner.request.withoutAmount"> <Text textAlign="center" variant="heading3">
Allow <Text fontWeight="bold">{{ dappName: dapp.name }}</Text> to use your Allow <Text fontWeight="bold">{dapp.name}</Text> to use your {permitCurrency?.symbol}?
{{ currencySymbol: permitCurrency?.symbol }}? </Text>
</Trans> </Trans>
</Text>
) )
} }
const getReadableMethodName = (ethMethod: EthMethod, dappNameOrUrl: string): JSX.Element => { const getReadableMethodName = (ethMethod: EthMethod): string => {
switch (ethMethod) { switch (ethMethod) {
case EthMethod.PersonalSign: case EthMethod.PersonalSign:
case EthMethod.EthSign: case EthMethod.EthSign:
case EthMethod.SignTypedData: case EthMethod.SignTypedData:
return ( return t('Signature request from')
<Trans i18nKey="qrScanner.request.method.signature">
Signature request from
<Text fontWeight="bold">{{ dappNameOrUrl }}</Text>
</Trans>
)
case EthMethod.EthSendTransaction: case EthMethod.EthSendTransaction:
return ( return t('Transaction request from')
<Trans i18nKey="qrScanner.request.method.transaction">
Transaction request from
<Text fontWeight="bold">{{ dappNameOrUrl }}</Text>
</Trans>
)
} }
return ( return t('Request from')
<Trans i18nKey="qrScanner.request.method.default">
Request from
<Text fontWeight="bold">{{ dappNameOrUrl }}</Text>
</Trans>
)
} }
return ( return (
<Text textAlign="center" variant="heading3"> <Text textAlign="center" variant="heading3">
{getReadableMethodName(method, truncateDappName(dapp.name || dapp.url))} <Text>{getReadableMethodName(method)}</Text>
<Text fontWeight="bold"> {truncateDappName(dapp.name || dapp.url)}</Text>
</Text> </Text>
) )
} }
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
import { Transaction, TransactionDescription } from 'no-yolo-signatures' import { Transaction, TransactionDescription } from 'no-yolo-signatures'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { ScrollView } from 'react-native-gesture-handler' import { ScrollView } from 'react-native-gesture-handler'
import { LinkButton } from 'src/components/buttons/LinkButton' import { LinkButton } from 'src/components/buttons/LinkButton'
import { SpendingDetails } from 'src/components/WalletConnect/RequestModal/SpendingDetails' import { SpendingDetails } from 'src/components/WalletConnect/RequestModal/SpendingDetails'
...@@ -162,29 +162,25 @@ function TransactionDetails({ ...@@ -162,29 +162,25 @@ function TransactionDetails({
) : null} ) : null}
{to ? ( {to ? (
<Flex row alignItems="center" gap="$spacing16"> <Flex row alignItems="center" gap="$spacing16">
<Trans i18nKey="walletConnect.request.details.recipient"> <Text color="$neutral2" variant="body2">
<Text color="$neutral2" variant="body2"> {t('To')}:
To: </Text>
</Text> <AddressButton address={to} chainId={chainId} />
<AddressButton address={to} chainId={chainId} />
</Trans>
</Flex> </Flex>
) : null} ) : null}
<Flex row alignItems="center" gap="$spacing16"> <Flex row alignItems="center" gap="$spacing16">
<Trans i18nKey="walletConnect.request.details.function"> <Text color="$neutral2" variant="body2">
<Text color="$neutral2" variant="body2"> {t('Function')}:
Function: </Text>
<Flex
backgroundColor={isLoading ? '$transparent' : '$surface3'}
borderRadius="$rounded12"
px="$spacing8"
py="$spacing4">
<Text color="$neutral1" loading={isLoading} variant="monospace">
{parsedData ? parsedData.name : t('Unknown')}
</Text> </Text>
<Flex </Flex>
backgroundColor={isLoading ? '$transparent' : '$surface3'}
borderRadius="$rounded12"
px="$spacing8"
py="$spacing4">
<Text color="$neutral1" loading={isLoading} variant="monospace">
{{ functionName: parsedData ? parsedData.name : t('common.text.unknown') }}
</Text>
</Flex>
</Trans>
</Flex> </Flex>
</Flex> </Flex>
) )
...@@ -220,7 +216,7 @@ function RequestDetailsContent({ request }: Props): JSX.Element { ...@@ -220,7 +216,7 @@ function RequestDetailsContent({ request }: Props): JSX.Element {
<Text variant="body2">{message}</Text> <Text variant="body2">{message}</Text>
) : ( ) : (
<Text color="$neutral2" variant="body2"> <Text color="$neutral2" variant="body2">
{t('qrScanner.request.message.unavailable')} {t('No message found.')}
</Text> </Text>
) )
} }
......
import React from 'react' import React from 'react'
import { Trans } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
...@@ -18,6 +18,7 @@ export function SpendingDetails({ ...@@ -18,6 +18,7 @@ export function SpendingDetails({
value: string value: string
chainId: ChainId chainId: ChainId
}): JSX.Element { }): JSX.Element {
const { t } = useTranslation()
const { convertFiatAmountFormatted, formatCurrencyAmount } = useLocalizationContext() const { convertFiatAmountFormatted, formatCurrencyAmount } = useLocalizationContext()
const nativeCurrencyInfo = useNativeCurrencyInfo(chainId) const nativeCurrencyInfo = useNativeCurrencyInfo(chainId)
...@@ -30,26 +31,21 @@ export function SpendingDetails({ ...@@ -30,26 +31,21 @@ export function SpendingDetails({
: null : null
const usdValue = useUSDValue(chainId, value) const usdValue = useUSDValue(chainId, value)
const tokenAmountWithSymbol =
formatCurrencyAmount({ value: nativeCurrencyAmount, type: NumberType.TokenTx }) +
' ' +
getSymbolDisplayText(nativeCurrencyInfo?.currency.symbol)
const fiatAmount = convertFiatAmountFormatted(usdValue, NumberType.FiatTokenPrice)
return ( return (
<Flex row alignItems="center" gap="$spacing16"> <Flex row alignItems="center" gap="$spacing16">
<Trans i18nKey="walletConnect.request.details.sending"> <Text color="$neutral2" variant="body2">
<Text color="$neutral2" variant="body2"> {t('Sending')}:
Sending: </Text>
<Flex row alignItems="center" gap="$spacing4">
<CurrencyLogo currencyInfo={nativeCurrencyInfo} size={iconSizes.icon16} />
<Text variant="subheading2">
{formatCurrencyAmount({ value: nativeCurrencyAmount, type: NumberType.TokenTx })}{' '}
{getSymbolDisplayText(nativeCurrencyInfo?.currency.symbol)}
</Text>
<Text color="$neutral2" loading={!usdValue} variant="subheading2">
({convertFiatAmountFormatted(usdValue, NumberType.FiatTokenPrice)})
</Text> </Text>
<Flex row alignItems="center" gap="$spacing4"> </Flex>
<CurrencyLogo currencyInfo={nativeCurrencyInfo} size={iconSizes.icon16} />
<Text variant="subheading2">{{ tokenAmountWithSymbol }}</Text>
<Text color="$neutral2" loading={!usdValue} variant="subheading2">
({{ fiatAmount }})
</Text>
</Flex>
</Trans>
</Flex> </Flex>
) )
} }
...@@ -314,7 +314,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -314,7 +314,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
) : ( ) : (
<Flex row alignItems="center" justifyContent="space-between"> <Flex row alignItems="center" justifyContent="space-between">
<Text color="$neutral1" variant="subheading2"> <Text color="$neutral1" variant="subheading2">
{t('walletConnect.request.label.network')} {t('Network')}
</Text> </Text>
<NetworkPill <NetworkPill
showIcon showIcon
...@@ -333,8 +333,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -333,8 +333,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
<AccountDetails address={request.account} /> <AccountDetails address={request.account} />
{!hasSufficientFunds && ( {!hasSufficientFunds && (
<Text color="$DEP_accentWarning" pt="$spacing8" variant="body2"> <Text color="$DEP_accentWarning" pt="$spacing8" variant="body2">
{t('walletConnect.request.error.insufficientFunds', { {t('You don’t have enough {{symbol}} to complete this transaction.', {
currencySymbol: nativeCurrency?.symbol, symbol: nativeCurrency?.symbol,
})} })}
</Text> </Text>
)} )}
...@@ -351,7 +351,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -351,7 +351,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
/> />
} }
textColor="$DEP_accentWarning" textColor="$DEP_accentWarning"
title={t('walletConnect.request.error.network')} title={t('Internet or network connection error')}
/> />
) : ( ) : (
<WarningSection <WarningSection
...@@ -367,7 +367,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -367,7 +367,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
testID={ElementName.Cancel} testID={ElementName.Cancel}
theme="tertiary" theme="tertiary"
onPress={onReject}> onPress={onReject}>
{t('common.button.cancel')} {t('Cancel')}
</Button> </Button>
<Button <Button
fill fill
...@@ -381,9 +381,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -381,9 +381,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
await onConfirm() await onConfirm()
} }
}}> }}>
{isTransactionRequest(request) {isTransactionRequest(request) ? t('Accept') : t('Sign')}
? t('common.button.accept')
: t('walletConnect.request.button.sign')}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
...@@ -421,9 +419,9 @@ function WarningSection({ ...@@ -421,9 +419,9 @@ function WarningSection({
width={iconSizes.icon16} width={iconSizes.icon16}
/> />
<Text color="$neutral2" fontStyle="italic" variant="body3"> <Text color="$neutral2" fontStyle="italic" variant="body3">
{isTransactionRequest(request) {t('Be careful: this {{ requestType }} may transfer assets', {
? t('walletConnect.request.warning.general.transaction') requestType: isTransactionRequest(request) ? 'transaction' : 'message',
: t('walletConnect.request.warning.general.message')} })}
</Text> </Text>
</Flex> </Flex>
) )
......
...@@ -68,7 +68,7 @@ const SitePermissions = (): JSX.Element => { ...@@ -68,7 +68,7 @@ const SitePermissions = (): JSX.Element => {
allowFontScaling={false} allowFontScaling={false}
color="$neutral2" color="$neutral2"
variant="subheading2"> variant="subheading2">
{t('walletConnect.permissions.title')} {t('App permissions')}
</Text> </Text>
<Flex centered row gap="$spacing8"> <Flex centered row gap="$spacing8">
<Icons.Check color="$statusSuccess" size={iconSizes.icon16} /> <Icons.Check color="$statusSuccess" size={iconSizes.icon16} />
...@@ -78,7 +78,7 @@ const SitePermissions = (): JSX.Element => { ...@@ -78,7 +78,7 @@ const SitePermissions = (): JSX.Element => {
color="$neutral1" color="$neutral1"
flexGrow={1} flexGrow={1}
variant={normalInfoTextSize}> variant={normalInfoTextSize}>
{t('walletConnect.permissions.option.viewWalletAddress')} {t('View your wallet address')}
</Text> </Text>
</Flex> </Flex>
<Flex centered row gap="$spacing8"> <Flex centered row gap="$spacing8">
...@@ -89,7 +89,7 @@ const SitePermissions = (): JSX.Element => { ...@@ -89,7 +89,7 @@ const SitePermissions = (): JSX.Element => {
color="$neutral1" color="$neutral1"
flexGrow={1} flexGrow={1}
variant={normalInfoTextSize}> variant={normalInfoTextSize}>
{t('walletConnect.permissions.option.viewTokenBalances')} {t('View your token balances')}
</Text> </Text>
</Flex> </Flex>
<Flex centered row gap="$spacing8"> <Flex centered row gap="$spacing8">
...@@ -100,7 +100,7 @@ const SitePermissions = (): JSX.Element => { ...@@ -100,7 +100,7 @@ const SitePermissions = (): JSX.Element => {
color="$neutral1" color="$neutral1"
flexGrow={1} flexGrow={1}
variant={normalInfoTextSize}> variant={normalInfoTextSize}>
{t('walletConnect.permissions.option.transferAssets')} {t('Transfer your assets without consent')}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
...@@ -124,7 +124,7 @@ const NetworksRow = ({ chains }: { chains: ChainId[] }): JSX.Element => { ...@@ -124,7 +124,7 @@ const NetworksRow = ({ chains }: { chains: ChainId[] }): JSX.Element => {
allowFontScaling={false} allowFontScaling={false}
color="$neutral2" color="$neutral2"
variant="subheading2"> variant="subheading2">
{t('walletConnect.permissions.networks')} {t('Networks')}
</Text> </Text>
<NetworkLogos chains={chains} /> <NetworkLogos chains={chains} />
</Flex> </Flex>
...@@ -258,7 +258,7 @@ export const PendingConnectionModal = ({ pendingSession, onClose }: Props): JSX. ...@@ -258,7 +258,7 @@ export const PendingConnectionModal = ({ pendingSession, onClose }: Props): JSX.
px="$spacing24" px="$spacing24"
textAlign="center" textAlign="center"
variant="heading3"> variant="heading3">
{t('walletConnect.pending.title', { {t('{{ dappName }} wants to connect to your wallet', {
dappName: truncateDappName(dappName), dappName: truncateDappName(dappName),
})}{' '} })}{' '}
</Text> </Text>
...@@ -287,13 +287,13 @@ export const PendingConnectionModal = ({ pendingSession, onClose }: Props): JSX. ...@@ -287,13 +287,13 @@ export const PendingConnectionModal = ({ pendingSession, onClose }: Props): JSX.
testID="cancel-pending-connection" testID="cancel-pending-connection"
theme="secondary" theme="secondary"
onPress={(): Promise<void> => onPressSettleConnection(false)}> onPress={(): Promise<void> => onPressSettleConnection(false)}>
{t('common.button.cancel')} {t('Cancel')}
</Button> </Button>
<Button <Button
fill fill
testID="connect-pending-connection" testID="connect-pending-connection"
onPress={(): Promise<void> => onPressSettleConnection(true)}> onPress={(): Promise<void> => onPressSettleConnection(true)}>
{t('walletConnect.pending.button.connect')} {t('Connect')}
</Button> </Button>
</Flex> </Flex>
</AnimatedFlex> </AnimatedFlex>
......
...@@ -37,7 +37,7 @@ export const PendingConnectionSwitchAccountModal = ({ ...@@ -37,7 +37,7 @@ export const PendingConnectionSwitchAccountModal = ({
<ActionSheetModal <ActionSheetModal
header={ header={
<Flex centered gap="$spacing4" py="$spacing16"> <Flex centered gap="$spacing4" py="$spacing16">
<Text variant="buttonLabel2">{t('walletConnect.pending.switchAccount')}</Text> <Text variant="buttonLabel2">{t('Switch Account')}</Text>
</Flex> </Flex>
} }
isVisible={true} isVisible={true}
......
...@@ -63,7 +63,7 @@ export const PendingConnectionSwitchNetworkModal = ({ ...@@ -63,7 +63,7 @@ export const PendingConnectionSwitchNetworkModal = ({
<ActionSheetModal <ActionSheetModal
header={ header={
<Flex centered gap="$spacing4" py="$spacing16"> <Flex centered gap="$spacing4" py="$spacing16">
<Text variant="buttonLabel2">{t('walletConnect.pending.switchNetwork')}</Text> <Text variant="buttonLabel2">{t('Switch Network')}</Text>
</Flex> </Flex>
} }
isVisible={true} isVisible={true}
......
...@@ -76,12 +76,14 @@ export function WalletConnectModal({ ...@@ -76,12 +76,14 @@ export function WalletConnectModal({
if (!supportedURI) { if (!supportedURI) {
setShouldFreezeCamera(true) setShouldFreezeCamera(true)
Alert.alert( Alert.alert(
t('walletConnect.error.unsupported.title'), t('Invalid QR Code'),
// TODO(EXT-495): Add Scantastic product name here when ready // TODO(EXT-495): Add Scantastic product name here when ready
t('walletConnect.error.unsupported.message'), t(
'Make sure that you’re scanning a valid WalletConnect or Ethereum address QR code before trying again.'
),
[ [
{ {
text: t('common.button.tryAgain'), text: t('Try again'),
onPress: (): void => { onPress: (): void => {
setShouldFreezeCamera(false) setShouldFreezeCamera(false)
}, },
...@@ -101,11 +103,13 @@ export function WalletConnectModal({ ...@@ -101,11 +103,13 @@ export function WalletConnectModal({
if (supportedURI.type === URIType.WalletConnectURL) { if (supportedURI.type === URIType.WalletConnectURL) {
setShouldFreezeCamera(true) setShouldFreezeCamera(true)
Alert.alert( Alert.alert(
t('walletConnect.error.unsupportedV1.title'), t('Invalid QR Code'),
t('walletConnect.error.unsupportedV1.message'), t(
'WalletConnect v1 is no longer supported. The application you’re trying to connect to needs to upgrade to WalletConnect v2.'
),
[ [
{ {
text: t('common.button.ok'), text: t('OK'),
onPress: (): void => { onPress: (): void => {
setShouldFreezeCamera(false) setShouldFreezeCamera(false)
}, },
...@@ -122,11 +126,11 @@ export function WalletConnectModal({ ...@@ -122,11 +126,11 @@ export function WalletConnectModal({
} catch (error) { } catch (error) {
logger.error(error, { tags: { file: 'WalletConnectModal', function: 'onScanCode' } }) logger.error(error, { tags: { file: 'WalletConnectModal', function: 'onScanCode' } })
Alert.alert( Alert.alert(
t('walletConnect.error.general.title'), t('WalletConnect Error'),
t('walletConnect.error.general.message'), t('There was an issue with WalletConnect. Please try again'),
[ [
{ {
text: t('common.button.ok'), text: t('OK'),
onPress: (): void => { onPress: (): void => {
setShouldFreezeCamera(false) setShouldFreezeCamera(false)
}, },
...@@ -164,18 +168,14 @@ export function WalletConnectModal({ ...@@ -164,18 +168,14 @@ export function WalletConnectModal({
const isAllowed = isAllowedUwULinkRequest(parsedUwulinkRequest) const isAllowed = isAllowedUwULinkRequest(parsedUwulinkRequest)
if (!isAllowed) { if (!isAllowed) {
Alert.alert( Alert.alert(t('UwU Link error'), t('This QR code is not supported.'), [
t('walletConnect.error.uwu.title'), {
t('walletConnect.error.uwu.unsupported'), text: t('OK'),
[ onPress: (): void => {
{ setShouldFreezeCamera(false)
text: t('common.button.ok'),
onPress: (): void => {
setShouldFreezeCamera(false)
},
}, },
] },
) ])
return return
} }
...@@ -201,7 +201,7 @@ export function WalletConnectModal({ ...@@ -201,7 +201,7 @@ export function WalletConnectModal({
onClose() onClose()
} catch (_) { } catch (_) {
setShouldFreezeCamera(false) setShouldFreezeCamera(false)
Alert.alert(t('walletConnect.error.uwu.title'), t('walletConnect.error.uwu.scan')) Alert.alert(t('UwU Link error'), t('There was an issue scanning this QR code.'))
} }
} }
...@@ -312,8 +312,8 @@ export function WalletConnectModal({ ...@@ -312,8 +312,8 @@ export function WalletConnectModal({
)} )}
<Text color="$neutral1" variant="buttonLabel2"> <Text color="$neutral1" variant="buttonLabel2">
{currentScreenState === ScannerModalState.ScanQr {currentScreenState === ScannerModalState.ScanQr
? t('qrScanner.recipient.action.show') ? t('Show my QR code')
: t('qrScanner.recipient.action.scan')} : t('Scan a QR code')}
</Text> </Text>
</Flex> </Flex>
</TouchableArea> </TouchableArea>
......
...@@ -104,8 +104,10 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element { ...@@ -104,8 +104,10 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element {
if (!isRequestFromSignerAccount) { if (!isRequestFromSignerAccount) {
return ( return (
<WarningModal <WarningModal
caption={t('walletConnect.request.warning.message')} caption={t(
closeText={t('common.button.dismiss')} 'In order to sign messages or transactions, you’ll need to import the wallet’s recovery phrase.'
)}
closeText={t('Dismiss')}
icon={ icon={
<EyeIcon <EyeIcon
color={colors.neutral2.get()} color={colors.neutral2.get()}
...@@ -116,7 +118,7 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element { ...@@ -116,7 +118,7 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element {
} }
modalName={ModalName.WCViewOnlyWarning} modalName={ModalName.WCViewOnlyWarning}
severity={WarningSeverity.None} severity={WarningSeverity.None}
title={t('walletConnect.request.warning.title')} title={t('This wallet is in view only mode')}
onCancel={onClose} onCancel={onClose}
onClose={onClose}> onClose={onClose}>
<Flex <Flex
......
...@@ -57,7 +57,7 @@ function PortfolioValue({ ...@@ -57,7 +57,7 @@ function PortfolioValue({
return ( return (
<Text color="$neutral2" loading={isLoading} variant="subheading2"> <Text color="$neutral2" loading={isLoading} variant="subheading2">
{portfolioValue === undefined {portfolioValue === undefined
? t('common.text.notAvailable') ? t('N/A')
: convertFiatAmountFormatted(portfolioValue, NumberType.PortfolioBalance)} : convertFiatAmountFormatted(portfolioValue, NumberType.PortfolioBalance)}
</Text> </Text>
) )
...@@ -100,9 +100,9 @@ export function AccountCardItem({ ...@@ -100,9 +100,9 @@ export function AccountCardItem({
const menuActions = useMemo(() => { const menuActions = useMemo(() => {
return [ return [
{ title: t('account.wallet.action.copy'), systemIcon: 'doc.on.doc' }, { title: t('Copy wallet address'), systemIcon: 'doc.on.doc' },
{ title: t('account.wallet.action.settings'), systemIcon: 'gearshape' }, { title: t('Wallet settings'), systemIcon: 'gearshape' },
{ title: t('account.wallet.button.remove'), systemIcon: 'trash', destructive: true }, { title: t('Remove wallet'), systemIcon: 'trash', destructive: true },
] ]
}, [t]) }, [t])
......
import React from 'react' import React from 'react'
import { AccountHeader } from 'src/components/accounts/AccountHeader' import { AccountHeader } from 'src/components/accounts/AccountHeader'
import { render } from 'src/test/test-utils' import { render } from 'src/test/test-utils'
import { ACCOUNT } from 'wallet/src/test/fixtures' import { mockWalletPreloadedState } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
describe(AccountHeader, () => { describe(AccountHeader, () => {
it('renders without error', () => { it('renders without error', () => {
const tree = render(<AccountHeader />, { preloadedState: mockWalletPreloadedState(ACCOUNT) }) const tree = render(<AccountHeader />, { preloadedState: mockWalletPreloadedState })
expect(tree.toJSON()).toMatchSnapshot() expect(tree.toJSON()).toMatchSnapshot()
}) })
......
...@@ -4,23 +4,25 @@ import { AccountList } from 'src/components/accounts/AccountList' ...@@ -4,23 +4,25 @@ import { AccountList } from 'src/components/accounts/AccountList'
import { render, screen } from 'src/test/test-utils' import { render, screen } from 'src/test/test-utils'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks' import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks'
import { ACCOUNT, ON_PRESS_EVENT_PAYLOAD, amounts } from 'wallet/src/test/fixtures' import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/eventFixtures'
import { mockLocalizedFormatter } from 'wallet/src/test/mocks' import { account } from 'wallet/src/test/fixtures'
import { Amounts, Portfolios } from 'wallet/src/test/gqlFixtures'
import { mockLocalizedFormatter } from 'wallet/src/test/utils'
const resolvers: Resolvers = { const resolvers: Resolvers = {
Portfolio: { Portfolio: {
tokensTotalDenominatedValue: () => amounts.md(), tokensTotalDenominatedValue: () => Amounts.md,
}, },
} }
describe(AccountList, () => { describe(AccountList, () => {
it('renders without error', async () => { it('renders without error', async () => {
const tree = render(<AccountList accounts={[ACCOUNT]} onPress={jest.fn()} />, { resolvers }) const tree = render(<AccountList accounts={[account]} onPress={jest.fn()} />, { resolvers })
expect( expect(
await screen.findByText( await screen.findByText(
mockLocalizedFormatter.formatNumberOrString({ mockLocalizedFormatter.formatNumberOrString({
value: amounts.md().value, value: Portfolios[0].tokensTotalDenominatedValue?.value,
type: NumberType.PortfolioBalance, type: NumberType.PortfolioBalance,
currencyCode: 'usd', currencyCode: 'usd',
}) })
...@@ -31,21 +33,21 @@ describe(AccountList, () => { ...@@ -31,21 +33,21 @@ describe(AccountList, () => {
it('handles press on card items', async () => { it('handles press on card items', async () => {
const onPressSpy = jest.fn() const onPressSpy = jest.fn()
render(<AccountList accounts={[ACCOUNT]} onPress={onPressSpy} />, { render(<AccountList accounts={[account]} onPress={onPressSpy} />, {
resolvers, resolvers,
}) })
// go to success state // go to success state
expect( expect(
await screen.findByText( await screen.findByText(
mockLocalizedFormatter.formatNumberOrString({ mockLocalizedFormatter.formatNumberOrString({
value: amounts.md().value, value: Portfolios[0].tokensTotalDenominatedValue?.value,
type: NumberType.PortfolioBalance, type: NumberType.PortfolioBalance,
currencyCode: 'usd', currencyCode: 'usd',
}) })
) )
).toBeDefined() ).toBeDefined()
fireEvent.press(screen.getByTestId(`account_item/${ACCOUNT.address}`), ON_PRESS_EVENT_PAYLOAD) fireEvent.press(screen.getByTestId(`account_item/${account.address}`), ON_PRESS_EVENT_PAYLOAD)
expect(onPressSpy).toHaveBeenCalledTimes(1) expect(onPressSpy).toHaveBeenCalledTimes(1)
}) })
......
...@@ -31,7 +31,7 @@ const ViewOnlyHeader = (): JSX.Element => { ...@@ -31,7 +31,7 @@ const ViewOnlyHeader = (): JSX.Element => {
return ( return (
<Flex fill px="$spacing24" py="$spacing8"> <Flex fill px="$spacing24" py="$spacing8">
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
{t('account.wallet.header.viewOnly')} {t('View only wallets')}
</Text> </Text>
</Flex> </Flex>
) )
...@@ -42,7 +42,7 @@ const SignerHeader = (): JSX.Element => { ...@@ -42,7 +42,7 @@ const SignerHeader = (): JSX.Element => {
return ( return (
<Flex fill px="$spacing24" py="$spacing8"> <Flex fill px="$spacing24" py="$spacing8">
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
{t('account.wallet.header.other')} {t('Your other wallets')}
</Text> </Text>
</Flex> </Flex>
) )
......
...@@ -40,7 +40,7 @@ export function OfflineBanner(): JSX.Element | null { ...@@ -40,7 +40,7 @@ export function OfflineBanner(): JSX.Element | null {
width={iconSizes.icon24} width={iconSizes.icon24}
/> />
} }
text={t('home.banner.offline')} text={t('You are in offline mode')}
translateY={BANNER_HEIGHT - EXTRA_MARGIN} translateY={BANNER_HEIGHT - EXTRA_MARGIN}
/> />
) : null ) : null
......
import React from 'react' import React from 'react'
import { BackButton } from 'src/components/buttons/BackButton' import { BackButton } from 'src/components/buttons/BackButton'
import { fireEvent, render, screen } from 'src/test/test-utils' import { fireEvent, render, screen } from 'src/test/test-utils'
import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures' import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/eventFixtures'
const mockedGoBack = jest.fn() const mockedGoBack = jest.fn()
jest.mock('@react-navigation/native', () => { jest.mock('@react-navigation/native', () => {
......
...@@ -42,7 +42,7 @@ export function CopyTextButton({ copyText }: Props): JSX.Element { ...@@ -42,7 +42,7 @@ export function CopyTextButton({ copyText }: Props): JSX.Element {
return ( return (
<Button icon={isCopied ? copiedIcon : copyIcon} theme="tertiary" onPress={onPress}> <Button icon={isCopied ? copiedIcon : copyIcon} theme="tertiary" onPress={onPress}>
{isCopied ? t('common.button.copied') : t('common.button.copy')} {isCopied ? t`Copied` : t`Copy`}
</Button> </Button>
) )
} }
import React, { ComponentProps, ReactNode, useCallback, useContext, useMemo } from 'react' import React, { ComponentProps, ReactNode, useCallback, useContext, useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans } from 'react-i18next'
import { Gesture, GestureDetector } from 'react-native-gesture-handler' import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import { runOnJS } from 'react-native-reanimated' import { runOnJS } from 'react-native-reanimated'
import { OnboardingStackBaseParams, useOnboardingStackNavigation } from 'src/app/navigation/types' import { OnboardingStackBaseParams, useOnboardingStackNavigation } from 'src/app/navigation/types'
...@@ -7,7 +7,7 @@ import { CloseButton } from 'src/components/buttons/CloseButton' ...@@ -7,7 +7,7 @@ import { CloseButton } from 'src/components/buttons/CloseButton'
import { CarouselContext } from 'src/components/carousel/Carousel' import { CarouselContext } from 'src/components/carousel/Carousel'
import { OnboardingScreens } from 'src/screens/Screens' import { OnboardingScreens } from 'src/screens/Screens'
import { Flex, Text, useDeviceDimensions } from 'ui/src' import { Flex, Text, useDeviceDimensions } from 'ui/src'
import { getCloudProviderName } from 'wallet/src/utils/platform' import { isAndroid } from 'wallet/src/utils/platform'
function Page({ function Page({
text, text,
...@@ -16,7 +16,6 @@ function Page({ ...@@ -16,7 +16,6 @@ function Page({
text: ReactNode text: ReactNode
params: OnboardingStackBaseParams params: OnboardingStackBaseParams
}): JSX.Element { }): JSX.Element {
const { t } = useTranslation()
const { fullWidth } = useDeviceDimensions() const { fullWidth } = useDeviceDimensions()
const { goToPrev, goToNext } = useContext(CarouselContext) const { goToPrev, goToNext } = useContext(CarouselContext)
const navigation = useOnboardingStackNavigation() const navigation = useOnboardingStackNavigation()
...@@ -56,7 +55,7 @@ function Page({ ...@@ -56,7 +55,7 @@ function Page({
px="$spacing24" px="$spacing24"
width={fullWidth}> width={fullWidth}>
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
{t('onboarding.tooltip.recoveryPhrase.trigger')} <Trans>What’s a recovery phrase?</Trans>
</Text> </Text>
<GestureDetector gesture={dismissGesture}> <GestureDetector gesture={dismissGesture}>
<CloseButton color="$neutral2" onPress={(): void => undefined} /> <CloseButton color="$neutral2" onPress={(): void => undefined} />
...@@ -72,14 +71,13 @@ function Page({ ...@@ -72,14 +71,13 @@ function Page({
) )
} }
const cloudProviderName = getCloudProviderName()
export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): JSX.Element[] => [ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): JSX.Element[] => [
<Page <Page
params={params} params={params}
text={ text={
<CustomHeadingText> <CustomHeadingText>
<Trans i18nKey="account.seedPhrase.education.part1"> <Trans>
A recovery phrase (or seed phrase) is a A recovery phrase (or seed phrase) is a{' '}
<CustomHeadingText color="$accent1">set of words</CustomHeadingText> required to access <CustomHeadingText color="$accent1">set of words</CustomHeadingText> required to access
your wallet, <CustomHeadingText color="$accent1">like a password.</CustomHeadingText> your wallet, <CustomHeadingText color="$accent1">like a password.</CustomHeadingText>
</Trans> </Trans>
...@@ -90,9 +88,9 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J ...@@ -90,9 +88,9 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J
params={params} params={params}
text={ text={
<CustomHeadingText> <CustomHeadingText>
<Trans i18nKey="account.seedPhrase.education.part2"> <Trans>
You can <CustomHeadingText color="$accent1">enter</CustomHeadingText> your recovery phrase You can <CustomHeadingText color="$accent1">enter</CustomHeadingText> your recovery phrase
on a new device on a new device{' '}
<CustomHeadingText color="$accent1">to restore your wallet</CustomHeadingText> and its <CustomHeadingText color="$accent1">to restore your wallet</CustomHeadingText> and its
contents. contents.
</Trans> </Trans>
...@@ -103,9 +101,9 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J ...@@ -103,9 +101,9 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J
params={params} params={params}
text={ text={
<CustomHeadingText> <CustomHeadingText>
<Trans i18nKey="account.seedPhrase.education.part3"> <Trans>
But, if you But, if you{' '}
<CustomHeadingText color="$accent1">lose your recovery phrase</CustomHeadingText>, you’ll <CustomHeadingText color="$accent1">lose your recovery phrase</CustomHeadingText>, you’ll{' '}
<CustomHeadingText color="$accent1">lose access</CustomHeadingText> to your wallet. <CustomHeadingText color="$accent1">lose access</CustomHeadingText> to your wallet.
</Trans> </Trans>
</CustomHeadingText> </CustomHeadingText>
...@@ -115,13 +113,19 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J ...@@ -115,13 +113,19 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J
params={params} params={params}
text={ text={
<CustomHeadingText> <CustomHeadingText>
<Trans i18nKey="account.seedPhrase.education.part4"> {isAndroid ? (
Instead of memorizing your recovery phrase, you can <Trans>
<CustomHeadingText color="$accent1"> Instead of memorizing your recovery phrase, you can{' '}
back it up to {{ cloudProviderName }} <CustomHeadingText color="$accent1">back it up to Google Drive</CustomHeadingText> and
</CustomHeadingText> protect it with a password.
and protect it with a password. </Trans>
</Trans> ) : (
<Trans>
Instead of memorizing your recovery phrase, you can{' '}
<CustomHeadingText color="$accent1">back it up to iCloud</CustomHeadingText> and protect
it with a password.
</Trans>
)}
</CustomHeadingText> </CustomHeadingText>
} }
/>, />,
...@@ -129,8 +133,8 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J ...@@ -129,8 +133,8 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J
params={params} params={params}
text={ text={
<CustomHeadingText> <CustomHeadingText>
<Trans i18nKey="account.seedPhrase.education.part5"> <Trans>
You can also manually back up your recovery phrase by You can also manually back up your recovery phrase by{' '}
<CustomHeadingText color="$accent1">writing it down</CustomHeadingText> and storing it in <CustomHeadingText color="$accent1">writing it down</CustomHeadingText> and storing it in
a safe place. a safe place.
</Trans> </Trans>
...@@ -141,8 +145,8 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J ...@@ -141,8 +145,8 @@ export const SeedPhraseEducationContent = (params: OnboardingStackBaseParams): J
params={params} params={params}
text={ text={
<CustomHeadingText> <CustomHeadingText>
<Trans i18nKey="account.seedPhrase.education.part6"> <Trans>
We recommend using We recommend using{' '}
<CustomHeadingText color="$accent1">both types of backups</CustomHeadingText>, because if <CustomHeadingText color="$accent1">both types of backups</CustomHeadingText>, because if
you lose your recovery phrase, you won’t be able to restore your wallet. you lose your recovery phrase, you won’t be able to restore your wallet.
</Trans> </Trans>
......
...@@ -142,8 +142,8 @@ export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element ...@@ -142,8 +142,8 @@ export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element
return ( return (
<Flex height="100%" pb="$spacing60"> <Flex height="100%" pb="$spacing60">
<BaseCard.ErrorState <BaseCard.ErrorState
retryButtonLabel={t('common.button.retry')} retryButtonLabel={t('Retry')}
title={t('explore.tokens.error')} title={t('Couldn’t load tokens')}
onRetry={onRetry} onRetry={onRetry}
/> />
</Flex> </Flex>
...@@ -188,7 +188,7 @@ export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element ...@@ -188,7 +188,7 @@ export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element
mt="$spacing16" mt="$spacing16"
pl="$spacing4"> pl="$spacing4">
<Text color="$neutral2" flexShrink={0} paddingEnd="$spacing8" variant="subheading2"> <Text color="$neutral2" flexShrink={0} paddingEnd="$spacing8" variant="subheading2">
{t('explore.tokens.top.title')} {t('Top tokens')}
</Text> </Text>
<Flex flexShrink={1}> <Flex flexShrink={1}>
<SortButton orderBy={orderBy} /> <SortButton orderBy={orderBy} />
......
...@@ -39,7 +39,7 @@ export function FavoriteHeaderRow({ ...@@ -39,7 +39,7 @@ export function FavoriteHeaderRow({
) : ( ) : (
<TouchableArea hitSlop={16} onPress={onPress}> <TouchableArea hitSlop={16} onPress={onPress}>
<Text color="$accent1" variant="buttonLabel3"> <Text color="$accent1" variant="buttonLabel3">
{t('common.button.done')} {t('Done')}
</Text> </Text>
</TouchableArea> </TouchableArea>
)} )}
......
...@@ -72,9 +72,9 @@ export function FavoriteTokensGrid({ ...@@ -72,9 +72,9 @@ export function FavoriteTokensGrid({
return ( return (
<AnimatedFlex entering={FadeIn} style={animatedStyle}> <AnimatedFlex entering={FadeIn} style={animatedStyle}>
<FavoriteHeaderRow <FavoriteHeaderRow
editingTitle={t('explore.tokens.favorite.title.edit')} editingTitle={t('Edit favorite tokens')}
isEditing={isEditing} isEditing={isEditing}
title={t('explore.tokens.favorite.title.default')} title={t('Favorite tokens')}
onPress={(): void => setIsEditing(!isEditing)} onPress={(): void => setIsEditing(!isEditing)}
/> />
{showLoading ? ( {showLoading ? (
......
...@@ -52,8 +52,8 @@ function FavoriteWalletCard({ ...@@ -52,8 +52,8 @@ function FavoriteWalletCard({
/// Options for long press context menu /// Options for long press context menu
const menuActions = useMemo(() => { const menuActions = useMemo(() => {
return [ return [
{ title: t('explore.wallets.favorite.action.remove'), systemIcon: 'heart.fill' }, { title: t('Remove favorite'), systemIcon: 'heart.fill' },
{ title: t('explore.wallets.favorite.action.edit'), systemIcon: 'square.and.pencil' }, { title: t('Edit favorites'), systemIcon: 'square.and.pencil' },
] ]
}, [t]) }, [t])
......
...@@ -71,9 +71,9 @@ export function FavoriteWalletsGrid({ ...@@ -71,9 +71,9 @@ export function FavoriteWalletsGrid({
return ( return (
<AnimatedFlex entering={FadeIn} style={animatedStyle}> <AnimatedFlex entering={FadeIn} style={animatedStyle}>
<FavoriteHeaderRow <FavoriteHeaderRow
editingTitle={t('explore.wallets.favorite.title.edit')} editingTitle={t('Edit favorite wallets')}
isEditing={isEditing} isEditing={isEditing}
title={t('explore.wallets.favorite.title.default')} title={t('Favorite wallets')}
onPress={(): void => setIsEditing(!isEditing)} onPress={(): void => setIsEditing(!isEditing)}
/> />
{showLoading ? ( {showLoading ? (
......
...@@ -74,12 +74,12 @@ export const TokenItem = memo(function _TokenItem({ ...@@ -74,12 +74,12 @@ export const TokenItem = memo(function _TokenItem({
const getMetadataSubtitle = (): string | undefined => { const getMetadataSubtitle = (): string | undefined => {
switch (metadataDisplayType) { switch (metadataDisplayType) {
case TokenMetadataDisplayType.MarketCap: case TokenMetadataDisplayType.MarketCap:
return t('explore.tokens.metadata.marketCap', { number: marketCapFormatted }) return t('{{num}} MCap', { num: marketCapFormatted })
case TokenMetadataDisplayType.Volume: case TokenMetadataDisplayType.Volume:
return t('explore.tokens.metadata.volume', { number: volume24hFormatted }) return t('{{num}} Vol', { num: volume24hFormatted })
case TokenMetadataDisplayType.TVL: case TokenMetadataDisplayType.TVL:
return t('explore.tokens.metadata.totalValueLocked', { return t('{{num}} TVL', {
number: totalValueLockedFormatted, num: totalValueLockedFormatted,
}) })
case TokenMetadataDisplayType.Symbol: case TokenMetadataDisplayType.Symbol:
return symbol return symbol
......
...@@ -8,9 +8,9 @@ import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks' ...@@ -8,9 +8,9 @@ import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks'
import { FavoritesState } from 'wallet/src/features/favorites/slice' import { FavoritesState } from 'wallet/src/features/favorites/slice'
import { CurrencyField } from 'wallet/src/features/transactions/transactionState/types' import { CurrencyField } from 'wallet/src/features/transactions/transactionState/types'
import { SectionName } from 'wallet/src/telemetry/constants' import { SectionName } from 'wallet/src/telemetry/constants'
import { SAMPLE_SEED_ADDRESS_1 } from 'wallet/src/test/fixtures/constants' import { DaiAsset } from 'wallet/src/test/gqlFixtures'
const tokenId = SAMPLE_SEED_ADDRESS_1 const tokenId = DaiAsset.address?.toLowerCase() ?? ''
const currencyId = `1-${tokenId}` const currencyId = `1-${tokenId}`
const resolvers: Resolvers = { const resolvers: Resolvers = {
......
...@@ -107,31 +107,29 @@ export function useExploreTokenContextMenu({ ...@@ -107,31 +107,29 @@ export function useExploreTokenContextMenu({
const menuActions = useMemo( const menuActions = useMemo(
() => [ () => [
{ {
title: isFavorited title: isFavorited ? t('Remove favorite') : t('Favorite token'),
? t('explore.tokens.favorite.action.remove')
: t('explore.tokens.favorite.action.add'),
systemIcon: isFavorited ? 'heart.fill' : 'heart', systemIcon: isFavorited ? 'heart.fill' : 'heart',
onPress: onPressToggleFavorite, onPress: onPressToggleFavorite,
}, },
...(onEditFavorites ...(onEditFavorites
? [ ? [
{ {
title: t('explore.tokens.favorite.action.edit'), title: t('Edit favorites'),
systemIcon: 'square.and.pencil', systemIcon: 'square.and.pencil',
onPress: onEditFavorites, onPress: onEditFavorites,
}, },
] ]
: []), : []),
{ title: t('common.button.swap'), systemIcon: 'arrow.2.squarepath', onPress: onPressSwap }, { title: t('Swap'), systemIcon: 'arrow.2.squarepath', onPress: onPressSwap },
{ {
title: t('common.button.receive'), title: t('Receive'),
systemIcon: 'qrcode', systemIcon: 'qrcode',
onPress: onPressReceive, onPress: onPressReceive,
}, },
...(!onEditFavorites ...(!onEditFavorites
? [ ? [
{ {
title: t('common.button.share'), title: t('Share'),
systemIcon: 'square.and.arrow.up', systemIcon: 'square.and.arrow.up',
onPress: onPressShare, onPress: onPressShare,
}, },
......
...@@ -73,13 +73,10 @@ export function SearchEmptySection(): JSX.Element { ...@@ -73,13 +73,10 @@ export function SearchEmptySection(): JSX.Element {
gap="$spacing16" gap="$spacing16"
justifyContent="space-between" justifyContent="space-between"
mb="$spacing4"> mb="$spacing4">
<SectionHeaderText <SectionHeaderText icon={<RecentIcon />} title={t('Recent searches')} />
icon={<RecentIcon />}
title={t('explore.search.section.recent')}
/>
<TouchableArea onPress={onPressClearSearchHistory}> <TouchableArea onPress={onPressClearSearchHistory}>
<Text color="$accent1" variant="buttonLabel3"> <Text color="$accent1" variant="buttonLabel3">
{t('explore.search.action.clear')} {t('Clear all')}
</Text> </Text>
</TouchableArea> </TouchableArea>
</Flex> </Flex>
...@@ -92,19 +89,16 @@ export function SearchEmptySection(): JSX.Element { ...@@ -92,19 +89,16 @@ export function SearchEmptySection(): JSX.Element {
</AnimatedFlex> </AnimatedFlex>
)} )}
<Flex gap="$spacing4"> <Flex gap="$spacing4">
<SectionHeaderText icon={<TrendIcon />} title={t('explore.search.section.popularTokens')} /> <SectionHeaderText icon={<TrendIcon />} title={t('Popular tokens')} />
<SearchPopularTokens /> <SearchPopularTokens />
</Flex> </Flex>
<Flex gap="$spacing4"> <Flex gap="$spacing4">
<SectionHeaderText icon={<TrendIcon />} title={t('explore.search.section.popularNFT')} /> <SectionHeaderText icon={<TrendIcon />} title={t('Popular NFT collections')} />
<SearchPopularNFTCollections /> <SearchPopularNFTCollections />
</Flex> </Flex>
<FlatList <FlatList
ListHeaderComponent={ ListHeaderComponent={
<SectionHeaderText <SectionHeaderText icon={<TrendIcon />} title={t('Suggested wallets')} />
icon={<TrendIcon />}
title={t('explore.search.section.suggestedWallets')}
/>
} }
data={SUGGESTED_WALLETS} data={SUGGESTED_WALLETS}
keyExtractor={walletKey} keyExtractor={walletKey}
......
...@@ -2,12 +2,12 @@ import React from 'react' ...@@ -2,12 +2,12 @@ import React from 'react'
import { SearchPopularTokens } from 'src/components/explore/search/SearchPopularTokens' import { SearchPopularTokens } from 'src/components/explore/search/SearchPopularTokens'
import { render, screen } from 'src/test/test-utils' import { render, screen } from 'src/test/test-utils'
import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks' import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks'
import { ethToken, usdcToken, wethToken } from 'wallet/src/test/fixtures' import { EthToken, TopTokens } from 'wallet/src/test/gqlFixtures'
const resolvers: Resolvers = { const resolvers: Resolvers = {
Query: { Query: {
topTokens: () => [wethToken(), usdcToken()], topTokens: () => TopTokens,
tokens: () => [ethToken({ address: null })], tokens: () => [{ ...EthToken, address: null }],
}, },
} }
......
...@@ -9,19 +9,19 @@ export const SearchResultsLoader = (): JSX.Element => { ...@@ -9,19 +9,19 @@ export const SearchResultsLoader = (): JSX.Element => {
return ( return (
<Flex gap="$spacing16"> <Flex gap="$spacing16">
<Flex gap="$spacing12"> <Flex gap="$spacing12">
<SectionHeaderText title={t('explore.search.section.tokens')} /> <SectionHeaderText title={t('Tokens')} />
<AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing8"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing8">
<Loader.Token repeat={2} /> <Loader.Token repeat={2} />
</AnimatedFlex> </AnimatedFlex>
</Flex> </Flex>
<Flex gap="$spacing12"> <Flex gap="$spacing12">
<SectionHeaderText title={t('explore.search.section.nft')} /> <SectionHeaderText title={t('NFT Collections')} />
<AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing8"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing8">
<Loader.Token repeat={2} /> <Loader.Token repeat={2} />
</AnimatedFlex> </AnimatedFlex>
</Flex> </Flex>
<Flex gap="$spacing12"> <Flex gap="$spacing12">
<SectionHeaderText title={t('explore.search.section.wallets')} /> <SectionHeaderText title={t('Wallets')} />
<AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing8"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} mx="$spacing8">
<Loader.Token /> <Loader.Token />
</AnimatedFlex> </AnimatedFlex>
......
...@@ -34,19 +34,19 @@ import { SearchResultOrHeader } from './types' ...@@ -34,19 +34,19 @@ import { SearchResultOrHeader } from './types'
const WalletHeaderItem: SearchResultOrHeader = { const WalletHeaderItem: SearchResultOrHeader = {
type: SEARCH_RESULT_HEADER_KEY, type: SEARCH_RESULT_HEADER_KEY,
title: i18n.t('explore.search.section.wallets'), title: i18n.t('Wallets'),
} }
const TokenHeaderItem: SearchResultOrHeader = { const TokenHeaderItem: SearchResultOrHeader = {
type: SEARCH_RESULT_HEADER_KEY, type: SEARCH_RESULT_HEADER_KEY,
title: i18n.t('explore.search.section.tokens'), title: i18n.t('Tokens'),
} }
const NFTHeaderItem: SearchResultOrHeader = { const NFTHeaderItem: SearchResultOrHeader = {
type: SEARCH_RESULT_HEADER_KEY, type: SEARCH_RESULT_HEADER_KEY,
title: i18n.t('explore.search.section.nft'), title: i18n.t('NFT Collections'),
} }
const EtherscanHeaderItem: SearchResultOrHeader = { const EtherscanHeaderItem: SearchResultOrHeader = {
type: SEARCH_RESULT_HEADER_KEY, type: SEARCH_RESULT_HEADER_KEY,
title: i18n.t('explore.search.action.viewEtherscan', { title: i18n.t('View on {{ blockExplorerName }}', {
blockExplorerName: CHAIN_INFO[ChainId.Mainnet].explorer.name, blockExplorerName: CHAIN_INFO[ChainId.Mainnet].explorer.name,
}), }),
} }
...@@ -170,8 +170,8 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): ...@@ -170,8 +170,8 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }):
return ( return (
<AnimatedFlex entering={FadeIn} exiting={FadeOut} pt="$spacing24"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} pt="$spacing24">
<BaseCard.ErrorState <BaseCard.ErrorState
retryButtonLabel="common.button.retry" retryButtonLabel="Retry"
title={t('explore.search.error')} title={t('Couldn’t load search results')}
onRetry={onRetry} onRetry={onRetry}
/> />
</AnimatedFlex> </AnimatedFlex>
...@@ -184,8 +184,8 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): ...@@ -184,8 +184,8 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }):
ListEmptyComponent={ ListEmptyComponent={
<AnimatedFlex entering={FadeIn} exiting={FadeOut} gap="$spacing8" mx="$spacing8"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} gap="$spacing8" mx="$spacing8">
<Text color="$neutral2" variant="body1"> <Text color="$neutral2" variant="body1">
<Trans i18nKey="explore.search.empty.full"> <Trans t={t}>
No results found for <Text color="$neutral1">"{{ searchQuery }}"</Text> No results found for <Text color="$neutral1">"{searchQuery}"</Text>
</Trans> </Trans>
</Text> </Text>
</AnimatedFlex> </AnimatedFlex>
......
...@@ -63,8 +63,8 @@ export function SearchENSAddressItem({ ...@@ -63,8 +63,8 @@ export function SearchENSAddressItem({
{showSecondLine ? ( {showSecondLine ? (
<Text color="$neutral2" ellipsizeMode="tail" numberOfLines={1} variant="subheading2"> <Text color="$neutral2" ellipsizeMode="tail" numberOfLines={1} variant="subheading2">
{showOwnedBy && {showOwnedBy &&
t('explore.search.label.ownedBy', { t('Owned by {{owner}}', {
ownerAddress: primaryENSName || formattedAddress, owner: primaryENSName || formattedAddress,
})} })}
{showAddress && formattedAddress} {showAddress && formattedAddress}
</Text> </Text>
......
...@@ -77,8 +77,8 @@ export function SearchWalletItemBase({ ...@@ -77,8 +77,8 @@ export function SearchWalletItemBase({
const menuActions = useMemo(() => { const menuActions = useMemo(() => {
return isFavorited return isFavorited
? [{ title: t('explore.wallets.favorite.action.remove'), systemIcon: 'heart.fill' }] ? [{ title: t('Remove favorite'), systemIcon: 'heart.fill' }]
: [{ title: t('explore.wallets.favorite.action.add'), systemIcon: 'heart' }] : [{ title: t('Favorite wallet'), systemIcon: 'heart' }]
}, [isFavorited, t]) }, [isFavorited, t])
return ( return (
......
...@@ -7,16 +7,7 @@ import { ...@@ -7,16 +7,7 @@ import {
import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks' import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { SearchResultType } from 'wallet/src/features/search/SearchResult' import { SearchResultType } from 'wallet/src/features/search/SearchResult'
import { import { SearchTokens, TopNFTCollections } from 'wallet/src/test/gqlFixtures'
amount,
ethToken,
nftCollection,
nftContract,
token,
tokenMarket,
tokenProject,
} from 'wallet/src/test/fixtures'
import { createArray } from 'wallet/src/test/utils'
type ExploreSearchResult = NonNullable<ExploreSearchQuery> type ExploreSearchResult = NonNullable<ExploreSearchQuery>
...@@ -26,31 +17,29 @@ describe(formatTokenSearchResults, () => { ...@@ -26,31 +17,29 @@ describe(formatTokenSearchResults, () => {
}) })
it('filters out duplicate results', () => { it('filters out duplicate results', () => {
const searchToken = token() const data = [SearchTokens[0], SearchTokens[0]] as ExploreSearchResult['searchTokens']
const data = createArray(2, () => searchToken)
const result = formatTokenSearchResults(data, '') const result = formatTokenSearchResults(data, '')
expect(result).toHaveLength(1) expect(result).toHaveLength(1)
expect(result?.[0]?.address).toEqual(data[0].address) expect(result?.[0]?.address).toEqual(SearchTokens?.[0]?.address)
}) })
it('uses tokens with highest volume for tokens with the same project id', () => { it('uses tokens with highest volume for duplicate results', () => {
const changedAddress = faker.finance.ethereumAddress() const changedAddress = faker.finance.ethereumAddress()
const data = [ const data = [
// Tokens with the same address and chain will have the same project id SearchTokens[0],
ethToken({ {
market: tokenMarket({ volume: amount({ value: 10 }) }), ...SearchTokens[0],
}),
ethToken({
address: changedAddress, address: changedAddress,
market: tokenMarket({ volume: amount({ value: 100 }) }), market: {
}), volume: {
ethToken({ value: 100,
market: tokenMarket({ volume: amount({ value: 20 }) }), },
}), },
] },
] as ExploreSearchResult['searchTokens']
const result = formatTokenSearchResults(data, '') const result = formatTokenSearchResults(data, '')
...@@ -60,10 +49,22 @@ describe(formatTokenSearchResults, () => { ...@@ -60,10 +49,22 @@ describe(formatTokenSearchResults, () => {
expect(result?.[0]?.address).toEqual(changedAddress) expect(result?.[0]?.address).toEqual(changedAddress)
}) })
it('sorts results by best search query match', () => { it('sorts results by search query match', () => {
const data: ExploreSearchResult['searchTokens'] = [ const data: ExploreSearchResult['searchTokens'] = [
ethToken({ project: tokenProject({ name: 'UniswapStartingName' }) }), {
ethToken({ project: tokenProject({ name: 'Uniswap' }) }), project: {
name: 'UniswapStartingName',
id: '2',
},
chain: Chain.Ethereum,
},
{
project: {
name: 'Uniswap',
id: '1',
},
chain: Chain.Ethereum,
},
] ]
const result = formatTokenSearchResults(data, 'uniswap') const result = formatTokenSearchResults(data, 'uniswap')
...@@ -74,65 +75,59 @@ describe(formatTokenSearchResults, () => { ...@@ -74,65 +75,59 @@ describe(formatTokenSearchResults, () => {
}) })
it('properly formats token search result', () => { it('properly formats token search result', () => {
const searchToken = token() const data = [SearchTokens[0]] as ExploreSearchResult['searchTokens']
const data = [searchToken]
const result = formatTokenSearchResults(data, '') const result = formatTokenSearchResults(data, '')
expect(result).toHaveLength(1) expect(result).toHaveLength(1)
expect(result?.[0]?.type).toEqual(SearchResultType.Token) expect(result?.[0]?.type).toEqual(SearchResultType.Token)
expect(result?.[0]?.chainId).toEqual(fromGraphQLChain(searchToken.chain)) expect(result?.[0]?.chainId).toEqual(fromGraphQLChain(SearchTokens[0]?.chain))
expect(result?.[0]?.address).toEqual(searchToken.address) expect(result?.[0]?.address).toEqual(SearchTokens?.[0]?.address)
expect(result?.[0]?.name).toEqual(searchToken.project?.name) expect(result?.[0]?.name).toEqual(SearchTokens?.[0]?.project?.name)
expect(result?.[0]?.symbol).toEqual(searchToken.symbol) expect(result?.[0]?.symbol).toEqual(SearchTokens?.[0]?.symbol)
expect(result?.[0]?.logoUrl).toEqual(searchToken.project?.logoUrl) expect(result?.[0]?.logoUrl).toEqual(SearchTokens?.[0]?.project?.logoUrl)
expect(result?.[0]?.safetyLevel).toEqual(searchToken.project?.safetyLevel) expect(result?.[0]?.safetyLevel).toEqual(SearchTokens?.[0]?.project?.safetyLevel)
})
})
describe(gqlNFTToNFTCollectionSearchResult, () => {
const node = TopNFTCollections[0]
it('returns null if required data is missing', () => {
expect(gqlNFTToNFTCollectionSearchResult({ ...node, name: null })).toEqual(null)
expect(gqlNFTToNFTCollectionSearchResult({ ...node, nftContracts: undefined })).toEqual(null)
expect(gqlNFTToNFTCollectionSearchResult({ ...node, nftContracts: [] })).toEqual(null)
})
it('properly formats NFT collection search result', () => {
const result = gqlNFTToNFTCollectionSearchResult(node)
expect(result?.type).toEqual(SearchResultType.NFTCollection)
expect(result?.chainId).toEqual(fromGraphQLChain(Chain.Ethereum))
expect(result?.address).toEqual(node?.nftContracts?.[0]?.address)
expect(result?.name).toEqual(node?.name)
expect(result?.imageUrl).toEqual(node?.image?.url)
expect(result?.isVerified).toEqual(node?.isVerified)
}) })
})
describe(gqlNFTToNFTCollectionSearchResult, () => { describe(formatNFTCollectionSearchResults, () => {
const collection = nftCollection({ it('returns undefined if there is no data', () => {
nftContracts: [nftContract({ chain: Chain.Ethereum })], expect(formatNFTCollectionSearchResults(null)).toEqual(undefined)
})
it('returns null if required data is missing', () => {
expect(gqlNFTToNFTCollectionSearchResult({ ...collection, name: null })).toEqual(null)
expect(gqlNFTToNFTCollectionSearchResult({ ...collection, nftContracts: undefined })).toEqual(
null
)
expect(gqlNFTToNFTCollectionSearchResult({ ...collection, nftContracts: [] })).toEqual(null)
})
it('properly formats NFT collection search result', () => {
const result = gqlNFTToNFTCollectionSearchResult(collection)
expect(result?.type).toEqual(SearchResultType.NFTCollection)
expect(result?.chainId).toEqual(fromGraphQLChain(Chain.Ethereum))
expect(result?.address).toEqual(collection.nftContracts[0]?.address)
expect(result?.name).toEqual(collection?.name)
expect(result?.imageUrl).toEqual(collection?.image?.url)
expect(result?.isVerified).toEqual(collection?.isVerified)
})
}) })
describe(formatNFTCollectionSearchResults, () => { it('filters out nfts that cannot be formatted', () => {
it('returns undefined if there is no data', () => { const nftSearchResult = {
expect(formatNFTCollectionSearchResults(null)).toEqual(undefined) edges: [
}) ...TopNFTCollections.map((nft) => ({ node: nft })),
{ node: { ...TopNFTCollections[0], name: null } },
it('filters out nfts that cannot be formatted', () => { ],
const topNFTCollections = createArray(2, nftCollection) }
const nftSearchResult = {
edges: [ const result = formatNFTCollectionSearchResults(nftSearchResult)
...topNFTCollections.map((nft) => ({ node: nft })),
{ node: nftCollection({ name: null }) }, expect(result).toHaveLength(2)
], expect(result?.[0]?.address).toEqual(TopNFTCollections?.[0]?.nftContracts?.[0]?.address)
} expect(result?.[1]?.address).toEqual(TopNFTCollections?.[1]?.nftContracts?.[0]?.address)
const result = formatNFTCollectionSearchResults(nftSearchResult)
expect(result).toHaveLength(2)
expect(result?.[0]?.address).toEqual(topNFTCollections[0].nftContracts[0]?.address)
expect(result?.[1]?.address).toEqual(topNFTCollections[1].nftContracts[0]?.address)
})
}) })
}) })
import { SEARCH_RESULT_HEADER_KEY } from 'src/components/explore/search/constants'
import { SearchResultOrHeader } from 'src/components/explore/search/types'
import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks' import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { import {
...@@ -8,6 +6,8 @@ import { ...@@ -8,6 +6,8 @@ import {
TokenSearchResult, TokenSearchResult,
} from 'wallet/src/features/search/SearchResult' } from 'wallet/src/features/search/SearchResult'
import { searchResultId } from 'wallet/src/features/search/searchHistorySlice' import { searchResultId } from 'wallet/src/features/search/searchHistorySlice'
import { SEARCH_RESULT_HEADER_KEY } from './constants'
import { SearchResultOrHeader } from './types'
const MAX_TOKEN_RESULTS_COUNT = 4 const MAX_TOKEN_RESULTS_COUNT = 4
...@@ -109,7 +109,7 @@ export const gqlNFTToNFTCollectionSearchResult = ( ...@@ -109,7 +109,7 @@ export const gqlNFTToNFTCollectionSearchResult = (
): NFTCollectionSearchResult | null => { ): NFTCollectionSearchResult | null => {
const contract = node?.nftContracts?.[0] const contract = node?.nftContracts?.[0]
// Only show NFT results that have fully populated results // Only show NFT results that have fully populated results
const chainId = fromGraphQLChain(contract?.chain ?? Chain.Ethereum) const chainId = fromGraphQLChain(node?.nftContracts?.[0]?.chain ?? Chain.Ethereum)
if (node.name && contract?.address && chainId) { if (node.name && contract?.address && chainId) {
return { return {
type: SearchResultType.NFTCollection, type: SearchResultType.NFTCollection,
......
...@@ -25,7 +25,7 @@ export function FiatOnRampCtaButton({ ...@@ -25,7 +25,7 @@ export function FiatOnRampCtaButton({
}: FiatOnRampCtaButtonProps): JSX.Element { }: FiatOnRampCtaButtonProps): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const buttonAvailable = eligible || isLoading const buttonAvailable = eligible || isLoading
const continueText = eligible ? continueButtonText : t('fiatOnRamp.error.unsupported') const continueText = eligible ? continueButtonText : t('Not supported in region')
return ( return (
<Trace <Trace
logPress logPress
......
...@@ -90,15 +90,13 @@ export function FORQuoteItem({ ...@@ -90,15 +90,13 @@ export function FORQuoteItem({
<Flex alignItems="flex-end" gap="$spacing4"> <Flex alignItems="flex-end" gap="$spacing4">
{quoteAmount && ( {quoteAmount && (
<Text color="$neutral1" variant="body3"> <Text color="$neutral1" variant="body3">
{t('fiatOnRamp.quote.amount', { {t('Receive {{amount}}', {
tokenAmount: `${quoteAmount + getSymbolDisplayText(currency?.symbol)}`, amount: `${quoteAmount + getSymbolDisplayText(currency?.symbol)}`,
})} })}
</Text> </Text>
)} )}
<Text color="$neutral2" variant="body3"> <Text color="$neutral2" variant="body3">
{t('fiatOnRamp.quote.amountAfterFees', { {t('{{amount}} after fees', { amount: quoteEquivalentInSourceCurrencyAmount })}
tokenAmount: quoteEquivalentInSourceCurrencyAmount,
})}
</Text> </Text>
</Flex> </Flex>
{showCarret ? ( {showCarret ? (
......
...@@ -65,20 +65,22 @@ export function ForceUpgradeModal(): JSX.Element { ...@@ -65,20 +65,22 @@ export function ForceUpgradeModal(): JSX.Element {
<> <>
{isVisible && ( {isVisible && (
<WarningModal <WarningModal
confirmText={t('forceUpgrade.action.confirm')} confirmText={t('Update app')}
hideHandlebar={upgradeStatus === UpgradeStatus.Required} hideHandlebar={upgradeStatus === UpgradeStatus.Required}
isDismissible={upgradeStatus !== UpgradeStatus.Required} isDismissible={upgradeStatus !== UpgradeStatus.Required}
modalName={ModalName.ForceUpgradeModal} modalName={ModalName.ForceUpgradeModal}
severity={WarningSeverity.High} severity={WarningSeverity.High}
title={t('forceUpgrade.title')} title={t('Update the app to continue')}
onClose={onClose} onClose={onClose}
onConfirm={onPressConfirm}> onConfirm={onPressConfirm}>
<Text color="$neutral2" textAlign="center" variant="body2"> <Text color="$neutral2" textAlign="center" variant="body2">
{t('forceUpgrade.description')} {t(
'The version of Uniswap Wallet you’re using is out of date and is missing critical upgrades. If you don’t update the app or you don’t have your recovery phrase written down, you won’t be able to access your assets.'
)}
</Text> </Text>
{mnemonicId && ( {mnemonicId && (
<Text color="$accent1" variant="buttonLabel3" onPress={onPressViewRecovery}> <Text color="$accent1" variant="buttonLabel3" onPress={onPressViewRecovery}>
{t('forceUpgrade.action.seedPhrase')} {t('View recovery phrase')}
</Text> </Text>
)} )}
</WarningModal> </WarningModal>
...@@ -94,7 +96,7 @@ export function ForceUpgradeModal(): JSX.Element { ...@@ -94,7 +96,7 @@ export function ForceUpgradeModal(): JSX.Element {
<TouchableArea onPress={onDismiss}> <TouchableArea onPress={onDismiss}>
<BackButtonView size={BACK_BUTTON_SIZE} /> <BackButtonView size={BACK_BUTTON_SIZE} />
</TouchableArea> </TouchableArea>
<Text variant="subheading1">{t('forceUpgrade.label.seedPhrase')}</Text> <Text variant="subheading1">{t('Recovery phrase')}</Text>
<Flex width={BACK_BUTTON_SIZE} /> <Flex width={BACK_BUTTON_SIZE} />
</Flex> </Flex>
<SeedPhraseDisplay mnemonicId={mnemonicId} onDismiss={onDismiss} /> <SeedPhraseDisplay mnemonicId={mnemonicId} onDismiss={onDismiss} />
......
...@@ -76,8 +76,8 @@ export const FeedTab = memo( ...@@ -76,8 +76,8 @@ export const FeedTab = memo(
const errorCard = ( const errorCard = (
<Flex grow style={containerProps?.emptyContainerStyle}> <Flex grow style={containerProps?.emptyContainerStyle}>
<BaseCard.ErrorState <BaseCard.ErrorState
retryButtonLabel={t('common.button.retry')} retryButtonLabel={t('Retry')}
title={t('home.feed.error')} title={t('Couldn’t load activity')}
onRetry={onRetry} onRetry={onRetry}
/> />
</Flex> </Flex>
...@@ -86,9 +86,9 @@ export const FeedTab = memo( ...@@ -86,9 +86,9 @@ export const FeedTab = memo(
const emptyListView = ( const emptyListView = (
<Flex grow style={containerProps?.emptyContainerStyle}> <Flex grow style={containerProps?.emptyContainerStyle}>
<BaseCard.EmptyState <BaseCard.EmptyState
description={t('home.feed.empty.description')} description={t('When your favorited wallets makes transactions, they’ll appear here.')}
icon={<NoTransactions />} icon={<NoTransactions />}
title={t('home.feed.empty.title')} title={t('No activity yet')}
onPress={onPressReceive} onPress={onPressReceive}
/> />
</Flex> </Flex>
......
import { FlashList } from '@shopify/flash-list' import { FlashList } from '@shopify/flash-list'
import React, { forwardRef, memo, useCallback, useMemo } from 'react' import React, { forwardRef, memo, useCallback, useMemo } from 'react'
import { RefreshControl } from 'react-native' import { RefreshControl } from 'react-native'
import { useAppDispatch } from 'src/app/hooks'
import { useAppStackNavigation } from 'src/app/navigation/types' import { useAppStackNavigation } from 'src/app/navigation/types'
import { useAdaptiveFooter } from 'src/components/home/hooks' import { useAdaptiveFooter } from 'src/components/home/hooks'
import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers' import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers'
import { NftView } from 'src/components/NFT/NftView' import { NftView } from 'src/components/NFT/NftView'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' import { Flex, useDeviceInsets, useSporeColors } from 'ui/src'
import { NftsList } from 'wallet/src/components/nfts/NftsList' import { NftsList } from 'wallet/src/components/nfts/NftsList'
import { GQLQueries } from 'wallet/src/data/queries' import { GQLQueries } from 'wallet/src/data/queries'
import { NFTItem } from 'wallet/src/features/nfts/types' import { NFTItem } from 'wallet/src/features/nfts/types'
import { ModalName } from 'wallet/src/telemetry/constants'
import { isAndroid } from 'wallet/src/utils/platform' import { isAndroid } from 'wallet/src/utils/platform'
export const NFTS_TAB_DATA_DEPENDENCIES = [GQLQueries.NftsTab] export const NFTS_TAB_DATA_DEPENDENCIES = [GQLQueries.NftsTab]
...@@ -34,7 +29,6 @@ export const NftsTab = memo( ...@@ -34,7 +29,6 @@ export const NftsTab = memo(
ref ref
) { ) {
const colors = useSporeColors() const colors = useSporeColors()
const dispatch = useAppDispatch()
const insets = useDeviceInsets() const insets = useDeviceInsets()
const navigation = useAppStackNavigation() const navigation = useAppStackNavigation()
...@@ -42,14 +36,6 @@ export const NftsTab = memo( ...@@ -42,14 +36,6 @@ export const NftsTab = memo(
containerProps?.contentContainerStyle containerProps?.contentContainerStyle
) )
const onPressScan = (): void => {
// in case we received a pending session from a previous scan after closing modal
dispatch(removePendingSession())
dispatch(
openModal({ name: ModalName.WalletConnectScan, initialState: ScannerModalState.WalletQr })
)
}
const renderNFTItem = useCallback( const renderNFTItem = useCallback(
(item: NFTItem) => { (item: NFTItem) => {
const onPressNft = (): void => { const onPressNft = (): void => {
...@@ -95,7 +81,6 @@ export const NftsTab = memo( ...@@ -95,7 +81,6 @@ export const NftsTab = memo(
renderNFTItem={renderNFTItem} renderNFTItem={renderNFTItem}
renderedInModal={renderedInModal} renderedInModal={renderedInModal}
onContentSizeChange={onContentSizeChange} onContentSizeChange={onContentSizeChange}
onPressEmptyState={onPressScan}
onRefresh={onRefresh} onRefresh={onRefresh}
onScroll={scrollHandler} onScroll={scrollHandler}
{...containerProps} {...containerProps}
......
...@@ -71,9 +71,9 @@ export const TokensTab = memo( ...@@ -71,9 +71,9 @@ export const TokensTab = memo(
// Show different empty state on external profile pages // Show different empty state on external profile pages
return isExternalProfile ? ( return isExternalProfile ? (
<BaseCard.EmptyState <BaseCard.EmptyState
description={t('home.tokens.empty.description')} description={t('When this wallet buys or receives tokens, they’ll appear here.')}
icon={<NoTokens />} icon={<NoTokens />}
title={t('home.tokens.empty.title')} title={t('No tokens yet')}
onPress={onPressAction} onPress={onPressAction}
/> />
) : ( ) : (
......
...@@ -37,8 +37,8 @@ export function WalletEmptyState(): JSX.Element { ...@@ -37,8 +37,8 @@ export function WalletEmptyState(): JSX.Element {
const options: { [key in ActionOption]: ActionCardItem } = useMemo( const options: { [key in ActionOption]: ActionCardItem } = useMemo(
() => ({ () => ({
[ActionOption.Buy]: { [ActionOption.Buy]: {
title: t('home.tokens.empty.action.buy.title'), title: t('Buy crypto'),
blurb: t('home.tokens.empty.action.buy.description'), blurb: t('You’ll need ETH to get started. Buy with a card or bank.'),
elementName: ElementName.EmptyStateBuy, elementName: ElementName.EmptyStateBuy,
icon: ( icon: (
<IconContainer <IconContainer
...@@ -54,8 +54,8 @@ export function WalletEmptyState(): JSX.Element { ...@@ -54,8 +54,8 @@ export function WalletEmptyState(): JSX.Element {
onPress: () => dispatch(openModal({ name: ModalName.FiatOnRamp })), onPress: () => dispatch(openModal({ name: ModalName.FiatOnRamp })),
}, },
[ActionOption.Receive]: { [ActionOption.Receive]: {
title: t('home.tokens.empty.action.receive.title'), title: t('Receive funds'),
blurb: t('home.tokens.empty.action.receive.description'), blurb: t('Transfer tokens from another wallet or crypto exchange.'),
elementName: ElementName.EmptyStateReceive, elementName: ElementName.EmptyStateReceive,
icon: ( icon: (
<IconContainer <IconContainer
...@@ -77,8 +77,8 @@ export function WalletEmptyState(): JSX.Element { ...@@ -77,8 +77,8 @@ export function WalletEmptyState(): JSX.Element {
), ),
}, },
[ActionOption.Import]: { [ActionOption.Import]: {
title: t('home.tokens.empty.action.import.title'), title: t('Import wallet'),
blurb: t('home.tokens.empty.action.import.description'), blurb: t(`Enter this wallet’s recovery phrase to begin swapping and sending.`),
elementName: ElementName.EmptyStateImport, elementName: ElementName.EmptyStateImport,
icon: ( icon: (
<IconContainer <IconContainer
......
...@@ -16,7 +16,7 @@ export function BackButtonView({ size, color, showButtonLabel }: Props): JSX.Ele ...@@ -16,7 +16,7 @@ export function BackButtonView({ size, color, showButtonLabel }: Props): JSX.Ele
<Icons.RotatableChevron color={color ?? '$neutral2'} height={size} width={size} /> <Icons.RotatableChevron color={color ?? '$neutral2'} height={size} width={size} />
{showButtonLabel && ( {showButtonLabel && (
<Text color="$neutral2" variant="subheading1"> <Text color="$neutral2" variant="subheading1">
{t('common.button.back')} {t('Back')}
</Text> </Text>
)} )}
</Flex> </Flex>
......
...@@ -78,22 +78,20 @@ export function SeedPhraseDisplay({ ...@@ -78,22 +78,20 @@ export function SeedPhraseDisplay({
testID={ElementName.Next} testID={ElementName.Next}
theme="secondary" theme="secondary"
onPress={(): void => setShowSeedPhrase(!showSeedPhrase)}> onPress={(): void => setShowSeedPhrase(!showSeedPhrase)}>
{showSeedPhrase {showSeedPhrase ? t('Hide recovery phrase') : t('Show recovery phrase')}
? t('setting.seedPhrase.action.hide')
: t('setting.seedPhrase.account.show')}
</Button> </Button>
</Flex> </Flex>
{showSeedPhraseViewWarningModal && ( {showSeedPhraseViewWarningModal && (
<WarningModal <WarningModal
hideHandlebar hideHandlebar
caption={t('setting.seedPhrase.warning.view.message')} caption={t('Anyone who knows your recovery phrase can access your wallet and funds.')}
closeText={t('common.button.close')} closeText={t('Close')}
confirmText={t('common.button.view')} confirmText={t('View')}
isDismissible={false} isDismissible={false}
modalName={ModalName.ViewSeedPhraseWarning} modalName={ModalName.ViewSeedPhraseWarning}
severity={WarningSeverity.High} severity={WarningSeverity.High}
title={t('setting.seedPhrase.warning.view.title')} title={t('View this in a private place')}
onCancel={(): void => { onCancel={(): void => {
setShowSeedPhraseViewWarningModal(false) setShowSeedPhraseViewWarningModal(false)
if (!showSeedPhrase) { if (!showSeedPhrase) {
...@@ -105,10 +103,12 @@ export function SeedPhraseDisplay({ ...@@ -105,10 +103,12 @@ export function SeedPhraseDisplay({
)} )}
{showScreenShotWarningModal && ( {showScreenShotWarningModal && (
<WarningModal <WarningModal
caption={t('setting.seedPhrase.warning.screenshot.message')} caption={t(
confirmText={t('common.button.close')} 'Anyone who gains access to your photos can access your wallet. We recommend that you write down your words instead.'
)}
confirmText={t('Close')}
modalName={ModalName.ScreenshotWarning} modalName={ModalName.ScreenshotWarning}
title={t('setting.seedPhrase.warning.screenshot.title')} title={t('Screenshots aren’t secure')}
onConfirm={(): void => setShowScreenShotWarningModal(false)} onConfirm={(): void => setShowScreenShotWarningModal(false)}
/> />
)} )}
......
...@@ -118,7 +118,7 @@ export function LongMarkdownText(props: LongMarkdownTextProps): JSX.Element { ...@@ -118,7 +118,7 @@ export function LongMarkdownText(props: LongMarkdownTextProps): JSX.Element {
testID="read-more-button" testID="read-more-button"
variant="buttonLabel3" variant="buttonLabel3"
onPress={toggleExpanded}> onPress={toggleExpanded}>
{expanded ? t('common.longText.button.less') : t('common.longText.button.more')} {expanded ? t('Read less') : t('Read more')}
</Text> </Text>
) : null} ) : null}
</Flex> </Flex>
......
...@@ -69,7 +69,7 @@ export function LongText(props: LongTextProps): JSX.Element { ...@@ -69,7 +69,7 @@ export function LongText(props: LongTextProps): JSX.Element {
testID="read-more-button" testID="read-more-button"
variant="buttonLabel3" variant="buttonLabel3"
onPress={(): void => setExpanded(!expanded)}> onPress={(): void => setExpanded(!expanded)}>
{expanded ? t('common.longText.button.less') : t('common.longText.button.more')} {expanded ? t('Read less') : t('Read more')}
</Text> </Text>
) : null} ) : null}
</Flex> </Flex>
......
...@@ -49,7 +49,7 @@ export function TooltipInfoButton({ ...@@ -49,7 +49,7 @@ export function TooltipInfoButton({
<WarningModal <WarningModal
backgroundIconColor={backgroundIconColor} backgroundIconColor={backgroundIconColor}
caption={modalText} caption={modalText}
closeText={closeText ?? t('common.button.close')} closeText={closeText ?? t('Close')}
icon={modalIcon} icon={modalIcon}
modalName={ModalName.TooltipContent} modalName={ModalName.TooltipContent}
title={modalTitle} title={modalTitle}
......
...@@ -127,7 +127,7 @@ export function ChangeUnitagModal({ ...@@ -127,7 +127,7 @@ export function ChangeUnitagModal({
dispatch( dispatch(
pushNotification({ pushNotification({
type: AppNotificationType.Success, type: AppNotificationType.Success,
title: t('unitags.notification.username.title'), title: t('Username changed'),
}) })
) )
navigation.goBack() navigation.goBack()
...@@ -141,7 +141,7 @@ export function ChangeUnitagModal({ ...@@ -141,7 +141,7 @@ export function ChangeUnitagModal({
dispatch( dispatch(
pushNotification({ pushNotification({
type: AppNotificationType.Error, type: AppNotificationType.Error,
errorMessage: t('unitags.notification.username.error'), errorMessage: t('Could not change username. Try again later.'),
}) })
) )
onClose() onClose()
...@@ -209,7 +209,7 @@ export function ChangeUnitagModal({ ...@@ -209,7 +209,7 @@ export function ChangeUnitagModal({
pt="$spacing12" pt="$spacing12"
px="$spacing24"> px="$spacing24">
<Text textAlign="center" variant="subheading1"> <Text textAlign="center" variant="subheading1">
{t('unitags.editUsername.title')} {t('Edit username')}
</Text> </Text>
<Flex <Flex
row row
...@@ -249,7 +249,7 @@ export function ChangeUnitagModal({ ...@@ -249,7 +249,7 @@ export function ChangeUnitagModal({
py="$spacing12" py="$spacing12"
width="100%"> width="100%">
<Text color="$statusCritical" variant="body3"> <Text color="$statusCritical" variant="body3">
{t('unitags.editUsername.warning.max')} {t('You’ve reached the maximum number of 2 usernames changes.')}
</Text> </Text>
</Flex> </Flex>
) : ( ) : (
...@@ -260,7 +260,9 @@ export function ChangeUnitagModal({ ...@@ -260,7 +260,9 @@ export function ChangeUnitagModal({
py="$spacing12" py="$spacing12"
width="100%"> width="100%">
<Text color="$neutral2" variant="body3"> <Text color="$neutral2" variant="body3">
{t('unitags.editUsername.warning.default')} {t(
'Once you change your username, you can never claim it again. You can only change it 2 times.'
)}
</Text> </Text>
</Flex> </Flex>
)} )}
...@@ -283,7 +285,7 @@ export function ChangeUnitagModal({ ...@@ -283,7 +285,7 @@ export function ChangeUnitagModal({
<ActivityIndicator color={colors.sporeWhite.val} /> <ActivityIndicator color={colors.sporeWhite.val} />
</Flex> </Flex>
) : ( ) : (
t('unitags.editUsername.button.confirm') t('Save changes')
)} )}
</Button> </Button>
</Flex> </Flex>
...@@ -314,17 +316,19 @@ function ChangeUnitagConfirmModal({ ...@@ -314,17 +316,19 @@ function ChangeUnitagConfirmModal({
<Icons.AlertTriangle color="$statusCritical" size="$icon.24" /> <Icons.AlertTriangle color="$statusCritical" size="$icon.24" />
</Flex> </Flex>
<Text textAlign="center" variant="subheading1"> <Text textAlign="center" variant="subheading1">
{t('unitags.editUsername.confirm.title')} {t('Are you sure?')}
</Text> </Text>
<Text color="$neutral2" textAlign="center" variant="body2"> <Text color="$neutral2" textAlign="center" variant="body2">
{t('unitags.editUsername.confirm.subtitle')} {t(
'You’re about to change your username. Once you change it, you can never claim it again.'
)}
</Text> </Text>
<Flex centered row gap="$spacing12" pt="$spacing24"> <Flex centered row gap="$spacing12" pt="$spacing24">
<Button fill testID={ElementName.Remove} theme="secondary" onPress={onClose}> <Button fill testID={ElementName.Remove} theme="secondary" onPress={onClose}>
{t('common.button.back')} {t('Back')}
</Button> </Button>
<Button fill testID={ElementName.Remove} theme="detrimental" onPress={onChangeSubmit}> <Button fill testID={ElementName.Remove} theme="detrimental" onPress={onChangeSubmit}>
{t('common.button.confirm')} {t('Confirm')}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -128,9 +128,9 @@ const ChoosePhotoOption = ({ type }: { type: PhotoAction }): JSX.Element => { ...@@ -128,9 +128,9 @@ const ChoosePhotoOption = ({ type }: { type: PhotoAction }): JSX.Element => {
color={type === PhotoAction.RemovePhoto ? '$statusCritical' : '$neutral1'} color={type === PhotoAction.RemovePhoto ? '$statusCritical' : '$neutral1'}
numberOfLines={1} numberOfLines={1}
variant="buttonLabel2"> variant="buttonLabel2">
{type === PhotoAction.BrowseCameraRoll && t('unitags.choosePhoto.option.cameraRoll')} {type === PhotoAction.BrowseCameraRoll && t('Choose from camera roll')}
{type === PhotoAction.BrowseNftsList && t('unitags.choosePhoto.option.nft')} {type === PhotoAction.BrowseNftsList && t('Choose an NFT')}
{type === PhotoAction.RemovePhoto && t('unitags.choosePhoto.option.remove')} {type === PhotoAction.RemovePhoto && t('Remove profile picture')}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -39,7 +39,7 @@ export function DeleteUnitagModal({ ...@@ -39,7 +39,7 @@ export function DeleteUnitagModal({
dispatch( dispatch(
pushNotification({ pushNotification({
type: AppNotificationType.Error, type: AppNotificationType.Error,
errorMessage: t('unitags.notification.delete.error'), errorMessage: t('Could not delete username. Try again later.'),
}) })
) )
onClose() onClose()
...@@ -66,7 +66,7 @@ export function DeleteUnitagModal({ ...@@ -66,7 +66,7 @@ export function DeleteUnitagModal({
dispatch( dispatch(
pushNotification({ pushNotification({
type: AppNotificationType.Success, type: AppNotificationType.Success,
title: t('unitags.notification.delete.title'), title: t('Username deleted'),
}) })
) )
navigation.goBack() navigation.goBack()
...@@ -93,10 +93,12 @@ export function DeleteUnitagModal({ ...@@ -93,10 +93,12 @@ export function DeleteUnitagModal({
<Icons.AlertTriangle color="$statusCritical" size="$icon.24" /> <Icons.AlertTriangle color="$statusCritical" size="$icon.24" />
</Flex> </Flex>
<Text textAlign="center" variant="subheading1"> <Text textAlign="center" variant="subheading1">
{t('unitags.delete.confirm.title')} {t('Are you sure?')}
</Text> </Text>
<Text color="$neutral2" textAlign="center" variant="body2"> <Text color="$neutral2" textAlign="center" variant="body2">
{t('unitags.delete.confirm.subtitle')} {t(
'You’re about to delete your username and customizable profile details. You will not be able to reclaim it.'
)}
</Text> </Text>
<Flex centered row gap="$spacing12" pt="$spacing24"> <Flex centered row gap="$spacing12" pt="$spacing24">
<Button <Button
...@@ -110,7 +112,7 @@ export function DeleteUnitagModal({ ...@@ -110,7 +112,7 @@ export function DeleteUnitagModal({
<ActivityIndicator color={colors.sporeWhite.val} /> <ActivityIndicator color={colors.sporeWhite.val} />
</Flex> </Flex>
) : ( ) : (
t('common.button.delete') t('Delete')
)} )}
</Button> </Button>
</Flex> </Flex>
......
import React from 'react' import React from 'react'
import { Trans, useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Keyboard, StyleProp, ViewStyle } from 'react-native' import { Keyboard, StyleProp, ViewStyle } from 'react-native'
import { useAppDispatch } from 'src/app/hooks' import { useAppDispatch } from 'src/app/hooks'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
...@@ -110,24 +110,24 @@ export function UnitagBanner({ ...@@ -110,24 +110,24 @@ export function UnitagBanner({
justifyContent="space-between" justifyContent="space-between"
onPress={onPressClaimNow}> onPress={onPressClaimNow}>
<Text color="$neutral2" variant="subheading2"> <Text color="$neutral2" variant="subheading2">
<Trans i18nKey="unitags.banner.title.compact"> <Text color="$accent1" variant="buttonLabel3">
<Text color="$accent1" variant="buttonLabel3"> {t('Claim your {{unitagSuffix}} username', {
Claim your {{ unitagDomain: UNITAG_SUFFIX_NO_LEADING_DOT }} username unitagSuffix: UNITAG_SUFFIX_NO_LEADING_DOT,
</Text> })}
and build out your customizable profile. </Text>
</Trans> {t(' and build out your customizable profile.')}
</Text> </Text>
</Flex> </Flex>
) : ( ) : (
<Flex fill gap="$spacing16" justifyContent="space-between"> <Flex fill gap="$spacing16" justifyContent="space-between">
<Flex gap="$spacing4"> <Flex gap="$spacing4">
<Text variant="subheading2"> <Text variant="subheading2">
{t('unitags.banner.title.full', { {t('Claim your {{unitagSuffix}} username', {
unitagDomain: UNITAG_SUFFIX_NO_LEADING_DOT, unitagSuffix: UNITAG_SUFFIX_NO_LEADING_DOT,
})} })}
</Text> </Text>
<Text color="$neutral2" variant="body3"> <Text color="$neutral2" variant="body3">
{t('unitags.banner.subtitle')} {t('Build a personalized web3 profile and easily share your address with friends.')}
</Text> </Text>
</Flex> </Flex>
<Flex row gap="$spacing2"> <Flex row gap="$spacing2">
...@@ -140,7 +140,7 @@ export function UnitagBanner({ ...@@ -140,7 +140,7 @@ export function UnitagBanner({
testID={ElementName.Confirm} testID={ElementName.Confirm}
onPress={onPressClaimNow}> onPress={onPressClaimNow}>
<Text color="white" variant="buttonLabel4"> <Text color="white" variant="buttonLabel4">
{t('unitags.banner.button.claim')} {t('Claim now')}
</Text> </Text>
</TouchableArea> </TouchableArea>
<TouchableArea <TouchableArea
...@@ -151,7 +151,7 @@ export function UnitagBanner({ ...@@ -151,7 +151,7 @@ export function UnitagBanner({
testID={ElementName.Cancel} testID={ElementName.Cancel}
onPress={onPressMaybeLater}> onPress={onPressMaybeLater}>
<Text color="$neutral2" variant="buttonLabel4"> <Text color="$neutral2" variant="buttonLabel4">
{t('common.button.later')} {t('Maybe later')}
</Text> </Text>
</TouchableArea> </TouchableArea>
</Flex> </Flex>
......
...@@ -51,9 +51,11 @@ export function UnitagsIntroModal(): JSX.Element { ...@@ -51,9 +51,11 @@ export function UnitagsIntroModal(): JSX.Element {
<BottomSheetModal name={ModalName.UnitagsIntro} onClose={onClose}> <BottomSheetModal name={ModalName.UnitagsIntro} onClose={onClose}>
<Flex gap="$spacing24" px="$spacing24" py="$spacing16"> <Flex gap="$spacing24" px="$spacing24" py="$spacing16">
<Flex alignItems="center" gap="$spacing12"> <Flex alignItems="center" gap="$spacing12">
<Text variant="subheading1">{t('unitags.intro.title')}</Text> <Text variant="subheading1">{t('Introducing usernames')}</Text>
<Text color="$neutral2" textAlign="center" variant="body2"> <Text color="$neutral2" textAlign="center" variant="body2">
{t('unitags.intro.subtitle')} {t(
'Say goodbye to 0x addresses. Usernames are readable names that make it easier to send and receive crypto.'
)}
</Text> </Text>
</Flex> </Flex>
<Flex alignItems="center" maxHeight={105}> <Flex alignItems="center" maxHeight={105}>
...@@ -64,13 +66,13 @@ export function UnitagsIntroModal(): JSX.Element { ...@@ -64,13 +66,13 @@ export function UnitagsIntroModal(): JSX.Element {
/> />
</Flex> </Flex>
<Flex gap="$spacing16" px="$spacing20"> <Flex gap="$spacing16" px="$spacing20">
<BodyItem Icon={Icons.UserSquare} title={t('unitags.intro.features.profile')} /> <BodyItem Icon={Icons.UserSquare} title={t('Customizable profiles')} />
<BodyItem Icon={Icons.Ticket} title={t('unitags.intro.features.free')} /> <BodyItem Icon={Icons.Ticket} title={t('Free to claim')} />
<BodyItem Icon={Icons.Lightning} title={t('unitags.intro.features.ens')} /> <BodyItem Icon={Icons.Lightning} title={t('Powered by ENS subdomains')} />
</Flex> </Flex>
<Flex gap="$spacing8"> <Flex gap="$spacing8">
<Button size="medium" theme="primary" onPress={onPressClaimOneNow}> <Button size="medium" theme="primary" onPress={onPressClaimOneNow}>
{t('common.button.continue')} {t('Continue')}
</Button> </Button>
</Flex> </Flex>
<Flex $short={{ py: '$none', mx: '$spacing12' }} mx="$spacing24"> <Flex $short={{ py: '$none', mx: '$spacing12' }} mx="$spacing24">
......
import React, { useRef, useState } from 'react' import React, { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { Keyboard, TextInput } from 'react-native' import { Keyboard, TextInput } from 'react-native'
import { PasswordInput } from 'src/components/input/PasswordInput' import { PasswordInput } from 'src/components/input/PasswordInput'
import { PasswordError } from 'src/features/onboarding/PasswordError' import { PasswordError } from 'src/features/onboarding/PasswordError'
...@@ -90,9 +90,9 @@ export function CloudBackupPasswordForm({ ...@@ -90,9 +90,9 @@ export function CloudBackupPasswordForm({
let errorText = '' let errorText = ''
if (error === PasswordErrors.WeakPassword) { if (error === PasswordErrors.WeakPassword) {
errorText = t('settings.setting.backup.password.error.weak') errorText = t('Weak password')
} else if (error === PasswordErrors.PasswordsDoNotMatch) { } else if (error === PasswordErrors.PasswordsDoNotMatch) {
errorText = t('settings.setting.backup.password.error.mismatch') errorText = t('Passwords do not match')
} else if (error) { } else if (error) {
// use the upstream zxcvbn error message // use the upstream zxcvbn error message
errorText = error errorText = error
...@@ -104,11 +104,7 @@ export function CloudBackupPasswordForm({ ...@@ -104,11 +104,7 @@ export function CloudBackupPasswordForm({
<Flex gap="$spacing8"> <Flex gap="$spacing8">
<PasswordInput <PasswordInput
ref={passwordInputRef} ref={passwordInputRef}
placeholder={ placeholder={isConfirmation ? t('Confirm password') : t('Create password')}
isConfirmation
? t('settings.setting.backup.password.placeholder.confirm')
: t('settings.setting.backup.password.placeholder.create')
}
returnKeyType="next" returnKeyType="next"
value={password} value={password}
onChangeText={(newText: string): void => { onChangeText={(newText: string): void => {
...@@ -124,42 +120,29 @@ export function CloudBackupPasswordForm({ ...@@ -124,42 +120,29 @@ export function CloudBackupPasswordForm({
<Flex centered row gap="$spacing12" px="$spacing16"> <Flex centered row gap="$spacing12" px="$spacing16">
<Icons.DiamondExclamation color="$neutral2" size={iconSizes.icon20} /> <Icons.DiamondExclamation color="$neutral2" size={iconSizes.icon20} />
<Text color="$neutral2" variant="body3"> <Text color="$neutral2" variant="body3">
{t('settings.setting.backup.password.disclaimer')} {t(
'Uniswap Labs does not store your password and can’t recover it, so it’s crucial you remember it.'
)}
</Text> </Text>
</Flex> </Flex>
)} )}
</Flex> </Flex>
<Button disabled={isButtonDisabled} testID={ElementName.Next} onPress={onPressNext}> <Button disabled={isButtonDisabled} testID={ElementName.Next} onPress={onPressNext}>
{t('common.button.continue')} {t('Continue')}
</Button> </Button>
</> </>
) )
} }
function PasswordStrengthText({ strength }: { strength: PasswordStrength }): JSX.Element { function PasswordStrengthText({ strength }: { strength: PasswordStrength }): JSX.Element {
const { t } = useTranslation() const { text, color } = getPasswordStrengthTextAndColor(strength)
const { color } = getPasswordStrengthTextAndColor(strength)
const hasPassword = strength !== PasswordStrength.NONE const hasPassword = strength !== PasswordStrength.NONE
let strengthText: string = ''
switch (strength) {
case PasswordStrength.STRONG:
strengthText = t('settings.setting.backup.password.strong')
break
case PasswordStrength.MEDIUM:
strengthText = t('settings.setting.backup.password.medium')
break
case PasswordStrength.WEAK:
strengthText = t('settings.setting.backup.password.weak')
break
default:
break
}
return ( return (
<Flex centered row opacity={hasPassword ? 1 : 0} pt="$spacing12" px="$spacing8"> <Flex centered row opacity={hasPassword ? 1 : 0} pt="$spacing12" px="$spacing8">
<Text color={color} variant="body3"> <Text color={color} variant="body3">
{strengthText} <Trans>This is a {text.toLowerCase()} password</Trans>
</Text> </Text>
</Flex> </Flex>
) )
......
...@@ -19,7 +19,7 @@ import { ...@@ -19,7 +19,7 @@ import {
} from 'wallet/src/features/wallet/accounts/editAccountSaga' } from 'wallet/src/features/wallet/accounts/editAccountSaga'
import { AccountType, BackupType } from 'wallet/src/features/wallet/accounts/types' import { AccountType, BackupType } from 'wallet/src/features/wallet/accounts/types'
import { useAccount } from 'wallet/src/features/wallet/hooks' import { useAccount } from 'wallet/src/features/wallet/hooks'
import { getCloudProviderName } from 'wallet/src/utils/platform' import { isAndroid } from 'wallet/src/utils/platform'
type Props = { type Props = {
accountAddress: Address accountAddress: Address
...@@ -80,13 +80,17 @@ export function CloudBackupProcessingAnimation({ ...@@ -80,13 +80,17 @@ export function CloudBackupProcessingAnimation({
}) })
Alert.alert( Alert.alert(
t('settings.setting.backup.error.title', { cloudProviderName: getCloudProviderName() }), isAndroid ? t('Google Drive error') : t('iCloud error'),
t('settings.setting.backup.error.message.full', { isAndroid
cloudProviderName: getCloudProviderName(), ? t(
}), 'Unable to backup recovery phrase to Google Drive. Please ensure you have Google Drive enabled with available storage space and try again.'
)
: t(
'Unable to backup recovery phrase to iCloud. Please ensure you have iCloud enabled with available storage space and try again.'
),
[ [
{ {
text: t('common.button.ok'), text: t('OK'),
style: 'default', style: 'default',
onPress: onErrorPress, onPress: onErrorPress,
}, },
...@@ -114,9 +118,7 @@ export function CloudBackupProcessingAnimation({ ...@@ -114,9 +118,7 @@ export function CloudBackupProcessingAnimation({
<ActivityIndicator size="large" /> <ActivityIndicator size="large" />
</Flex> </Flex>
<Text variant="heading3"> <Text variant="heading3">
{t('settings.setting.backup.status.inProgress', { {isAndroid ? t('Backing up to Google Drive...') : t('Backing up to iCloud...')}
cloudProviderName: getCloudProviderName(),
})}
</Text> </Text>
</Flex> </Flex>
) : ( ) : (
...@@ -129,9 +131,7 @@ export function CloudBackupProcessingAnimation({ ...@@ -129,9 +131,7 @@ export function CloudBackupProcessingAnimation({
size={iconSize} size={iconSize}
/> />
<Text variant="heading3"> <Text variant="heading3">
{t('settings.setting.backup.status.complete', { {isAndroid ? t('Backed up to Google Drive') : t('Backed up to iCloud')}
cloudProviderName: getCloudProviderName(),
})}
</Text> </Text>
</Flex> </Flex>
) )
......
...@@ -7,15 +7,12 @@ import { ...@@ -7,15 +7,12 @@ import {
TransactionType, TransactionType,
} from 'wallet/src/features/transactions/types' } from 'wallet/src/features/transactions/types'
import { RootState } from 'wallet/src/state' import { RootState } from 'wallet/src/state'
import { signerMnemonicAccount } from 'wallet/src/test/fixtures' import { account, mockWalletPreloadedState } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
const account = signerMnemonicAccount()
const MOCK_DATE_PROMPTED = Date.now() const MOCK_DATE_PROMPTED = Date.now()
const state = { const state = {
...mockWalletPreloadedState(), ...mockWalletPreloadedState,
wallet: { wallet: {
appRatingProvidedMs: MOCK_DATE_PROMPTED, appRatingProvidedMs: MOCK_DATE_PROMPTED,
}, },
......
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.
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.
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.
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.
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