ci(release): publish latest release

parent ad632eb4
* @uniswap/web-admins
IPFS hash of the deployment:
- CIDv0: `QmNxB7dgMsLA6wASBs8xCSjKa4XAkeZNiiaf7uAG7KxPme`
- CIDv1: `bafybeiajdg2uit3dy2c5dbxclr7w6subg6t4fxnegfifpiskay2qlyys5u`
We are back with another (small) round of updates. Check out what is new below:
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeiajdg2uit3dy2c5dbxclr7w6subg6t4fxnegfifpiskay2qlyys5u.ipfs.dweb.link/
- https://bafybeiajdg2uit3dy2c5dbxclr7w6subg6t4fxnegfifpiskay2qlyys5u.ipfs.cf-ipfs.com/
- [ipfs://QmNxB7dgMsLA6wASBs8xCSjKa4XAkeZNiiaf7uAG7KxPme/](ipfs://QmNxB7dgMsLA6wASBs8xCSjKa4XAkeZNiiaf7uAG7KxPme/)
## 5.12.0 (2024-02-22)
### Features
* **web:** hotfix to prod for unitags launch (#6465) e22ab94
Token Details Page Improvements — We took a pass at simplifying the core flows of token detail pages. We clarified the language around contract addresses and made it simpler than ever to copy them to your clipboard. In addition, we added a quick and easy route to our ‘receive’ flow from any given token details page. Share (and send) tokens easier than ever with our app!
Other notable changes:
- Gas estimation and approval bug fix
- Updated Twitter icons to ‘X’
- Unsupported language bug fix
- Android bug fixes
web/5.12.0
\ No newline at end of file
mobile/1.21.1
\ No newline at end of file
......@@ -125,17 +125,17 @@ android {
dev {
isDefault(true)
applicationIdSuffix ".dev"
versionName "1.22"
versionName "1.21.1"
dimension "variant"
}
beta {
applicationIdSuffix ".beta"
versionName "1.22"
versionName "1.21.1"
dimension "variant"
}
prod {
dimension "variant"
versionName "1.22"
versionName "1.21.1"
}
}
......
......@@ -2450,7 +2450,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2496,7 +2496,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
......@@ -2542,7 +2542,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
......@@ -2588,7 +2588,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
......@@ -2630,7 +2630,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2673,7 +2673,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
......@@ -2716,7 +2716,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
......@@ -2759,7 +2759,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
......@@ -2795,7 +2795,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -2833,7 +2833,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3003,7 +3003,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -3047,7 +3047,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
......@@ -3143,7 +3143,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3214,7 +3214,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
......@@ -3310,7 +3310,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3381,7 +3381,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.22;
MARKETING_VERSION = 1.21.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
......@@ -78,7 +78,7 @@
"@uniswap/analytics": "1.7.0",
"@uniswap/analytics-events": "2.31.0",
"@uniswap/ethers-rs-mobile": "0.0.5",
"@uniswap/sdk-core": "4.1.2",
"@uniswap/sdk-core": "4.0.7",
"@uniswap/v3-sdk": "3.10.2",
"@walletconnect/core": "2.10.1",
"@walletconnect/react-native-compat": "2.10.1",
......
......@@ -47,7 +47,7 @@ import { useAsyncData } from 'utilities/src/react/hooks'
import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext'
import { config } from 'wallet/src/config'
import { uniswapUrls } from 'wallet/src/constants/urls'
import { initFirebaseAppCheck } from 'wallet/src/features/appCheck'
import { initFirebaseAppCheck } from 'wallet/src/features/appCheck/utils'
import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks'
import { EXPERIMENT_NAMES, FEATURE_FLAGS } from 'wallet/src/features/experiments/constants'
import { selectFavoriteTokens } from 'wallet/src/features/favorites/selectors'
......@@ -125,8 +125,6 @@ function App(): JSX.Element | null {
tier: getStatsigEnvironmentTier(),
},
api: uniswapUrls.statsigProxyUrl,
disableAutoMetricsLogging: true,
disableErrorLogging: true,
},
sdkKey: DUMMY_STATSIG_SDK_KEY,
user: deviceId ? { userID: deviceId } : {},
......
import React, { ErrorInfo, PropsWithChildren } from 'react'
import { useTranslation } from 'react-i18next'
import { Image, StyleSheet } from 'react-native'
import RNRestart from 'react-native-restart'
import { useAppDispatch } from 'src/app/hooks'
import { Button, Flex, Text } from 'ui/src'
import { DEAD_LUNI } from 'ui/src/assets'
import DeadLuni from 'ui/src/assets/graphics/dead-luni.svg'
import { logger } from 'utilities/src/logger/logger'
import { useAccounts } from 'wallet/src/features/wallet/hooks'
import { setFinishedOnboarding } from 'wallet/src/features/wallet/slice'
......@@ -62,15 +61,9 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element {
}
return (
<Flex
centered
fill
backgroundColor="$surface1"
gap="$spacing16"
px="$spacing16"
py="$spacing48">
<Flex centered fill gap="$spacing16" px="$spacing16" py="$spacing48">
<Flex centered grow gap="$spacing36">
<Image source={DEAD_LUNI} style={styles.errorImage} />
<DeadLuni />
<Flex centered gap="$spacing8">
<Text variant="subheading1">{t('Uh oh!')}</Text>
<Text variant="body2">{t('Something crashed.')}</Text>
......@@ -88,11 +81,3 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element {
</Flex>
)
}
const styles = StyleSheet.create({
errorImage: {
height: 150,
resizeMode: 'contain',
width: 150,
},
})
import { PropsWithChildren, useCallback } from 'react'
import { useAppDispatch } from 'src/app/hooks'
import { useAppStackNavigation } from 'src/app/navigation/types'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { closeModal, openModal } from 'src/features/modals/modalSlice'
import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex'
import { Screens } from 'src/screens/Screens'
......@@ -9,13 +8,11 @@ import {
NavigateToSwapFlowArgs,
WalletNavigationProvider,
} from 'wallet/src/contexts/WalletNavigationContext'
import { useFiatOnRampIpAddressQuery } from 'wallet/src/features/fiatOnRamp/api'
import { ModalName } from 'wallet/src/telemetry/constants'
export function MobileWalletNavigationProvider({ children }: PropsWithChildren): JSX.Element {
const navigateToAccountActivityList = useNavigateToHomepageTab(HomeScreenTabIndex.Activity)
const navigateToAccountTokenList = useNavigateToHomepageTab(HomeScreenTabIndex.Tokens)
const navigateToBuyOrReceiveWithEmptyWallet = useNavigateToBuyOrReceiveWithEmptyWallet()
const navigateToAccountActivityList = useNavigateToHomepageTab(HomeScreenTabIndex.Activity)
const navigateToSwapFlow = useNavigateToSwapFlow()
const navigateToTokenDetails = useNavigateToTokenDetails()
......@@ -23,7 +20,6 @@ export function MobileWalletNavigationProvider({ children }: PropsWithChildren):
<WalletNavigationProvider
navigateToAccountActivityList={navigateToAccountActivityList}
navigateToAccountTokenList={navigateToAccountTokenList}
navigateToBuyOrReceiveWithEmptyWallet={navigateToBuyOrReceiveWithEmptyWallet}
navigateToSwapFlow={navigateToSwapFlow}
navigateToTokenDetails={navigateToTokenDetails}>
{children}
......@@ -63,25 +59,3 @@ function useNavigateToTokenDetails(): (currencyId: string) => void {
[navigation]
)
}
function useNavigateToBuyOrReceiveWithEmptyWallet(): () => void {
const dispatch = useAppDispatch()
const { data } = useFiatOnRampIpAddressQuery()
const fiatOnRampEligible = Boolean(data?.isBuyAllowed)
return useCallback((): void => {
dispatch(closeModal({ name: ModalName.Send }))
if (fiatOnRampEligible) {
dispatch(openModal({ name: ModalName.FiatOnRamp }))
} else {
dispatch(
openModal({
name: ModalName.WalletConnectScan,
initialState: ScannerModalState.WalletQr,
})
)
}
}, [dispatch, fiatOnRampEligible])
}
......@@ -2,18 +2,17 @@ import React from 'react'
import { AccountSwitcherModal } from 'src/app/modals/AccountSwitcherModal'
import { ExperimentsModal } from 'src/app/modals/ExperimentsModal'
import { ExploreModal } from 'src/app/modals/ExploreModal'
import { FiatOnRampAggregatorModal } from 'src/app/modals/FiatOnRampModalAggregator'
import { SwapModal } from 'src/app/modals/SwapModal'
import { TransferTokenModal } from 'src/app/modals/TransferTokenModal'
import { ViewOnlyExplainerModal } from 'src/app/modals/ViewOnlyExplainerModal'
import { LazyModalRenderer } from 'src/app/modals/utils'
import { ViewOnlyExplainerModal } from 'src/app/modals/ViewOnlyExplainerModal'
import { ForceUpgradeModal } from 'src/components/forceUpgrade/ForceUpgradeModal'
import { RemoveWalletModal } from 'src/components/RemoveWallet/RemoveWalletModal'
import { RestoreWalletModal } from 'src/components/RestoreWalletModal/RestoreWalletModal'
import { WalletConnectModals } from 'src/components/WalletConnect/WalletConnectModals'
import { ForceUpgradeModal } from 'src/components/forceUpgrade/ForceUpgradeModal'
import { UnitagsIntroModal } from 'src/components/unitags/UnitagsIntroModal'
import { WalletConnectModals } from 'src/components/WalletConnect/WalletConnectModals'
import { LockScreenModal } from 'src/features/authentication/LockScreenModal'
import { ExchangeTransferModal } from 'src/features/fiatOnRamp/ExchangeTransferModal'
import { FiatOnRampAggregatorModal } from 'src/features/fiatOnRamp/FiatOnRampAggregatorModal'
import { FiatOnRampModal } from 'src/features/fiatOnRamp/FiatOnRampModal'
import { ScantasticModal } from 'src/features/scantastic/ScantasticModal'
import { ReceiveCryptoModal } from 'src/screens/ReceiveCryptoModal'
......@@ -24,10 +23,6 @@ import { ModalName } from 'wallet/src/telemetry/constants'
export function AppModals(): JSX.Element {
return (
<>
<LazyModalRenderer name={ModalName.ExchangeTransferModal}>
<ExchangeTransferModal />
</LazyModalRenderer>
<LazyModalRenderer name={ModalName.Experiments}>
<ExperimentsModal />
</LazyModalRenderer>
......
......@@ -269,26 +269,37 @@ exports[`AccountSwitcher renders correctly 1`] = `
</View>
</View>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={16}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="copy"
......@@ -507,27 +518,45 @@ exports[`AccountSwitcher renders correctly 1`] = `
ExpoLinearGradient
</View>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={
{
"bottom": 5,
"left": 5,
"right": 5,
"top": 5,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"marginTop": 16,
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
>
......
......@@ -33,28 +33,39 @@ exports[`AccountHeader renders without error 1`] = `
}
>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onLongPress={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={20}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"flexDirection": "row",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="manage"
......@@ -323,26 +334,37 @@ exports[`AccountHeader renders without error 1`] = `
</View>
</View>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={20}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
>
......@@ -418,27 +440,38 @@ exports[`AccountHeader renders without error 1`] = `
}
>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={20}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"flexShrink": 1,
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
>
......@@ -532,27 +565,38 @@ exports[`AccountHeader renders without error 1`] = `
</Text>
</View>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={20}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"paddingLeft": 8,
"transform": [
{
"scale": 1,
},
],
}
}
>
......
......@@ -108,31 +108,48 @@ exports[`AccountList renders without error 1`] = `
onPress={[Function]}
>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onLongPress={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={
{
"bottom": 5,
"left": 5,
"right": 5,
"top": 5,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"paddingBottom": 12,
"paddingLeft": 24,
"paddingRight": 24,
"paddingTop": 8,
"transform": [
{
"scale": 1,
},
],
}
}
>
......
......@@ -264,7 +264,7 @@ function FavoritesSection(props: FavoritesSectionProps): JSX.Element | null {
px="$spacing12"
zIndex={1}>
{hasFavoritedTokens && <FavoriteTokensGrid {...props} />}
{hasFavoritedWallets && <FavoriteWalletsGrid {...props} />}
{hasFavoritedWallets && <FavoriteWalletsGrid showLoading={props.showLoading} />}
</Flex>
)
}
......
......@@ -2,11 +2,18 @@ import { ImpactFeedbackStyle } from 'expo-haptics'
import React, { memo, useCallback } from 'react'
import { ViewProps } from 'react-native'
import ContextMenu from 'react-native-context-menu-view'
import { FadeIn, SharedValue } from 'react-native-reanimated'
import {
FadeIn,
SharedValue,
interpolate,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import { useAppDispatch } from 'src/app/hooks'
import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import RemoveButton from 'src/components/explore/RemoveButton'
import { useAnimatedCardDragStyle, useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { Loader } from 'src/components/loading'
import { disableOnPress } from 'src/utils/disableOnPress'
import { usePollOnFocusOnly } from 'src/utils/hooks'
......@@ -48,6 +55,8 @@ function FavoriteTokenCard({
const dispatch = useAppDispatch()
const tokenDetailsNavigation = useTokenDetailsNavigation()
const { convertFiatAmountFormatted } = useLocalizationContext()
const dragAnimationProgress = useSharedValue(0)
const wasTouched = useSharedValue(false)
const { data, networkStatus, startPolling, stopPolling } = useFavoriteTokenCardQuery({
variables: currencyIdToContractInput(currencyId),
......@@ -94,14 +103,45 @@ function FavoriteTokenCard({
tokenDetailsNavigation.navigate(currencyId)
}
const animatedDragStyle = useAnimatedCardDragStyle(isTouched, dragActivationProgress)
useAnimatedReaction(
() => dragActivationProgress.value,
(activationProgress, prev) => {
const prevActivationProgress = prev ?? 0
// If the activation progress is increasing (the user is touching one of the cards)
if (activationProgress > prevActivationProgress) {
if (isTouched.value) {
// If the current card is the one being touched, reset the animation progress
wasTouched.value = true
dragAnimationProgress.value = 0
} else {
// Otherwise, animate the card
wasTouched.value = false
dragAnimationProgress.value = activationProgress
}
}
// If the activation progress is decreasing (the user is no longer touching one of the cards)
else {
if (isTouched.value || wasTouched.value) {
// If the current card is the one that was being touched, reset the animation progress
dragAnimationProgress.value = 0
} else {
// Otherwise, animate the card
dragAnimationProgress.value = activationProgress
}
}
}
)
const animatedStyle = useAnimatedStyle(() => ({
opacity: interpolate(dragAnimationProgress.value, [0, 1], [1, 0.5]),
}))
if (isNonPollingRequestInFlight(networkStatus)) {
return <Loader.Favorite height={FAVORITE_TOKEN_CARD_LOADER_HEIGHT} />
}
return (
<AnimatedFlex style={animatedDragStyle}>
<AnimatedFlex style={animatedStyle}>
<ContextMenu
actions={menuActions}
disabled={isEditing}
......
import { ImpactFeedbackStyle } from 'expo-haptics'
import { memo, useCallback, useMemo } from 'react'
import { default as React, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ViewProps } from 'react-native'
import ContextMenu from 'react-native-context-menu-view'
import { SharedValue } from 'react-native-reanimated'
import { useAppDispatch } from 'src/app/hooks'
import { useEagerExternalProfileNavigation } from 'src/app/navigation/hooks'
import { useAnimatedCardDragStyle } from 'src/components/explore/hooks'
import RemoveButton from 'src/components/explore/RemoveButton'
import { disableOnPress } from 'src/utils/disableOnPress'
import { AnimatedFlex, Flex, TouchableArea } from 'ui/src'
import { Flex, TouchableArea } from 'ui/src'
import { borderRadii, iconSizes } from 'ui/src/theme'
import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon'
import { DisplayNameText } from 'wallet/src/components/accounts/DisplayNameText'
......@@ -21,16 +19,12 @@ import { DisplayNameType } from 'wallet/src/features/wallet/types'
type FavoriteWalletCardProps = {
address: Address
isEditing?: boolean
isTouched: SharedValue<boolean>
dragActivationProgress: SharedValue<number>
setIsEditing: (update: boolean) => void
} & ViewProps
function FavoriteWalletCard({
export default function FavoriteWalletCard({
address,
isEditing,
isTouched,
dragActivationProgress,
setIsEditing,
...rest
}: FavoriteWalletCardProps): JSX.Element {
......@@ -57,60 +51,51 @@ function FavoriteWalletCard({
]
}, [t])
const animatedDragStyle = useAnimatedCardDragStyle(isTouched, dragActivationProgress)
return (
<AnimatedFlex style={animatedDragStyle}>
<ContextMenu
actions={menuActions}
disabled={isEditing}
style={{ borderRadius: borderRadii.rounded16 }}
onPress={(e): void => {
// Emitted index based on order of menu action array
// remove favorite action
if (e.nativeEvent.index === 0) {
onRemove()
}
// Edit mode toggle action
if (e.nativeEvent.index === 1) {
setIsEditing(true)
}
<ContextMenu
actions={menuActions}
disabled={isEditing}
style={{ borderRadius: borderRadii.rounded16 }}
onPress={(e): void => {
// Emitted index based on order of menu action array
// remove favorite action
if (e.nativeEvent.index === 0) {
onRemove()
}
// Edit mode toggle action
if (e.nativeEvent.index === 1) {
setIsEditing(true)
}
}}
{...rest}>
<TouchableArea
hapticFeedback
borderRadius="$rounded16"
hapticStyle={ImpactFeedbackStyle.Light}
m="$spacing4"
onLongPress={disableOnPress}
onPress={(): void => {
navigate(address)
}}
{...rest}>
<TouchableArea
hapticFeedback
activeOpacity={isEditing ? 1 : undefined}
backgroundColor="$surface2"
borderRadius="$rounded16"
disabled={isEditing}
hapticStyle={ImpactFeedbackStyle.Light}
m="$spacing4"
onLongPress={disableOnPress}
onPress={(): void => {
navigate(address)
}}
onPressIn={async (): Promise<void> => {
await preload(address)
}}>
<BaseCard.Shadow>
<Flex row gap="$spacing4" justifyContent="space-between">
<Flex row shrink alignItems="center" gap="$spacing8">
{icon}
<DisplayNameText
displayName={displayName}
textProps={{
adjustsFontSizeToFit: displayName?.type === DisplayNameType.Address,
variant: 'body1',
}}
/>
</Flex>
<RemoveButton visible={isEditing} onPress={onRemove} />
onPressIn={async (): Promise<void> => {
await preload(address)
}}>
<BaseCard.Shadow>
<Flex row gap="$spacing4" justifyContent="space-between">
<Flex row shrink alignItems="center" gap="$spacing8">
{icon}
<DisplayNameText
displayName={displayName}
textProps={{
adjustsFontSizeToFit: displayName?.type === DisplayNameType.Address,
variant: 'body1',
}}
/>
</Flex>
</BaseCard.Shadow>
</TouchableArea>
</ContextMenu>
</AnimatedFlex>
<RemoveButton visible={isEditing} onPress={onRemove} />
</Flex>
</BaseCard.Shadow>
</TouchableArea>
</ContextMenu>
)
}
export default memo(FavoriteWalletCard)
import { default as React, useCallback, useEffect, useMemo, useState } from 'react'
import { default as React, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FadeIn, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'
import { FadeIn } from 'react-native-reanimated'
import { useAppSelector } from 'src/app/hooks'
import { FavoriteHeaderRow } from 'src/components/explore/FavoriteHeaderRow'
import FavoriteWalletCard from 'src/components/explore/FavoriteWalletCard'
import { Loader } from 'src/components/loading'
import {
AutoScrollProps,
SortableGrid,
SortableGridChangeEvent,
SortableGridRenderItem,
} from 'src/components/sortableGrid'
import { AnimatedFlex, Flex } from 'ui/src'
import { selectWatchedAddressSet } from 'wallet/src/features/favorites/selectors'
import { setFavoriteWallets } from 'wallet/src/features/favorites/slice'
import { useAppDispatch } from 'wallet/src/state'
const NUM_COLUMNS = 2
const ITEM_FLEX = { flex: 1 / NUM_COLUMNS }
type FavoriteWalletsGridProps = AutoScrollProps & {
showLoading: boolean
}
const HALF_WIDTH = { width: '50%' }
/** Renders the favorite wallets section on the Explore tab */
export function FavoriteWalletsGrid({
showLoading,
...rest
}: FavoriteWalletsGridProps): JSX.Element {
export function FavoriteWalletsGrid({ showLoading }: { showLoading: boolean }): JSX.Element {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const [isEditing, setIsEditing] = useState(false)
const isTokenDragged = useSharedValue(false)
const watchedWalletsSet = useAppSelector(selectWatchedAddressSet)
const watchedWalletsList = useMemo(() => Array.from(watchedWalletsSet), [watchedWalletsSet])
......@@ -43,33 +27,8 @@ export function FavoriteWalletsGrid({
}
}, [watchedWalletsSet.size])
const handleOrderChange = useCallback(
({ data }: SortableGridChangeEvent<string>) => {
dispatch(setFavoriteWallets({ addresses: data }))
},
[dispatch]
)
const renderItem = useCallback<SortableGridRenderItem<string>>(
({ item: address, isTouched, dragActivationProgress }): JSX.Element => (
<FavoriteWalletCard
key={address}
address={address}
dragActivationProgress={dragActivationProgress}
isEditing={isEditing}
isTouched={isTouched}
setIsEditing={setIsEditing}
/>
),
[isEditing]
)
const animatedStyle = useAnimatedStyle(() => ({
zIndex: isTokenDragged.value ? 1 : 0,
}))
return (
<AnimatedFlex entering={FadeIn} style={animatedStyle}>
<AnimatedFlex entering={FadeIn}>
<FavoriteHeaderRow
editingTitle={t('Edit favorite wallets')}
isEditing={isEditing}
......@@ -79,21 +38,17 @@ export function FavoriteWalletsGrid({
{showLoading ? (
<FavoriteWalletsGridLoader />
) : (
<SortableGrid
{...rest}
activeItemOpacity={1}
data={watchedWalletsList}
editable={isEditing}
numColumns={NUM_COLUMNS}
renderItem={renderItem}
onChange={handleOrderChange}
onDragEnd={(): void => {
isTokenDragged.value = false
}}
onDragStart={(): void => {
isTokenDragged.value = true
}}
/>
<Flex row flexWrap="wrap">
{watchedWalletsList.map((address) => (
<FavoriteWalletCard
key={address}
address={address}
isEditing={isEditing}
setIsEditing={setIsEditing}
style={HALF_WIDTH}
/>
))}
</Flex>
)}
</AnimatedFlex>
)
......
import { SharedEventName } from '@uniswap/analytics-events'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { NativeSyntheticEvent, Share, ViewStyle } from 'react-native'
import { NativeSyntheticEvent, Share } from 'react-native'
import { ContextMenuAction, ContextMenuOnPressNativeEvent } from 'react-native-context-menu-view'
import {
AnimateStyle,
SharedValue,
interpolate,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { useSelectHasTokenFavorited, useToggleFavoriteCallback } from 'src/features/favorites/hooks'
import { openModal } from 'src/features/modals/modalSlice'
......@@ -156,44 +148,3 @@ export function useExploreTokenContextMenu({
return { menuActions, onContextMenuPress }
}
export function useAnimatedCardDragStyle(
isTouched: SharedValue<boolean>,
dragActivationProgress: SharedValue<number>
): AnimateStyle<ViewStyle> {
const wasTouched = useSharedValue(false)
const dragAnimationProgress = useSharedValue(0)
useAnimatedReaction(
() => dragActivationProgress.value,
(activationProgress, prev) => {
const prevActivationProgress = prev ?? 0
// If the activation progress is increasing (the user is touching one of the cards)
if (activationProgress > prevActivationProgress) {
if (isTouched.value) {
// If the current card is the one being touched, reset the animation progress
wasTouched.value = true
dragAnimationProgress.value = 0
} else {
// Otherwise, animate the card
wasTouched.value = false
dragAnimationProgress.value = activationProgress
}
}
// If the activation progress is decreasing (the user is no longer touching one of the cards)
else {
if (isTouched.value || wasTouched.value) {
// If the current card is the one that was being touched, reset the animation progress
dragAnimationProgress.value = 0
} else {
// Otherwise, animate the card
dragAnimationProgress.value = activationProgress
}
}
}
)
return useAnimatedStyle(() => ({
opacity: interpolate(dragAnimationProgress.value, [0, 1], [1, 0.5]),
}))
}
......@@ -468,27 +468,44 @@ exports[`SearchPopularTokens renders without error 2`] = `
onPress={[Function]}
>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onLongPress={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={
{
"bottom": 5,
"left": 5,
"right": 5,
"top": 5,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="search-token-item"
......@@ -650,27 +667,44 @@ exports[`SearchPopularTokens renders without error 2`] = `
onPress={[Function]}
>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onLongPress={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={
{
"bottom": 5,
"left": 5,
"right": 5,
"top": 5,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="search-token-item"
......
......@@ -2,10 +2,10 @@ import { Currency } from '@uniswap/sdk-core'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import { useFiatOnRampLogoUrl } from 'src/components/fiatOnRamp/hooks'
import { Loader } from 'src/components/loading'
import { useFormatExactCurrencyAmount } from 'src/features/fiatOnRamp/hooks'
import { getServiceProviderLogo } from 'src/features/fiatOnRamp/utils'
import { Flex, Icons, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import { Flex, Icons, Text, TouchableArea } from 'ui/src'
import { fonts, iconSizes } from 'ui/src/theme'
import { FiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks'
import { FORQuote, FORServiceProvider } from 'wallet/src/features/fiatOnRamp/types'
......@@ -13,12 +13,6 @@ import { ImageUri } from 'wallet/src/features/images/ImageUri'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { getSymbolDisplayText } from 'wallet/src/utils/currency'
function LogoLoader(): JSX.Element {
return (
<Loader.Box borderRadius="$roundedFull" height={iconSizes.icon40} width={iconSizes.icon40} />
)
}
export function FORQuoteItem({
quote,
serviceProvider,
......@@ -52,8 +46,7 @@ export function FORQuoteItem({
currencySymbol: baseCurrency.symbol,
})
const isDarkMode = useIsDarkMode()
const logoUrl = getServiceProviderLogo(serviceProvider?.logos, isDarkMode)
const logoUrl = useFiatOnRampLogoUrl(serviceProvider?.logos)
return (
<TouchableArea onPress={onPress}>
......@@ -69,17 +62,11 @@ export function FORQuoteItem({
<QuoteLoader showCarret={showCarret} />
) : (
<Flex row alignItems="center" gap="$spacing12">
<Flex>
{logoUrl ? (
<ImageUri
fallback={<LogoLoader />}
imageStyle={ServiceProviderLogoStyles.icon}
uri={logoUrl}
/>
) : (
<LogoLoader />
)}
</Flex>
<Loader.Box
borderRadius="$roundedFull"
height={iconSizes.icon40}
width={iconSizes.icon40}
/>
<Flex shrink gap="$spacing4">
<Text color="$neutral1" variant="subheading2">
{serviceProvider?.name}
......@@ -109,6 +96,23 @@ export function FORQuoteItem({
<Flex />
)}
</Flex>
{
// TODO: Enable once https://linear.app/uniswap/issue/MOB-2565/implement-service-providers-logo-once-meld-has-added-them-on-their is unblocked
false && logoUrl && (
<ImageUri
fallback={
<Loader.Box
borderRadius="$roundedFull"
height={iconSizes.icon40}
width={iconSizes.icon40}
/>
}
imageStyle={ServiceProviderLogoStyles.icon}
resizeMode="contain"
uri={logoUrl}
/>
)
}
</Flex>
)}
</Flex>
......
import { useIsDarkMode } from 'ui/src'
import { FORLogo } from 'wallet/src/features/fiatOnRamp/types'
export function useFiatOnRampLogoUrl(logos: FORLogo | undefined): string | undefined {
const isDarkMode = useIsDarkMode()
if (!logos) {
return
}
return isDarkMode ? logos.darkLogo : logos.lightLogo
}
import React from 'react'
import { WalletLoader } from 'src/components/loading/WalletLoader'
import { render } from 'src/test/test-utils'
it('renders wallet loader', () => {
const tree = render(<WalletLoader opacity={1} />)
expect(tree).toMatchSnapshot()
})
import { Flex } from 'ui/src/components/layout/Flex'
import { Text } from 'ui/src/components/text/Text'
import React from 'react'
import { ADDRESS_WRAPPER_HEIGHT } from 'src/features/import/WalletPreviewCard'
import { Flex, Text } from 'ui/src'
interface Props {
opacity: number
}
export const ADDRESS_WRAPPER_HEIGHT = 36
export function WalletLoader({ opacity }: Props): JSX.Element {
return (
<Flex
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders wallet loader 1`] = `
<View
sentry-label="WalletLoader"
style={
{
"alignItems": "center",
"borderBottomColor": "#CECECE",
"borderBottomLeftRadius": 20,
"borderBottomRightRadius": 20,
"borderBottomWidth": 1,
"borderLeftColor": "#CECECE",
"borderLeftWidth": 1,
"borderRightColor": "#CECECE",
"borderRightWidth": 1,
"borderStyle": "solid",
"borderTopColor": "#CECECE",
"borderTopLeftRadius": 20,
"borderTopRightRadius": 20,
"borderTopWidth": 1,
"flexDirection": "row",
"justifyContent": "flex-start",
"opacity": 1,
"overflow": "hidden",
"paddingBottom": 16,
"paddingLeft": 16,
"paddingRight": 16,
"paddingTop": 16,
}
}
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 12,
"height": 36,
}
}
>
<View
style={
{
"backgroundColor": "#CECECE",
"borderBottomLeftRadius": 999999,
"borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999,
"borderTopRightRadius": 999999,
"flexDirection": "column",
"height": 32,
"width": 32,
}
}
/>
<View
style={
{
"alignItems": "flex-start",
"flexDirection": "column",
"width": "100%",
}
}
>
<View
onLayout={[Function]}
style={
{
"flexDirection": "column",
"opacity": 0,
}
}
testID="shimmer-placeholder"
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
}
}
testID="text-placeholder"
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"position": "relative",
}
}
>
<View
accessibilityElementsHidden={true}
importantForAccessibility="no-hide-descendants"
>
<Text
allowFontScaling={true}
maxFontSizeMultiplier={1.4}
style={
{
"color": "transparent",
"fontFamily": "Basel-Book",
"fontSize": 19,
"lineHeight": 24,
"opacity": 0,
}
}
suppressHighlighting={true}
>
Wallet Nickname
</Text>
</View>
<View
style={
{
"backgroundColor": "#F9F9F9",
"borderBottomLeftRadius": 4,
"borderBottomRightRadius": 4,
"borderTopLeftRadius": 4,
"borderTopRightRadius": 4,
"bottom": "5%",
"flexDirection": "column",
"left": 0,
"position": "absolute",
"right": 0,
"top": "5%",
}
}
/>
</View>
</View>
</View>
<View
onLayout={[Function]}
style={
{
"flexDirection": "column",
"opacity": 0,
}
}
testID="shimmer-placeholder"
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
}
}
testID="text-placeholder"
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"position": "relative",
}
}
>
<View
accessibilityElementsHidden={true}
importantForAccessibility="no-hide-descendants"
>
<Text
allowFontScaling={true}
maxFontSizeMultiplier={1.4}
style={
{
"color": "transparent",
"fontFamily": "Basel-Book",
"fontSize": 17,
"lineHeight": 24,
"opacity": 0,
}
}
suppressHighlighting={true}
>
0xaaaa...aaaa
</Text>
</View>
<View
style={
{
"backgroundColor": "#F9F9F9",
"borderBottomLeftRadius": 4,
"borderBottomRightRadius": 4,
"borderTopLeftRadius": 4,
"borderTopRightRadius": 4,
"bottom": "5%",
"flexDirection": "column",
"left": 0,
"position": "absolute",
"right": 0,
"top": "5%",
}
}
/>
</View>
</View>
</View>
</View>
</View>
</View>
`;
import React, { memo } from 'react'
import { TransactionLoader } from 'src/components/loading/TransactionLoader'
import { WalletLoader } from 'src/components/loading/WalletLoader'
import { WaveLoader } from 'src/components/loading/WaveLoader'
import { Flex, FlexLoader, FlexLoaderProps, getToken, Skeleton } from 'ui/src'
......@@ -11,6 +12,20 @@ function Graph(): JSX.Element {
)
}
function Wallets({ repeat = 1 }: { repeat?: number }): JSX.Element {
return (
<Skeleton>
<Flex gap="$spacing12">
{new Array(repeat).fill(null).map((_, i, { length }) => (
<React.Fragment key={i}>
<WalletLoader opacity={(length - i) / length} />
</React.Fragment>
))}
</Flex>
</Skeleton>
)
}
export const Transaction = memo(function _Transaction({
repeat = 1,
}: {
......@@ -57,6 +72,7 @@ function Favorite({ height, contrast }: { height?: number; contrast?: boolean })
export const Loader = {
Box,
Transaction,
Wallets,
Graph,
Image,
Favorite,
......
......@@ -30,9 +30,6 @@ export function useAnimatedZIndex(renderIndex: number): SharedValue<number> {
previousActiveIndex: previousActiveIndexValue.value,
}),
({ touchedIndex, previousActiveIndex }) => {
if (touchedIndex === null) {
return null
}
if (renderIndex === touchedIndex) {
// Display the currently touched item on top of all other items
zIndexValue.value = 10000
......
import React, { useRef, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import { Keyboard, TextInput } from 'react-native'
import { PasswordInput } from 'src/components/input/PasswordInput'
import { PasswordError } from 'src/features/onboarding/PasswordError'
import { Button, Flex, Icons, Text } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { useDebounce } from 'utilities/src/time/timing'
import { ElementName } from 'wallet/src/telemetry/constants'
import {
PASSWORD_VALIDATION_DEBOUNCE_MS,
PasswordStrength,
getPasswordStrength,
getPasswordStrengthTextAndColor,
isPasswordStrongEnough,
} from 'wallet/src/utils/password'
import { validatePassword } from 'wallet/src/utils/password'
export enum PasswordErrors {
WeakPassword = 'WeakPassword',
......@@ -36,17 +29,9 @@ export function CloudBackupPasswordForm({
const passwordInputRef = useRef<TextInput>(null)
const [password, setPassword] = useState('')
const [error, setError] = useState<PasswordErrors | undefined>(undefined)
const [error, setError] = useState<PasswordErrors | string | undefined>(undefined)
const [passwordStrength, setPasswordStrength] = useState(PasswordStrength.NONE)
const debouncedPasswordStrength = useDebounce(passwordStrength, PASSWORD_VALIDATION_DEBOUNCE_MS)
const isStrongPassword = isPasswordStrongEnough({
minStrength: PasswordStrength.MEDIUM,
currentStrength: passwordStrength,
})
const isButtonDisabled =
!!error || password.length === 0 || (!isConfirmation && !isStrongPassword)
const isButtonDisabled = !!error || password.length === 0
const onPasswordChangeText = (newPassword: string): void => {
if (isConfirmation && newPassword === password) {
......@@ -54,15 +39,15 @@ export function CloudBackupPasswordForm({
}
// always reset error if not confirmation
if (!isConfirmation) {
setPasswordStrength(getPasswordStrength(newPassword))
setError(undefined)
}
setPassword(newPassword)
}
const onPasswordSubmitEditing = (): void => {
if (!isConfirmation && !isStrongPassword) {
setError(PasswordErrors.WeakPassword)
const { valid, validationErrorString } = validatePassword(password)
if (!isConfirmation && !valid) {
setError(validationErrorString || PasswordErrors.WeakPassword)
return
}
if (isConfirmation && passwordToConfirm !== password) {
......@@ -74,8 +59,9 @@ export function CloudBackupPasswordForm({
}
const onPressNext = (): void => {
if (!isConfirmation && !isStrongPassword) {
setError(PasswordErrors.WeakPassword)
const { valid, validationErrorString } = validatePassword(password)
if (!isConfirmation && !valid) {
setError(validationErrorString || PasswordErrors.WeakPassword)
return
}
if (isConfirmation && passwordToConfirm !== password) {
......@@ -113,7 +99,6 @@ export function CloudBackupPasswordForm({
}}
onSubmitEditing={onPasswordSubmitEditing}
/>
{!isConfirmation && <PasswordStrengthText strength={debouncedPasswordStrength} />}
{error ? <PasswordError errorText={errorText} /> : null}
</Flex>
{!isConfirmation && (
......@@ -133,17 +118,3 @@ export function CloudBackupPasswordForm({
</>
)
}
function PasswordStrengthText({ strength }: { strength: PasswordStrength }): JSX.Element {
const { text, color } = getPasswordStrengthTextAndColor(strength)
const hasPassword = strength !== PasswordStrength.NONE
return (
<Flex centered row opacity={hasPassword ? 1 : 0} pt="$spacing12" px="$spacing8">
<Text color={color} variant="body3">
<Trans>This is a {text.toLowerCase()} password</Trans>
</Text>
</Flex>
)
}
import { useAppDispatch, useAppSelector } from 'src/app/hooks'
import { closeModal } from 'src/features/modals/modalSlice'
import { selectModalState } from 'src/features/modals/selectModalState'
import { ExchangeTransferConnecting } from 'src/screens/ExchangeTransferConnecting'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ModalName } from 'wallet/src/telemetry/constants'
export function ExchangeTransferModal(): JSX.Element | null {
const dispatch = useAppDispatch()
const onClose = (): void => {
dispatch(closeModal({ name: ModalName.ExchangeTransferModal }))
}
const { initialState } = useAppSelector(selectModalState(ModalName.ExchangeTransferModal))
const serviceProvider = initialState?.serviceProvider
return serviceProvider ? (
<BottomSheetModal
fullScreen
hideHandlebar
hideKeyboardOnDismiss
renderBehindTopInset
name={ModalName.ExchangeTransferModal}
onClose={onClose}>
<ExchangeTransferConnecting serviceProvider={serviceProvider} onClose={onClose} />
</BottomSheetModal>
) : null
}
import { FORTransferInstitution } from 'wallet/src/features/fiatOnRamp/types'
export interface ExchangeTransferModalState {
serviceProvider: FORTransferInstitution
}
......@@ -11,7 +11,6 @@ import {
import { iconSizes } from 'ui/src/theme'
export const SERVICE_PROVIDER_ICON_SIZE = 90
export const SERVICE_PROVIDER_ICON_BORDER_RADIUS = 20
export function FiatOnRampConnectingView({
amount,
......@@ -19,7 +18,7 @@ export function FiatOnRampConnectingView({
serviceProviderName,
serviceProviderLogo,
}: {
amount?: string
amount: string
quoteCurrencyCode?: string
serviceProviderName: string
serviceProviderLogo?: JSX.Element
......@@ -49,7 +48,7 @@ export function FiatOnRampConnectingView({
<Text variant="subheading1">
{t('Connecting you to {{serviceProvider}}', { serviceProvider: serviceProviderName })}
</Text>
{quoteCurrencyCode && amount && (
{quoteCurrencyCode && (
<Text color="$neutral2" variant="body2">
{t('Buying {{amount}} worth of {{quoteCurrencyCode}}', {
amount,
......@@ -74,7 +73,7 @@ const styles = StyleSheet.create({
},
uniswapLogoWrapper: {
backgroundColor: '#FFEFF8', // #FFD8EF with 40% opacity on a white background
borderRadius: SERVICE_PROVIDER_ICON_BORDER_RADIUS,
borderRadius: 20,
height: SERVICE_PROVIDER_ICON_SIZE,
width: SERVICE_PROVIDER_ICON_SIZE,
},
......
......@@ -5,14 +5,11 @@ import { useTranslation } from 'react-i18next'
import { ListRenderItemInfo } from 'react-native'
import { getCountry } from 'react-native-localize'
import { FadeIn, FadeOut } from 'react-native-reanimated'
import { useAppDispatch } from 'src/app/hooks'
import { openModal } from 'src/features/modals/modalSlice'
import { AnimatedFlex, Flex, Loader, Text, TouchableArea } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { useFiatOnRampAggregatorTransferInstitutionsQuery } from 'wallet/src/features/fiatOnRamp/api'
import { FORTransferInstitution } from 'wallet/src/features/fiatOnRamp/types'
import { RemoteImage } from 'wallet/src/features/images/RemoteImage'
import { ModalName } from 'wallet/src/telemetry/constants'
function key(item: FORTransferInstitution): string {
return item.id as string
......@@ -64,24 +61,15 @@ function CEXItemWrapper({
)
}
export function TransferInstitutionSelector({ onClose }: { onClose: () => void }): JSX.Element {
const dispatch = useAppDispatch()
export function TransferInstitutionSelector(): JSX.Element {
const { data, isLoading } = useFiatOnRampAggregatorTransferInstitutionsQuery({
countryCode: getCountry(),
})
const onSelectTransferInstitution = useCallback(
(transferInstitution: FORTransferInstitution) => {
dispatch(
openModal({
name: ModalName.ExchangeTransferModal,
initialState: { serviceProvider: transferInstitution },
})
)
onClose()
},
[dispatch, onClose]
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onSelectTransferInstitution = useCallback((transferInstitution: FORTransferInstitution) => {
//TODO(MOB-2603): fetch widget and launch transfer flow
}, [])
const renderItem = useCallback(
({ item: institution }: ListRenderItemInfo<FORTransferInstitution>) => (
......
......@@ -19,7 +19,6 @@ import {
isInvalidRequestAmountTooLow,
} from 'wallet/src/features/fiatOnRamp/utils'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { useActiveAccountAddress } from 'wallet/src/features/wallet/hooks'
// TODO: https://linear.app/uniswap/issue/MOB-2532/implement-fetching-of-available-fiat-currencies-from-meld
const MELD_FIAT_CURRENCY_CODES = ['usd', 'eur']
......@@ -61,7 +60,6 @@ export function useFiatOnRampQuotes({
quotes: FORQuote[] | undefined
} {
const debouncedBaseCurrencyAmount = useDebounce(baseCurrencyAmount, Delay.Short)
const walletAddress = useActiveAccountAddress()
const {
currentData: quotesResponse,
......@@ -74,7 +72,6 @@ export function useFiatOnRampQuotes({
sourceCurrencyCode: baseCurrencyCode,
destinationCurrencyCode: quoteCurrencyCode,
countryCode,
walletAddress: walletAddress ?? '',
}
: skipToken,
{
......
import { FORLogo, FORQuote, FORServiceProvider } from 'wallet/src/features/fiatOnRamp/types'
import { FORQuote, FORServiceProvider } from 'wallet/src/features/fiatOnRamp/types'
export function getServiceProviderForQuote(
quote: FORQuote | undefined,
......@@ -6,14 +6,3 @@ export function getServiceProviderForQuote(
): FORServiceProvider | undefined {
return serviceProviders?.find((sp) => sp.serviceProvider === quote?.serviceProvider)
}
export function getServiceProviderLogo(
logos: FORLogo | undefined,
isDarkMode: boolean
): string | undefined {
if (!logos) {
return
}
return isDarkMode ? logos.darkLogo : logos.lightLogo
}
......@@ -5,7 +5,7 @@ import {
TextInput as NativeTextInput,
TextInputContentSizeChangeEventData,
} from 'react-native'
import { ColorTokens, Flex } from 'ui/src'
import { ColorTokens, Flex, useSporeColors } from 'ui/src'
import { spacing } from 'ui/src/theme'
import { TextInput } from 'wallet/src/components/input/TextInput'
import { isAndroid } from 'wallet/src/utils/platform'
......@@ -56,6 +56,7 @@ function Inputs({
layerType,
...inputProps
}: Props & { layerType?: 'foreground' | 'background' }): JSX.Element {
const colors = useSporeColors()
const [isMultiline, setIsMultiline] = useState(false)
const handleContentSizeChange = useCallback(
......@@ -119,7 +120,7 @@ function Inputs({
py="$none"
returnKeyType="done"
scrollEnabled={false}
selectionColor="$neutral1"
selectionColor={colors.neutral1.get()}
spellCheck={false}
testID="import_account_form/input"
textAlign={isInputEmpty ? 'left' : backgroundTextAlignment}
......
import React from 'react'
import WalletPreviewCard from 'src/features/import/WalletPreviewCard'
import { render } from 'src/test/test-utils'
import { SAMPLE_SEED_ADDRESS_1 } from 'wallet/src/test/fixtures'
import { render } from 'wallet/src/test/test-utils'
import WalletPreviewCard from './WalletPreviewCard'
it('renders wallet preview card', () => {
const tree = render(
......
import React from 'react'
import { Flex, Icons, Text, TouchableArea, Unicon, UniconV2 } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { NumberType } from 'utilities/src/format/types'
......
......@@ -158,17 +158,41 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
}
>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={
{
"bottom": 5,
"left": 5,
"right": 5,
"top": 5,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"backgroundColor": "#FFEFFF",
......@@ -176,17 +200,12 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
"borderBottomRightRadius": 12,
"borderTopLeftRadius": 12,
"borderTopRightRadius": 12,
"flexDirection": "column",
"onPressIn": undefined,
"opacity": 1,
"paddingBottom": 8,
"paddingLeft": 8,
"paddingRight": 8,
"paddingTop": 8,
"transform": [
{
"scale": 1,
},
],
}
}
>
......
......@@ -3,7 +3,6 @@ import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { RemoveWalletModalState } from 'src/components/RemoveWallet/RemoveWalletModalState'
import { ScantasticModalState } from 'src/features/scantastic/ScantasticModalState'
import { Screens } from 'src/screens/Screens'
import { FORTransferInstitution } from 'wallet/src/features/fiatOnRamp/types'
import { TransactionState } from 'wallet/src/features/transactions/transactionState/types'
import { ModalName } from 'wallet/src/telemetry/constants'
......@@ -14,9 +13,6 @@ export interface AppModalState<T> {
export interface ModalsState {
[ModalName.AccountSwitcher]: AppModalState<undefined>
[ModalName.ExchangeTransferModal]: AppModalState<{
serviceProvider: FORTransferInstitution
}>
[ModalName.Experiments]: AppModalState<undefined>
[ModalName.Explore]: AppModalState<ExploreModalState>
[ModalName.FiatCurrencySelector]: AppModalState<undefined>
......
......@@ -2,7 +2,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { ExploreModalState } from 'src/app/modals/ExploreModalState'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { RemoveWalletModalState } from 'src/components/RemoveWallet/RemoveWalletModalState'
import { ExchangeTransferModalState } from 'src/features/fiatOnRamp/ExchangeTransferModalState'
import { ScantasticModalState } from 'src/features/scantastic/ScantasticModalState'
import { Screens } from 'src/screens/Screens'
import { getKeys } from 'utilities/src/primitives/objects'
......@@ -15,11 +14,6 @@ type AccountSwitcherModalParams = {
initialState?: undefined
}
type ExchangeTransferModalParams = {
name: typeof ModalName.ExchangeTransferModal
initialState?: ExchangeTransferModalState
}
type ExperimentsModalParams = { name: typeof ModalName.Experiments; initialState?: undefined }
type ExploreModalParams = {
......@@ -82,7 +76,6 @@ type ViewOnlyExplainerParams = {
export type OpenModalParams =
| AccountSwitcherModalParams
| ExchangeTransferModalParams
| ExperimentsModalParams
| ExploreModalParams
| FiatCurrencySelectorParams
......@@ -102,10 +95,6 @@ export type OpenModalParams =
export type CloseModalParams = { name: keyof ModalsState }
export const initialModalState: ModalsState = {
[ModalName.ExchangeTransferModal]: {
isOpen: false,
initialState: undefined,
},
[ModalName.FiatOnRamp]: {
isOpen: false,
initialState: undefined,
......
......@@ -2,27 +2,44 @@
exports[`renders collection preview card 1`] = `
<View
cancelable={true}
disabled={false}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={
{
"bottom": 5,
"left": 5,
"right": 5,
"top": 5,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
>
......
import { useCallback } from 'react'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { closeModal, openModal } from 'src/features/modals/modalSlice'
import { useFiatOnRampIpAddressQuery } from 'wallet/src/features/fiatOnRamp/api'
import { useAppDispatch } from 'wallet/src/state'
import { ModalName } from 'wallet/src/telemetry/constants'
export function useOnSendEmptyActionPress(): () => void {
const { data } = useFiatOnRampIpAddressQuery()
const dispatch = useAppDispatch()
const fiatOnRampEligible = Boolean(data?.isBuyAllowed)
return useCallback((): void => {
dispatch(closeModal({ name: ModalName.Send }))
if (fiatOnRampEligible) {
dispatch(openModal({ name: ModalName.FiatOnRamp }))
} else {
dispatch(
openModal({
name: ModalName.WalletConnectScan,
initialState: ScannerModalState.WalletQr,
})
)
}
}, [dispatch, fiatOnRampEligible])
}
......@@ -8,6 +8,7 @@ import { RecipientSelect } from 'src/components/RecipientSelect/RecipientSelect'
import Trace from 'src/components/Trace/Trace'
import { Screen } from 'src/components/layout/Screen'
import { useBiometricAppSettings, useBiometricPrompt } from 'src/features/biometrics/hooks'
import { useOnSendEmptyActionPress } from 'src/features/transactions/hooks/useOnSendEmptyActionPress'
import { TransferHeader } from 'src/features/transactions/transfer/TransferHeader'
import { TransferStatus } from 'src/features/transactions/transfer/TransferStatus'
import { useWalletRestore } from 'src/features/wallet/hooks'
......@@ -106,6 +107,7 @@ export function TransferFlow({ prefilledState, onClose }: TransferFormProps): JS
dispatch,
TokenSelectorFlow.Transfer
)
const onSendEmptyActionPress = useOnSendEmptyActionPress()
// optimization for not rendering InnerContent initially,
// when modal is opened with recipient or token selector presented
......@@ -209,6 +211,7 @@ export function TransferFlow({ prefilledState, onClose }: TransferFormProps): JS
variation={TokenSelectorVariation.BalancesOnly}
onClose={onHideTokenSelector}
onSelectCurrency={onSelectCurrency}
onSendEmptyActionPress={onSendEmptyActionPress}
/>
)}
</>
......
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { getCountry } from 'react-native-localize'
import { useAppDispatch } from 'src/app/hooks'
import {
FiatOnRampConnectingView,
SERVICE_PROVIDER_ICON_BORDER_RADIUS,
SERVICE_PROVIDER_ICON_SIZE,
} from 'src/features/fiatOnRamp/FiatOnRampConnecting'
import { useFiatOnRampTransactionCreator } from 'src/features/fiatOnRamp/hooks'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { useTimeout } from 'utilities/src/time/timing'
import { useFiatOnRampAggregatorTransferWidgetQuery } from 'wallet/src/features/fiatOnRamp/api'
import { FORTransferInstitution } from 'wallet/src/features/fiatOnRamp/types'
import { RemoteImage } from 'wallet/src/features/images/RemoteImage'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
import { openUri } from 'wallet/src/utils/linking'
// Design decision
const CONNECTING_TIMEOUT = 2 * ONE_SECOND_MS
const DEFAULT_TRANSFER_AMOUNT = 1
const DEFAULT_TRANSFER_CURRENCY = 'ETH'
export function ExchangeTransferConnecting({
serviceProvider,
onClose,
}: {
serviceProvider: FORTransferInstitution
onClose: () => void
}): JSX.Element {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const activeAccountAddress = useActiveAccountAddressWithThrow()
const [timeoutElapsed, setTimeoutElapsed] = useState(false)
const { externalTransactionId, dispatchAddTransaction } =
useFiatOnRampTransactionCreator(activeAccountAddress)
const onError = useCallback((): void => {
dispatch(
pushNotification({
type: AppNotificationType.Error,
errorMessage: t('Something went wrong.'),
})
)
onClose()
}, [dispatch, onClose, t])
useTimeout(() => {
setTimeoutElapsed(true)
}, CONNECTING_TIMEOUT)
const {
data: widgetData,
isLoading: widgetLoading,
error: widgetError,
} = useFiatOnRampAggregatorTransferWidgetQuery({
sourceAmount: DEFAULT_TRANSFER_AMOUNT,
sourceCurrencyCode: DEFAULT_TRANSFER_CURRENCY,
countryCode: getCountry(),
institutionId: serviceProvider.id,
walletAddress: activeAccountAddress,
externalSessionId: externalTransactionId,
})
useEffect(() => {
if (widgetError) {
onError()
return
}
if (timeoutElapsed && !widgetLoading && widgetData) {
onClose()
openUri(widgetData.widgetUrl).catch(onError)
// TODO: Uncomment this when https://linear.app/uniswap/issue/MOB-2585/implement-polling-of-transaction-once-user-has-checked-out is implemented
// dispatchAddTransaction()
}
}, [
dispatchAddTransaction,
onClose,
onError,
timeoutElapsed,
widgetData,
widgetLoading,
widgetError,
])
return (
<FiatOnRampConnectingView
serviceProviderLogo={
<RemoteImage
borderRadius={SERVICE_PROVIDER_ICON_BORDER_RADIUS}
height={SERVICE_PROVIDER_ICON_SIZE}
uri={serviceProvider.icon}
width={SERVICE_PROVIDER_ICON_SIZE}
/>
}
serviceProviderName={serviceProvider.name}
/>
)
}
......@@ -50,7 +50,7 @@ export function ExploreScreen(): JSX.Element {
const [isSearchMode, setIsSearchMode] = useState<boolean>(false)
const textInputRef = useRef<TextInput>(null)
const onSearchChangeText = (newSearchFilter: string): void => {
const onChangeSearchFilter = (newSearchFilter: string): void => {
setSearchQuery(newSearchFilter)
}
......@@ -83,8 +83,9 @@ export function ExploreScreen(): JSX.Element {
backgroundColor={isSearchMode ? contrastBackgroundColor : searchBarBackgroundColor}
placeholder={t('Search tokens and wallets')}
showShadow={!isSearchMode}
value={searchQuery}
onCancel={onSearchCancel}
onChangeText={onSearchChangeText}
onChangeText={onChangeSearchFilter}
onFocus={onSearchFocus}
/>
</Flex>
......
......@@ -2,7 +2,6 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React, { ComponentProps, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { TextInput, TextInputProps } from 'react-native'
import FastImage from 'react-native-fast-image'
import { FadeIn, FadeOut, FadeOutDown } from 'react-native-reanimated'
import { useAppDispatch, useShouldShowNativeKeyboard } from 'src/app/hooks'
import { FiatOnRampStackParamList } from 'src/app/navigation/types'
......@@ -20,18 +19,17 @@ import { FiatOnRampCountryPicker } from 'src/features/fiatOnRamp/FiatOnRampCount
import { FiatOnRampTokenSelectorModal } from 'src/features/fiatOnRamp/FiatOnRampTokenSelector'
import { useFiatOnRampSupportedTokens } from 'src/features/fiatOnRamp/hooks'
import { FiatOnRampCurrency, InitialQuoteSelection } from 'src/features/fiatOnRamp/types'
import { getServiceProviderLogo } from 'src/features/fiatOnRamp/utils'
import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants'
import { MobileEventProperties } from 'src/features/telemetry/types'
import { FiatOnRampScreens } from 'src/screens/Screens'
import { AnimatedFlex, Flex, Text, useIsDarkMode } from 'ui/src'
import { AnimatedFlex, Flex, Text } from 'ui/src'
import { usePrevious } from 'utilities/src/react/hooks'
import { DecimalPadLegacy } from 'wallet/src/components/legacy/DecimalPadLegacy'
import { useBottomSheetContext } from 'wallet/src/components/modals/BottomSheetContext'
import { HandleBar } from 'wallet/src/components/modals/HandleBar'
import { useFiatOnRampAggregatorServiceProvidersQuery } from 'wallet/src/features/fiatOnRamp/api'
import { FORQuote, FORServiceProvider } from 'wallet/src/features/fiatOnRamp/types'
import { FORQuote } from 'wallet/src/features/fiatOnRamp/types'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
......@@ -59,21 +57,9 @@ function selectInitialQuote(
return { quote: undefined, type: undefined }
}
function preloadServiceProviderLogos(
serviceProviders: FORServiceProvider[],
isDarkMode: boolean
): void {
FastImage.preload(
serviceProviders
.map((sp) => ({ uri: getServiceProviderLogo(sp.logos, isDarkMode) }))
.filter((sp) => !!sp.uri)
)
}
export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const isDarkMode = useIsDarkMode()
const [selection, setSelection] = useState<TextInputProps['selection']>()
const [value, setValue] = useState('')
const [showTokenSelector, setShowTokenSelector] = useState(false)
......@@ -123,17 +109,6 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
error: serviceProvidersError,
} = useFiatOnRampAggregatorServiceProvidersQuery()
// preload service provider logos for given quotes for the next screen
useEffect(() => {
if (serviceProvidersResponse?.serviceProviders && quotes) {
const quotesServiceProviderNames = quotes.map((q) => q.serviceProvider)
const serviceProviders = serviceProvidersResponse.serviceProviders.filter(
(sp) => quotesServiceProviderNames.indexOf(sp.serviceProvider) !== -1
)
preloadServiceProviderLogos(serviceProviders, isDarkMode)
}
}, [serviceProvidersResponse, quotes, isDarkMode])
const { errorText, errorColor } = useParseFiatOnRampError(
quotesError || serviceProvidersError,
meldSupportedFiatCurrency.code
......
......@@ -6,10 +6,14 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Freeze } from 'react-freeze'
import { useTranslation } from 'react-i18next'
import { FlatList, StyleProp, View, ViewProps, ViewStyle } from 'react-native'
import { TapGestureHandler, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'
import Animated, {
FadeIn,
FadeOut,
cancelAnimation,
interpolateColor,
runOnJS,
useAnimatedGestureHandler,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
......@@ -25,6 +29,7 @@ import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import Trace from 'src/components/Trace/Trace'
import TraceTabView from 'src/components/Trace/TraceTabView'
import { AccountHeader } from 'src/components/accounts/AccountHeader'
import { pulseAnimation } from 'src/components/buttons/utils'
import { ACTIVITY_TAB_DATA_DEPENDENCIES, ActivityTab } from 'src/components/home/ActivityTab'
import { FEED_TAB_DATA_DEPENDENCIES, FeedTab } from 'src/components/home/FeedTab'
import { NFTS_TAB_DATA_DEPENDENCIES, NftsTab } from 'src/components/home/NftsTab'
......@@ -724,25 +729,44 @@ function ActionButton({
iconScale?: number
}): JSX.Element {
const colors = useSporeColors()
const scale = useSharedValue(1)
const animatedStyle = useAnimatedStyle(
() => ({
transform: [{ scale: scale.value }],
}),
[scale]
)
const media = useMedia()
const iconSize = media.short ? iconSizes.icon24 : iconSizes.icon28
const onGestureEvent = useAnimatedGestureHandler<TapGestureHandlerGestureEvent>({
onStart: () => {
cancelAnimation(scale)
scale.value = pulseAnimation(activeScale)
},
onEnd: () => {
runOnJS(onPress)()
},
})
return (
<Trace logPress element={name} pressEvent={eventName}>
<TouchableArea hapticFeedback flex={flex} scaleTo={activeScale} onPress={onPress}>
<AnimatedFlex
centered
fill
backgroundColor="$DEP_backgroundActionButton"
borderRadius="$rounded20"
p="$spacing16">
<Icon
color={colors.accent1.get()}
height={iconSize * iconScale}
strokeWidth={2}
width={iconSize * iconScale}
/>
</AnimatedFlex>
<TouchableArea hapticFeedback flex={flex} onPress={onPress}>
<TapGestureHandler onGestureEvent={onGestureEvent}>
<AnimatedFlex
centered
fill
backgroundColor="$DEP_backgroundActionButton"
borderRadius="$rounded20"
p="$spacing16"
style={animatedStyle}>
<Icon
color={colors.accent1.get()}
height={iconSize * iconScale}
strokeWidth={2}
width={iconSize * iconScale}
/>
</AnimatedFlex>
</TapGestureHandler>
</TouchableArea>
</Trace>
)
......
......@@ -4,16 +4,17 @@ import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useAppDispatch } from 'src/app/hooks'
import { OnboardingStackParamList } from 'src/app/navigation/types'
import { Loader } from 'src/components/loading'
import { clearCloudBackups } from 'src/features/CloudBackup/cloudBackupSlice'
import { useCloudBackups } from 'src/features/CloudBackup/hooks'
import {
startFetchingCloudStorageBackups,
stopFetchingCloudStorageBackups,
} from 'src/features/CloudBackup/RNCloudStorageBackupsManager'
import { clearCloudBackups } from 'src/features/CloudBackup/cloudBackupSlice'
import { useCloudBackups } from 'src/features/CloudBackup/hooks'
import { OnboardingScreen } from 'src/features/onboarding/OnboardingScreen'
import { OnboardingScreens } from 'src/screens/Screens'
import { useAddBackButton } from 'src/utils/useAddBackButton'
import { Flex, Icons, Loader } from 'ui/src'
import { Flex, Icons } from 'ui/src'
import { imageSizes } from 'ui/src/theme'
import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
......
......@@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'
import { ScrollView } from 'react-native-gesture-handler'
import { useAppDispatch } from 'src/app/hooks'
import { OnboardingStackParamList } from 'src/app/navigation/types'
import { Loader } from 'src/components/loading'
import WalletPreviewCard from 'src/features/import/WalletPreviewCard'
import { OnboardingScreen } from 'src/features/onboarding/OnboardingScreen'
import { OnboardingScreens } from 'src/screens/Screens'
import { Button, Flex, Loader } from 'ui/src'
import { Button, Flex } from 'ui/src'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { useTimeout } from 'utilities/src/time/timing'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import WalletPreviewCard from 'wallet/src/components/WalletPreviewCard/WalletPreviewCard'
import { useSelectWalletScreenQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { ImportType } from 'wallet/src/features/onboarding/types'
import {
......
......@@ -227,30 +227,48 @@ exports[`RestoreCloudBackupPasswordScreen renders correctly 1`] = `
}
>
<View
cancelable={true}
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
hitSlop={
{
"bottom": 5,
"left": 5,
"right": 5,
"top": 5,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"paddingBottom": 4,
"paddingLeft": 4,
"paddingRight": 4,
"paddingTop": 4,
"transform": [
{
"scale": 1,
},
],
}
}
>
......
......@@ -19,7 +19,7 @@ const ACCOUNT_IMAGE_SIZE = 52
const ICON_SIZE = 32
const ICON_BORDER_RADIUS = 100
function AccountCardItem({ onClose }: { onClose: () => void }): JSX.Element {
function AccountCardItem(): JSX.Element {
const dispatch = useAppDispatch()
const activeAccountAddress = useActiveAccountAddressWithThrow()
......@@ -35,7 +35,7 @@ function AccountCardItem({ onClose }: { onClose: () => void }): JSX.Element {
}
const onPressShowWalletQr = (): void => {
onClose()
dispatch(closeModal({ name: ModalName.ReceiveCryptoModal }))
dispatch(
openModal({ name: ModalName.WalletConnectScan, initialState: ScannerModalState.WalletQr })
)
......@@ -122,7 +122,7 @@ export function ReceiveCryptoModal(): JSX.Element {
{t('Deposit funds from another wallet or an account')}
</Text>
</Flex>
<AccountCardItem onClose={onClose} />
<AccountCardItem />
<Flex centered row shrink gap="$spacing12" py="$spacing8">
<Separator />
<Text color="$neutral2" textAlign="center" variant="body3">
......@@ -130,7 +130,7 @@ export function ReceiveCryptoModal(): JSX.Element {
</Text>
<Separator />
</Flex>
<TransferInstitutionSelector onClose={onClose} />
<TransferInstitutionSelector />
</Flex>
</BottomSheetModal>
)
......
......@@ -43,7 +43,6 @@ ignores: [
## Internal packages / workspaces
"wallet",
"utilities",
"ui",
## Top level local file paths
"abis",
"analytics",
......
......@@ -16,5 +16,4 @@ REACT_APP_TEMP_API_URL="https://temp.gateway.uniswap.org/v1"
REACT_APP_UNISWAP_API_URL="https://interface.gateway.uniswap.org/v2"
REACT_APP_UNISWAP_BASE_API_URL="https://interface.gateway.uniswap.org/"
REACT_APP_UNISWAP_GATEWAY_DNS="https://interface.gateway.uniswap.org/v2"
REACT_APP_UNITAGS_API_URL="https://gateway.uniswap.org/v2/unitags"
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"
......@@ -60,7 +60,6 @@ module.exports = {
transformIgnorePatterns: ['d3-array'],
moduleNameMapper: {
'd3-array': 'd3-array/dist/d3-array.min.js',
'^react-native$': 'react-native-web',
},
})
},
......
......@@ -3,107 +3,13 @@ import { getTestSelector } from '../../utils'
describe('Uni tags support', () => {
beforeEach(() => {
const unitagSpy = cy.spy().as('unitagSpy')
cy.intercept(/gateway.uniswap.org\/v2\/address/, (req) => {
unitagSpy(req)
})
cy.visit('/swap', {
featureFlags: [{ name: FeatureFlag.uniTags, value: true }],
})
})
it('displays banner in account drawer', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.contains('Introducing uni.eth usernames')
})
it('displays large banner on page', () => {
cy.get(getTestSelector('large-unitag-banner')).should('be.visible')
})
it('does not display banner on landing page', () => {
cy.visit('/?intro=true', {
featureFlags: [{ name: FeatureFlag.uniTags, value: true }],
})
cy.get(getTestSelector('large-unitag-banner')).should('not.be.visible')
})
it('opens modal and hides itself when accept button is clicked', () => {
cy.get(getTestSelector('large-unitag-banner')).within(() => {
cy.get(getTestSelector('unitag-banner-accept-button')).click()
})
cy.contains('Download the Uniswap app').should('exist')
cy.get(getTestSelector('get-the-app-close-button')).click()
cy.get(getTestSelector('large-unitag-banner')).should('not.be.visible')
it('displays claim banner in account drawer', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('Introducing uni.eth usernames').should('not.exist')
})
it('hides itself when reject button is clicked', () => {
cy.get(getTestSelector('large-unitag-banner')).within(() => {
cy.get(getTestSelector('unitag-banner-reject-button')).click()
})
cy.get(getTestSelector('large-unitag-banner')).should('not.be.visible')
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('Introducing uni.eth usernames').should('not.exist')
})
it('shows address if no Unitag or ENS exists', () => {
cy.hardhat().then(() => {
const unusedAccount = '0xF030EaA01aFf57A23483dC8A1c3550d153be69Fb'
cy.get(getTestSelector('web3-status-connected')).click()
cy.window().then((win) => win.ethereum.emit('accountsChanged', [unusedAccount]))
cy.get(getTestSelector('account-drawer-status')).within(() => {
cy.contains('0xF030...69Fb').should('be.visible')
})
})
})
it('shows Unitag, followed by address, if Unitag exists but not ENS', () => {
cy.intercept(/address/, { fixture: 'mini-portfolio/unitag.json' })
cy.hardhat().then(() => {
const accountWithUnitag = '0xF030EaA01aFf57A23483dC8A1c3550d153be69Fb'
cy.get(getTestSelector('web3-status-connected')).click()
cy.window().then((win) => win.ethereum.emit('accountsChanged', [accountWithUnitag]))
cy.get(getTestSelector('account-drawer-status')).within(() => {
cy.contains('hayden').should('be.visible')
cy.contains('0xF030...69Fb').should('be.visible')
})
})
})
it('shows ENS, followed by address, if ENS exists but not Unitag', () => {
cy.hardhat().then(() => {
const haydenAccount = '0x50EC05ADe8280758E2077fcBC08D878D4aef79C3'
const haydenENS = 'hayden.eth'
cy.get(getTestSelector('web3-status-connected')).click()
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
cy.get(getTestSelector('account-drawer-status')).within(() => {
cy.contains(haydenENS).should('be.visible')
cy.contains('0x50EC...79C3').should('be.visible')
})
})
})
it('shows Unitag and more option if user has both Unitag and ENS', () => {
cy.intercept(/address/, { fixture: 'mini-portfolio/unitag.json' })
cy.hardhat().then(() => {
const haydenAccount = '0x50EC05ADe8280758E2077fcBC08D878D4aef79C3'
const haydenUnitag = 'hayden'
const haydenENS = 'hayden.eth'
cy.get(getTestSelector('web3-status-connected')).click()
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
cy.get(getTestSelector('account-drawer-status')).within(() => {
cy.contains(haydenUnitag).should('be.visible')
cy.contains('0x50EC...79C3').should('be.visible')
})
cy.get(getTestSelector('secondary-identifiers'))
.trigger('mouseover')
.click()
.within(() => {
cy.contains(haydenENS).should('be.visible')
cy.contains('0x50EC...79C3').should('be.visible')
})
})
cy.contains('Claim your Uniswap username')
})
})
......@@ -199,7 +199,6 @@ describe('Permit2', () => {
cy.contains('Approve and swap').click()
// Verify token approval
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
expectTokenAllowanceForPermit2ToBeMax(DAI)
......@@ -232,6 +231,7 @@ describe('Permit2', () => {
})
it('prompts signature when existing permit approval is expired', () => {
setupInputs(DAI, USDC_MAINNET)
const expiredAllowance = { expiration: Math.floor((Date.now() - 1) / 1000) }
cy.hardhat()
.then(({ approval, wallet }) =>
......@@ -241,8 +241,7 @@ describe('Permit2', () => {
])
)
.then(() => {
setupInputs(DAI, USDC_MAINNET)
initiateSwap('Sign and swap')
initiateSwap('Approve and swap')
})
// Verify permit2 approval
......@@ -252,6 +251,7 @@ describe('Permit2', () => {
})
it('prompts signature when existing permit approval amount is too low', () => {
setupInputs(DAI, USDC_MAINNET)
const smallAllowance = { amount: 1 }
cy.hardhat()
.then(({ approval, wallet }) =>
......@@ -261,8 +261,7 @@ describe('Permit2', () => {
])
)
.then(() => {
setupInputs(DAI, USDC_MAINNET)
initiateSwap('Sign and swap')
initiateSwap('Approve and swap')
})
// Verify permit2 approval
......
......@@ -53,7 +53,6 @@ describe('Swap', () => {
})
it('swaps ETH for USDC', () => {
cy.interceptGraphqlOperation('Activity', 'mini-portfolio/empty_activity.json')
cy.visit('/swap')
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {
......
......@@ -163,7 +163,7 @@ describe('Wallet Dropdown', () => {
it('should dismiss the wallet bottom sheet when clicking buy crypto', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-buy-crypto')).click()
cy.get(getTestSelector('wallet-settings')).should('not.be.visible')
cy.contains('Buy crypto').should('not.be.visible')
})
it('should use a bottom sheet and dismiss when on a mobile screen size', () => {
......
{
"username": "hayden"
}
\ No newline at end of file
......@@ -21,12 +21,6 @@ declare global {
* @returns {Chainable<Subject>}
*/
waitForAmplitudeEvent(eventName: string, requiredProperties?: string[]): Chainable<Subject>
/**
* Intercepts a specific graphql operation and responds with the given fixture.
* @param {string} operationName - The name of the graphql operation to intercept.
* @param {string} fixturePath - The path to the fixture to respond with.
*/
interceptGraphqlOperation(operationName: string, fixturePath: string): Chainable<Subject>
}
interface VisitOptions {
serviceWorker?: true
......@@ -102,13 +96,3 @@ Cypress.Commands.add('waitForAmplitudeEvent', (eventName, requiredProperties) =>
}
return findAndDiscardEventsUpToTarget()
})
Cypress.Commands.add('interceptGraphqlOperation', (operationName, fixturePath) => {
return cy.intercept(/(?:interface|beta).gateway.uniswap.org\/v1\/graphql/, (req) => {
if (req.body.operationName === operationName) {
req.reply({ fixture: fixturePath })
} else {
req.continue()
}
})
})
......@@ -14,7 +14,7 @@ const forks = {
[ChainId.MAINNET]: {
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
// Temporarily hardcoding this to fix e2e tests as we investigate source of swap tests failing on older blocknumbers
blockNumber: 19270708,
blockNumber: 19164140,
...forkingConfig,
},
[ChainId.POLYGON]: {
......
......@@ -198,12 +198,12 @@
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/permit2-sdk": "1.2.0",
"@uniswap/redux-multicall": "1.1.8",
"@uniswap/router-sdk": "1.8.0",
"@uniswap/sdk-core": "4.1.2",
"@uniswap/router-sdk": "1.7.1",
"@uniswap/sdk-core": "4.0.7",
"@uniswap/smart-order-router": "3.17.3",
"@uniswap/token-lists": "1.0.0-beta.33",
"@uniswap/uniswapx-sdk": "1.4.1",
"@uniswap/universal-router-sdk": "1.7.1",
"@uniswap/universal-router-sdk": "1.5.8",
"@uniswap/v2-core": "1.0.1",
"@uniswap/v2-periphery": "1.1.0-beta.0",
"@uniswap/v2-sdk": "4.1.0",
......
import { ButtonEmphasis, ButtonSize, LoadingButtonSpinner, ThemeButton } from 'components/Button'
import Column from 'components/Column'
import Row from 'components/Row'
import Tooltip from 'components/Tooltip'
import { SupportArticleURL } from 'constants/supportArticles'
import { ReactNode, useReducer } from 'react'
import { Info } from 'react-feather'
import { Text } from 'rebass'
import styled from 'styled-components'
import { ExternalLink } from 'theme/components'
import { ThemedText } from 'theme/components/text'
const Container = styled(Column)`
position: relative;
height: 100%;
width: 100%;
`
const Tile = styled(ThemeButton)`
height: 100%;
width: 100%;
display: flex;
justify-content: flex-start;
padding: 12px;
border-color: transparent;
border-radius: 16px;
border-style: solid;
border-width: 1px;
`
const StyledLoadingButtonSpinner = styled(LoadingButtonSpinner)`
height: 28px;
width: 28px;
fill: ${({ theme }) => theme.accent1};
`
const ActionName = styled(Text)`
font-size: 16px;
font-style: normal;
font-weight: 535;
line-height: 24px;
`
const ErrorContainer = styled(Row)`
width: 100%;
position: absolute;
bottom: -24px;
display: flex;
justify-content: center;
align-items: center;
`
const ErrorText = styled(ThemedText.LabelMicro)`
color: ${({ theme }) => theme.neutral2};
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`
const ErrorLink = styled(ExternalLink)`
align-items: center;
display: flex;
height: 14px;
justify-content: center;
margin-left: 6px;
width: 14px;
`
const StyledInfoIcon = styled(Info)`
height: 12px;
width: 12px;
flex: 1 1 auto;
stroke: ${({ theme }) => theme.neutral2};
`
export function ActionTile({
dataTestId,
Icon,
name,
onClick,
loading,
disabled,
error,
errorMessage,
errorTooltip,
}: {
dataTestId: string
Icon: ReactNode
name: string
onClick: () => void
loading?: boolean
disabled?: boolean
error?: boolean
errorMessage?: string
errorTooltip?: string
}) {
const [showTooltip, toggleTooltip] = useReducer((isOpen) => !isOpen, false)
return (
<Container>
<Tile
data-testid={dataTestId}
size={ButtonSize.medium}
emphasis={ButtonEmphasis.highSoft}
onClick={onClick}
disabled={disabled}
>
<Column gap="12px">
{loading ? <StyledLoadingButtonSpinner /> : Icon}
<ActionName>{name}</ActionName>
</Column>
</Tile>
{error && (
<ErrorContainer>
<ErrorText>{errorMessage}</ErrorText>
<Tooltip show={showTooltip} text={errorTooltip}>
<ErrorLink
onMouseEnter={toggleTooltip}
onMouseLeave={toggleTooltip}
style={{ color: 'inherit' }}
href={SupportArticleURL.MOONPAY_REGIONAL_AVAILABILITY}
>
<StyledInfoIcon />
</ErrorLink>
</Tooltip>
</ErrorContainer>
)}
</Container>
)
}
......@@ -10,7 +10,7 @@ import { useCallback } from 'react'
import { SignatureType } from 'state/signatures/types'
import styled from 'styled-components'
import { EllipsisStyle, ThemedText } from 'theme/components'
import { shortenAddress } from 'utilities/src/addresses'
import { shortenAddress } from 'utils'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import { PortfolioLogo } from '../PortfolioLogo'
......
......@@ -4,7 +4,7 @@ import { DetailLineItem, LineItemData } from 'components/swap/DetailLineItem'
import TradePrice from 'components/swap/TradePrice'
import { UniswapXOrderDetails } from 'state/signatures/types'
import { ExternalLink } from 'theme/components'
import { ellipseMiddle } from 'utilities/src/addresses'
import { ellipseMiddle } from 'utils/addresses'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { formatTimestamp } from '../formatTimestamp'
......
......@@ -446,15 +446,7 @@ exports[`CancelLimitsDialog should render correctly 1`] = `
<body>
<div
aria-hidden="true"
>
<span
class="t_light is_inversed _dsp_contents"
>
<span
class=" t_light _dsp_contents is_Theme"
/>
</span>
</div>
/>
<reach-portal>
<div
data-focus-guard="true"
......
......@@ -82,39 +82,31 @@ exports[`OffchainOrderLineItem should render type EXCHANGE_RATE 1`] = `
user-select: text;
}
<span
class="t_light is_inversed _dsp_contents"
<div
class="c0 c1 c2"
>
<span
class=" t_light _dsp_contents is_Theme"
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
<div
class="c0 c1 c2"
Rate
</div>
<div
class="c3 c5 css-142zc9n"
>
<button
class="c6"
title="1 USDC = <0.00001 DAI "
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
class="c3 css-142zc9n"
>
Rate
1 USDC = &lt;0.00001 DAI
</div>
<div
class="c3 c5 css-142zc9n"
>
<button
class="c6"
title="1 USDC = <0.00001 DAI "
>
<div
class="c3 css-142zc9n"
>
1 USDC = &lt;0.00001 DAI
</div>
</button>
</div>
</div>
</span>
</span>
</button>
</div>
</div>
</DocumentFragment>
`;
......@@ -168,31 +160,23 @@ exports[`OffchainOrderLineItem should render type NETWORK_COST 1`] = `
overflow-wrap: break-word;
}
<span
class=" _dsp_contents"
<div
class="c0 c1 c2"
>
<span
class=" t_light _dsp_contents is_Theme"
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Network cost
</div>
<div
class="c3 c5 css-142zc9n"
>
<span>
$0
</span>
</div>
</div>
</span>
</span>
Network cost
</div>
<div
class="c3 c5 css-142zc9n"
>
<span>
$0
</span>
</div>
</div>
</DocumentFragment>
`;
......@@ -265,35 +249,27 @@ exports[`OffchainOrderLineItem should render type TRANSACTION_ID 1`] = `
overflow-wrap: break-word;
}
<span
class=" _dsp_contents"
<div
class="c0 c1 c2"
>
<span
class=" t_light _dsp_contents is_Theme"
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Transaction ID
</div>
<div
class="c3 c5 css-142zc9n"
>
<div
class="c0 c1 c2"
<a
class="c6"
href="https://etherscan.io/tx/0x123"
rel="noopener noreferrer"
target="_blank"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Transaction ID
</div>
<div
class="c3 c5 css-142zc9n"
>
<a
class="c6"
href="https://etherscan.io/tx/0x123"
rel="noopener noreferrer"
target="_blank"
>
0x12...x123
</a>
</div>
</div>
</span>
</span>
0x12...x123
</a>
</div>
</div>
</DocumentFragment>
`;
......@@ -24,7 +24,7 @@ import {
TransactionType,
WrapTransactionInfo,
} from 'state/transactions/types'
import { isAddress } from 'utilities/src/addresses'
import { isAddress } from 'utils'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { CancelledTransactionTitleTable, getActivityTitle, LimitOrderTextTable, OrderTextTable } from '../constants'
......
......@@ -28,7 +28,8 @@ import store from 'state'
import { addSignature } from 'state/signatures/reducer'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
import { TransactionType as LocalTransactionType } from 'state/transactions/types'
import { isAddress, isSameAddress } from 'utilities/src/addresses'
import { isAddress } from 'utils'
import { isSameAddress } from 'utils/addresses'
import { currencyId } from 'utils/currencyId'
import { NumberType, useFormatter } from 'utils/formatNumbers'
......
......@@ -70,7 +70,7 @@ describe('LimitDetailActivityRow', () => {
selected={false}
/>
)
expect(container.firstChild?.firstChild?.firstChild).toBeNull()
expect(container.firstChild).toBeNull()
})
it('should not render with invalid amounts', () => {
......@@ -81,7 +81,7 @@ describe('LimitDetailActivityRow', () => {
order={{ ...mockOrder, offchainOrderDetails: { ...mockOrderDetails, swapInfo: undefined as any } }}
/>
)
expect(container.firstChild?.firstChild?.firstChild).toBeNull()
expect(container.firstChild).toBeNull()
})
it('should render with valid details', () => {
......
......@@ -22,7 +22,7 @@ describe('OpenLimitOrdersButton', () => {
it('should not render if there are no open limit orders', () => {
mocked(useOpenLimitOrders).mockReturnValue({ openLimitOrders: [], loading: false, refetch: jest.fn() })
const { container } = render(<OpenLimitOrdersButton account="0x123" openLimitsMenu={jest.fn()} />)
expect(container.firstChild?.firstChild?.firstChild).toBeNull()
expect(container.firstChild).toBeNull()
})
it('should render if there are open limit orders', () => {
mocked(useOpenLimitOrders).mockReturnValue({
......@@ -43,7 +43,7 @@ describe('OpenLimitOrdersButton', () => {
const clickCallback = jest.fn()
const { container } = render(<OpenLimitOrdersButton account="0x123" openLimitsMenu={clickCallback} />)
act(() => {
fireEvent.click(container.firstChild?.firstChild?.firstChild as HTMLElement)
fireEvent.click(container.firstChild as HTMLElement)
})
expect(clickCallback).toHaveBeenCalled()
})
......
import { ChainId, Token } from '@uniswap/sdk-core'
import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens'
import { Interface } from 'ethers/lib/utils'
import { isAddress } from 'utilities/src/addresses'
import { DEFAULT_ERC20_DECIMALS } from 'utilities/src/tokens/constants'
import { isAddress } from 'utils'
import { arrayToSlices } from 'utils/arrays'
import { CurrencyKey, buildCurrencyKey, currencyKey } from 'utils/currencyKey'
import ERC20_ABI from 'wallet/src/abis/erc20.json'
......
......@@ -16,7 +16,7 @@ import { ContractInput, useUniswapPricesQuery } from 'graphql/data/__generated__
import { toContractInput } from 'graphql/data/util'
import useStablecoinPrice from 'hooks/useStablecoinPrice'
import { useMemo } from 'react'
import { getContract } from 'utilities/src/contracts/getContract'
import { getContract } from 'utils'
import { CurrencyKey, currencyKey, currencyKeyFromGraphQL } from 'utils/currencyKey'
import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'wallet/src/abis/types/v3'
......
import { ChainId, CurrencyAmount, Token, V3_CORE_FACTORY_ADDRESSES } from '@uniswap/sdk-core'
import IUniswapV3PoolStateJSON from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import { Pool, Position, computePoolAddress } from '@uniswap/v3-sdk'
import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens'
import { BigNumber } from 'ethers/lib/ethers'
import { Interface } from 'ethers/lib/utils'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { PositionDetails } from 'types/position'
import { DEFAULT_ERC20_DECIMALS } from 'utilities/src/tokens/constants'
import { currencyKey } from 'utils/currencyKey'
import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'wallet/src/abis/types/v3'
import { UniswapV3PoolInterface } from 'wallet/src/abis/types/v3/UniswapV3Pool'
......
This diff is collapsed.
......@@ -59,7 +59,10 @@ export const Scrim = (props: ScrimBackgroundProps) => {
}
const AccountDrawerScrollWrapper = styled.div`
overflow-y: auto;
overflow: hidden;
&:hover {
overflow-y: auto;
}
${ScrollBarStyles}
......
......@@ -66,7 +66,7 @@ function pickFontColor(variant: BadgeVariant | undefined, theme: DefaultTheme):
case BadgeVariant.WARNING_OUTLINE:
return theme.deprecated_accentWarning
default:
return theme.neutral2
return readableColor(theme.neutral2)
}
}
......
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