ci(release): publish latest release

parent 7bda1988
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmWaBTFEnY4LqKBdeyyRBmwhCuphBhe3p3CKmxnnuNhY5z` - CIDv0: `Qma3xNtyKF992ESqe25eV3JTR8LGZwuLLx2HyfJ2LwcGRw`
- CIDv1: `bafybeid2kthnxmhk5tcm2ogpsjzooeo3dj6bt2oh6tk3vbrsqf4sc2ogju` - CIDv1: `bafybeifoavmpqbbjcli2mn7q35itqfnsuiitrvfxvxkikguokyij7cqbhi`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
...@@ -10,15 +10,83 @@ You can also access the Uniswap Interface from an IPFS gateway. ...@@ -10,15 +10,83 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs. Your Uniswap settings are never remembered across different URLs.
IPFS gateways: IPFS gateways:
- https://bafybeid2kthnxmhk5tcm2ogpsjzooeo3dj6bt2oh6tk3vbrsqf4sc2ogju.ipfs.dweb.link/ - https://bafybeifoavmpqbbjcli2mn7q35itqfnsuiitrvfxvxkikguokyij7cqbhi.ipfs.dweb.link/
- https://bafybeid2kthnxmhk5tcm2ogpsjzooeo3dj6bt2oh6tk3vbrsqf4sc2ogju.ipfs.cf-ipfs.com/ - https://bafybeifoavmpqbbjcli2mn7q35itqfnsuiitrvfxvxkikguokyij7cqbhi.ipfs.cf-ipfs.com/
- [ipfs://QmWaBTFEnY4LqKBdeyyRBmwhCuphBhe3p3CKmxnnuNhY5z/](ipfs://QmWaBTFEnY4LqKBdeyyRBmwhCuphBhe3p3CKmxnnuNhY5z/) - [ipfs://Qma3xNtyKF992ESqe25eV3JTR8LGZwuLLx2HyfJ2LwcGRw/](ipfs://Qma3xNtyKF992ESqe25eV3JTR8LGZwuLLx2HyfJ2LwcGRw/)
### 5.63.4 (2025-01-02) ## 5.64.0 (2025-01-08)
### Features
* **web:** add pagination to the positions page (#14478) 69771b0
* **web:** add recommended tooltip (#14574) 7a763dc
* **web:** allow users to add with eth/weth (#14461) 4740134
* **web:** allowing users to remove with eth (#14519) 344c6a4
* **web:** disable clicking on FOR activity until confirmed on chain (#14605) f8c541a
* **web:** Enable GPU acceleration and layout containment on AppBody (#14710) 6f0b0ca
### Bug Fixes ### Bug Fixes
* **web:** set duped events as executed to prevent retry - prod (#14781) 35193b4 * **web:** 01 07 fix web re add images prop to portfoliologo and use split logo staging (#14876) 02a3013
* **web:** 01 08 fix web fix double logo on doublecurrencyandchainlogo staging (#14948) 1b606bb
* **web:** add analyze mode to build script (#14610) c9f9392
* **web:** add monad testnet rpc to web env (#14563) 649d925
* **web:** add white bgs to images and use universe token logos (#14604) c8ac0c9
* **web:** Adrian/direct t fixes staging (#14941) 58f2954
* **web:** bring back network logo on logoless placeholder (#14803) 97bda9d
* **web:** early return isRNDev (#14685) fb77d3a
* **web:** enable unitag search with suffix (#14711) 993cbd6
* **web:** fix alignment of liquidity header modal (#14682) b39d879
* **web:** fix conversion event success handler (#14532) e81947b
* **web:** fix crash on positions page (#14562) 2ba514a
* **web:** fix disabled swap button for previously-dismissed warning tokens (#14553) 5a987f5
* **web:** fix miniP truncated closed positions + testnet mode in pools tab (#14666) 69c50a7
* **web:** fix missing mweb swap (#14567) 0eab28e
* **web:** fix responsive walletconnect pane expando line (#14661) 81fd2ea
* **web:** fix v2 lp networks dropdown (#14576) a4f41d9
* **web:** fix v2 position page crash (#14792) af9dd17
* **web:** info text should be heading3 variant (#14739) 1dbc971
* **web:** input focus state on press (#14759) e090f25
* **web:** landing page translations line break (#14649) c1fb00f
* **web:** modal height fix (#14538) c7eac8b
* **web:** open LP learn more links in new tabs (#14568) 57c0752
* **web:** overflow issues in chart header (#14744) 1de24a8
* **web:** polish sprint nits (#14624) 2849220
* **web:** set duped events as executed to prevent retry (#14771) 6dcb3ef
* **web:** show more button hiding (#14742) a5edb7a
* **web:** some rich link previews broken bc their logo is a webp (#14638) 350a2d6
* **web:** submit conversion events to amplitude (#14497) e172586
* **web:** switch to new marketing events (#14700) 35d9d4f
* **web:** theme value transition for explore page tabs (#14609) 5bb4160
* **web:** tree-shake wagmi chains (#14566) 2c3d079
* **web:** truncation issue on max price position text (#14573) f137c41
* **web:** update global preferences menu (#14636) 38d5f38
* **web:** update tdp default input currency (#14699) d7147af
* **web:** URL prefill param without ?chain should use default chain instead of wallet chain (#14227) a8c57d6
* **web): Revert "feat(web:** Enable GPU acceleration and layout containment on AppBody (#14710)" (#14788) a5b4976
### Continuous Integration
* **web:** update sitemaps 77b75a6
### Styles
* **web:** all networks icon in explore page dropdown (#14639) bf1a42b
* **web:** decrease font size for Learn More link (#14626) 6ea86c2
* **web:** fix helper icon alignment in search dropdown (#14680) 237e505
* **web:** fix hover on active explore tabs (#14646) b1180ea
* **web:** fix some style nits in explore token page mobile action tabs (#14743) 9100557
### Code Refactoring
* **web:** refactor use is x page part 2 (#14652) aa54d69
* **web:** refactor use X page into reusable hook (#14651) f53fa2b
* **web:** refactor useIsNfts hook (#14653) 0132ff0
* **web:** use default match types and include optional override (#14704) edf95e9
web/5.63.4 web/5.64.0
\ No newline at end of file \ No newline at end of file
import 'utilities/src/logger/mocks' import 'utilities/jest-package-mocks'
import 'uniswap/jest-package-mocks'
import 'wallet/jest-package-mocks'
import 'ui/jest-package-mocks'
import { chrome } from 'jest-chrome' import { chrome } from 'jest-chrome'
import { AppearanceSettingType } from 'wallet/src/features/appearance/slice' import { AppearanceSettingType } from 'wallet/src/features/appearance/slice'
import { TextEncoder, TextDecoder } from 'util'
import { mockSharedPersistQueryClientProvider } from 'uniswap/src/test/mocks/mockSharedPersistQueryClientProvider' import { mockSharedPersistQueryClientProvider } from 'uniswap/src/test/mocks/mockSharedPersistQueryClientProvider'
import { mockUIAssets } from 'ui/src/test/mocks/mockUIAssets'
import { mockLocalizationContext } from 'uniswap/src/test/mocks/locale'
process.env.IS_UNISWAP_EXTENSION = true process.env.IS_UNISWAP_EXTENSION = true
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
const ignoreLogs = { const ignoreLogs = {
error: [ error: [
// We need to use _persist property to ensure that the state is properly // We need to use _persist property to ensure that the state is properly
...@@ -73,8 +70,3 @@ jest.mock('wallet/src/features/appearance/hooks', () => { ...@@ -73,8 +70,3 @@ jest.mock('wallet/src/features/appearance/hooks', () => {
} }
}) })
jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext({}))
jest.mock('uniswap/src/data/apiClients/SharedPersistQueryClientProvider', () => mockSharedPersistQueryClientProvider)
mockUIAssets()
...@@ -45,7 +45,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace' ...@@ -45,7 +45,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { ExtensionOnboardingFlow } from 'uniswap/src/types/screens/extension' import { ExtensionOnboardingFlow } from 'uniswap/src/types/screens/extension'
import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary' import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary'
import { SharedWalletProvider } from 'wallet/src/providers/SharedWalletProvider' import { SharedWalletProvider } from 'wallet/src/providers/SharedWalletProvider'
......
...@@ -23,7 +23,7 @@ import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice' ...@@ -23,7 +23,7 @@ import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { ExtensionScreens } from 'uniswap/src/types/screens/extension'
import { getUniqueId } from 'utilities/src/device/getUniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
......
...@@ -46,7 +46,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace' ...@@ -46,7 +46,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { UnitagUpdaterContextProvider, useUnitagUpdater } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider, useUnitagUpdater } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { getUniqueId } from 'utilities/src/device/getUniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { isDevEnv } from 'utilities/src/environment/env' import { isDevEnv } from 'utilities/src/environment/env'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
......
...@@ -31,7 +31,7 @@ import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext' ...@@ -31,7 +31,7 @@ import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext'
import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext' import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { getUniqueId } from 'utilities/src/device/getUniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { usePrevious } from 'utilities/src/react/hooks' import { usePrevious } from 'utilities/src/react/hooks'
......
import { ApolloProvider } from '@apollo/client' import { ApolloProvider } from '@apollo/client'
import { PropsWithChildren } from 'react' import { PropsWithChildren } from 'react'
import { localStorage } from 'redux-persist-webextension-storage' import { localStorage } from 'redux-persist-webextension-storage'
import { getReduxStore } from 'src/store/store'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient' import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient'
...@@ -11,6 +12,7 @@ export function GraphqlProvider({ children }: PropsWithChildren<unknown>): JSX.E ...@@ -11,6 +12,7 @@ export function GraphqlProvider({ children }: PropsWithChildren<unknown>): JSX.E
const apolloClient = usePersistedApolloClient({ const apolloClient = usePersistedApolloClient({
storageWrapper: localStorage, storageWrapper: localStorage,
maxCacheSizeInBytes: MAX_CACHE_SIZE_IN_BYTES, maxCacheSizeInBytes: MAX_CACHE_SIZE_IN_BYTES,
reduxStore: getReduxStore(),
}) })
if (!apolloClient) { if (!apolloClient) {
......
import { forwardRef } from 'react' import { forwardRef } from 'react'
import { useTranslation } from 'react-i18next'
import { TextInput } from 'react-native' import { TextInput } from 'react-native'
import { Input, InputProps } from 'src/app/components/Input' import { Input, InputProps } from 'src/app/components/Input'
import { Button, Flex, FlexProps, IconProps, Text } from 'ui/src' import { Button, Flex, FlexProps, IconProps, Text } from 'ui/src'
...@@ -50,11 +51,12 @@ export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function ...@@ -50,11 +51,12 @@ export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function
}) })
function StrengthIndicator({ strength }: { strength: PasswordStrength }): JSX.Element | null { function StrengthIndicator({ strength }: { strength: PasswordStrength }): JSX.Element | null {
const { t } = useTranslation()
if (strength === PasswordStrength.NONE) { if (strength === PasswordStrength.NONE) {
return null return null
} }
const { text, color } = getPasswordStrengthTextAndColor(strength) const { text, color } = getPasswordStrengthTextAndColor(t, strength)
return ( return (
<Flex position="absolute" right="$spacing24"> <Flex position="absolute" right="$spacing24">
......
...@@ -2,8 +2,14 @@ import { datadogLogs } from '@datadog/browser-logs' ...@@ -2,8 +2,14 @@ import { datadogLogs } from '@datadog/browser-logs'
import { datadogRum } from '@datadog/browser-rum' import { datadogRum } from '@datadog/browser-rum'
import { getDatadogEnvironment } from 'src/app/version' import { getDatadogEnvironment } from 'src/app/version'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import {
DatadogIgnoredErrorsConfigKey,
DatadogIgnoredErrorsValType,
DynamicConfigs,
} from 'uniswap/src/features/gating/configs'
import { Experiments } from 'uniswap/src/features/gating/experiments' import { Experiments } from 'uniswap/src/features/gating/experiments'
import { FeatureFlags, WALLET_FEATURE_FLAG_NAMES, getFeatureFlagName } from 'uniswap/src/features/gating/flags' import { FeatureFlags, WALLET_FEATURE_FLAG_NAMES, getFeatureFlagName } from 'uniswap/src/features/gating/flags'
import { getDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { Statsig } from 'uniswap/src/features/gating/sdk/statsig' import { Statsig } from 'uniswap/src/features/gating/sdk/statsig'
import { getUniqueId } from 'utilities/src/device/getUniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -39,12 +45,24 @@ export async function initializeDatadog(appName: string): Promise<void> { ...@@ -39,12 +45,24 @@ export async function initializeDatadog(appName: string): Promise<void> {
if (event.error.source === 'console') { if (event.error.source === 'console') {
return false return false
} }
const ignoredErrors = getDynamicConfigValue<
DynamicConfigs.DatadogIgnoredErrors,
DatadogIgnoredErrorsConfigKey,
DatadogIgnoredErrorsValType
>(DynamicConfigs.DatadogIgnoredErrors, DatadogIgnoredErrorsConfigKey.Errors, [])
const ignoredError = ignoredErrors.find(({ messageContains }) => event.error?.message.includes(messageContains))
if (ignoredError && Math.random() > ignoredError.sampleRate) {
return false
}
Object.defineProperty(event.error, 'stack', { Object.defineProperty(event.error, 'stack', {
value: event.error.stack?.replace(/chrome-extension:\/\/[a-z]{32}/gi, ''), value: event.error.stack?.replace(/chrome-extension:\/\/[a-z]{32}/gi, ''),
writable: false, writable: false,
configurable: true, configurable: true,
}) })
} }
return true return true
}, },
}) })
......
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { Button, Flex, Text, TouchableArea } from 'ui/src' import { Button, Flex, Text, TouchableArea } from 'ui/src'
import { Feedback, LikeSquare, MessageText, X } from 'ui/src/components/icons' import { Feedback, LikeSquare, MessageText, X } from 'ui/src/components/icons'
import { IconSizeTokens, zIndices } from 'ui/src/theme' import { IconSizeTokens, zIndices } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { useTranslation } from 'uniswap/src/i18n' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { appRatingPromptedMsSelector, appRatingProvidedMsSelector } from 'wallet/src/features/wallet/selectors'
import { setAppRating } from 'wallet/src/features/wallet/slice' import { setAppRating } from 'wallet/src/features/wallet/slice'
interface AppRatingModalProps { interface AppRatingModalProps {
...@@ -23,6 +25,26 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El ...@@ -23,6 +25,26 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
const { t } = useTranslation() const { t } = useTranslation()
const [state, setState] = useState(State.Initial) const [state, setState] = useState(State.Initial)
const dispatch = useDispatch() const dispatch = useDispatch()
const appRatingPromptedMs = useSelector(appRatingPromptedMsSelector)
const appRatingProvidedMs = useSelector(appRatingProvidedMsSelector)
const close = (): void => {
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'close',
appRatingPromptedMs,
appRatingProvidedMs,
})
onClose()
}
const onRemindLater = (): void => {
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'remind',
appRatingPromptedMs,
appRatingProvidedMs,
})
onClose()
}
const stateConfig = { const stateConfig = {
[State.Initial]: { [State.Initial]: {
...@@ -42,11 +64,16 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El ...@@ -42,11 +64,16 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
primaryButtonText: t('appRating.feedback.button.send'), primaryButtonText: t('appRating.feedback.button.send'),
Icon: MessageText, Icon: MessageText,
iconSize: '$icon.18' as IconSizeTokens, iconSize: '$icon.18' as IconSizeTokens,
onSecondaryButtonPress: () => onClose(), onSecondaryButtonPress: onRemindLater,
onPrimaryButtonPress: (): void => { onPrimaryButtonPress: (): void => {
// eslint-disable-next-line security/detect-non-literal-fs-filename // eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(uniswapUrls.walletFeedbackForm) window.open(uniswapUrls.walletFeedbackForm)
dispatch(setAppRating({ feedbackProvided: true })) dispatch(setAppRating({ feedbackProvided: true }))
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'feedback-form',
appRatingPromptedMs,
appRatingProvidedMs,
})
onClose() onClose()
}, },
}, },
...@@ -57,11 +84,16 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El ...@@ -57,11 +84,16 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
primaryButtonText: t('common.button.review'), primaryButtonText: t('common.button.review'),
Icon: Feedback, Icon: Feedback,
iconSize: '$icon.24' as IconSizeTokens, iconSize: '$icon.24' as IconSizeTokens,
onSecondaryButtonPress: () => onClose(), onSecondaryButtonPress: onRemindLater,
onPrimaryButtonPress: (): void => { onPrimaryButtonPress: (): void => {
// eslint-disable-next-line security/detect-non-literal-fs-filename // eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(`https://chromewebstore.google.com/detail/uniswap-extension/${chrome.runtime.id}/reviews`) window.open(`https://chromewebstore.google.com/detail/uniswap-extension/${chrome.runtime.id}/reviews`)
dispatch(setAppRating({ ratingProvided: true })) dispatch(setAppRating({ ratingProvided: true }))
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'store-review',
appRatingPromptedMs,
appRatingProvidedMs: Date.now(), // to avoid race condition with updates from redux
})
onClose() onClose()
}, },
}, },
...@@ -84,8 +116,8 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El ...@@ -84,8 +116,8 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
}, [dispatch]) }, [dispatch])
return ( return (
<Modal isDismissible isModalOpen name={ModalName.TokenWarningModal} backgroundColor="$surface1" onClose={onClose}> <Modal isDismissible isModalOpen name={ModalName.TokenWarningModal} backgroundColor="$surface1" onClose={close}>
<TouchableArea p="$spacing16" position="absolute" right={0} top={0} zIndex={zIndices.default} onPress={onClose}> <TouchableArea p="$spacing16" position="absolute" right={0} top={0} zIndex={zIndices.default} onPress={close}>
<X color="$neutral2" size="$icon.20" /> <X color="$neutral2" size="$icon.20" />
</TouchableArea> </TouchableArea>
<Flex alignItems="center" gap="$spacing8" pt="$spacing16"> <Flex alignItems="center" gap="$spacing8" pt="$spacing16">
......
...@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' ...@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { OnboardingScreenProps } from 'src/app/features/onboarding/OnboardingScreenProps' import { OnboardingScreenProps } from 'src/app/features/onboarding/OnboardingScreenProps'
import { Button, Flex, Text, TouchableArea } from 'ui/src' import { Button, Flex, Text, TouchableArea } from 'ui/src'
import { BackArrow } from 'ui/src/components/icons' import { BackArrow } from 'ui/src/components/icons'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
export function OnboardingScreenFrame({ export function OnboardingScreenFrame({
Icon, Icon,
......
...@@ -7,7 +7,7 @@ import { Settings } from 'ui/src/components/icons' ...@@ -7,7 +7,7 @@ import { Settings } from 'ui/src/components/icons'
import { Language, WALLET_SUPPORTED_LANGUAGES } from 'uniswap/src/features/language/constants' import { Language, WALLET_SUPPORTED_LANGUAGES } from 'uniswap/src/features/language/constants'
import { getLanguageInfo, useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks' import { getLanguageInfo, useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks'
import { setCurrentLanguage } from 'uniswap/src/features/settings/slice' import { setCurrentLanguage } from 'uniswap/src/features/settings/slice'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { GatingOverrides } from 'wallet/src/components/gating/GatingOverrides' import { GatingOverrides } from 'wallet/src/components/gating/GatingOverrides'
export function DevMenuScreen(): JSX.Element { export function DevMenuScreen(): JSX.Element {
......
import { t } from 'i18next'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScreenHeader } from 'src/app/components/layout/ScreenHeader' import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { ChangePasswordForm } from 'src/app/features/settings/password/ChangePasswordForm' import { ChangePasswordForm } from 'src/app/features/settings/password/ChangePasswordForm'
import { EnterPasswordForm } from 'src/app/features/settings/password/EnterPasswordForm' import { EnterPasswordForm } from 'src/app/features/settings/password/EnterPasswordForm'
...@@ -12,6 +12,7 @@ enum Step { ...@@ -12,6 +12,7 @@ enum Step {
} }
export function SettingsChangePasswordScreen(): JSX.Element { export function SettingsChangePasswordScreen(): JSX.Element {
const { t } = useTranslation()
const [currentStep, setCurrentStep] = useState(Step.EnterPassword) const [currentStep, setCurrentStep] = useState(Step.EnterPassword)
const { navigateBack } = useExtensionNavigation() const { navigateBack } = useExtensionNavigation()
......
import { t } from 'i18next'
import { useCallback, useEffect } from 'react' import { useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen' import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext' import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext' import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext'
...@@ -16,6 +16,7 @@ import { UnitagChooseProfilePicContent } from 'wallet/src/features/unitags/Unita ...@@ -16,6 +16,7 @@ import { UnitagChooseProfilePicContent } from 'wallet/src/features/unitags/Unita
import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks' import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks'
export function UnitagChooseProfilePicScreen(): JSX.Element { export function UnitagChooseProfilePicScreen(): JSX.Element {
const { t } = useTranslation()
const { goToNextStep, goToPreviousStep } = useOnboardingSteps() const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const { unitag, entryPoint, setProfilePicUri } = useUnitagClaimContext() const { unitag, entryPoint, setProfilePicUri } = useUnitagClaimContext()
const address = useAccountAddressFromUrlWithThrow() const address = useAccountAddressFromUrlWithThrow()
......
import { t } from 'i18next'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen' import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext' import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext' import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext'
...@@ -14,6 +14,7 @@ import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/ho ...@@ -14,6 +14,7 @@ import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/ho
type onNavigateContinueType = Exclude<ClaimUnitagContentProps['onNavigateContinue'], undefined> type onNavigateContinueType = Exclude<ClaimUnitagContentProps['onNavigateContinue'], undefined>
export function UnitagCreateUsernameScreen(): JSX.Element { export function UnitagCreateUsernameScreen(): JSX.Element {
const { t } = useTranslation()
const { goToNextStep, goToPreviousStep } = useOnboardingSteps() const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const { setUnitag, setEntryPoint } = useUnitagClaimContext() const { setUnitag, setEntryPoint } = useUnitagClaimContext()
const address = useAccountAddressFromUrlWithThrow() const address = useAccountAddressFromUrlWithThrow()
......
...@@ -164,6 +164,7 @@ ...@@ -164,6 +164,7 @@
8EE7C0582AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE7C0572AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift */; }; 8EE7C0582AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE7C0572AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift */; };
9127D1362CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9127D1342CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift */; }; 9127D1362CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9127D1342CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift */; };
9127D1372CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9127D1352CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift */; }; 9127D1372CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9127D1352CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift */; };
9173CEBC2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9173CEBB2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift */; };
91D501702CDBEAE700B09B7F /* TokenMarketParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D501652CDBEAE700B09B7F /* TokenMarketParts.graphql.swift */; }; 91D501702CDBEAE700B09B7F /* TokenMarketParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D501652CDBEAE700B09B7F /* TokenMarketParts.graphql.swift */; };
91D501712CDBEAE700B09B7F /* TokenBalanceMainParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D501662CDBEAE700B09B7F /* TokenBalanceMainParts.graphql.swift */; }; 91D501712CDBEAE700B09B7F /* TokenBalanceMainParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D501662CDBEAE700B09B7F /* TokenBalanceMainParts.graphql.swift */; };
91D501722CDBEAE700B09B7F /* TokenProjectMarketsParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D501672CDBEAE700B09B7F /* TokenProjectMarketsParts.graphql.swift */; }; 91D501722CDBEAE700B09B7F /* TokenProjectMarketsParts.graphql.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D501672CDBEAE700B09B7F /* TokenProjectMarketsParts.graphql.swift */; };
...@@ -501,6 +502,7 @@ ...@@ -501,6 +502,7 @@
8EE7C0572AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DescriptionTranslations.graphql.swift; sourceTree = "<group>"; }; 8EE7C0572AFD7B2100E0D9CD /* DescriptionTranslations.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DescriptionTranslations.graphql.swift; sourceTree = "<group>"; };
9127D1342CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBalanceMainParts.graphql.swift; sourceTree = "<group>"; }; 9127D1342CC2D3D00096F134 /* TokenBalanceMainParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBalanceMainParts.graphql.swift; sourceTree = "<group>"; };
9127D1352CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBalanceQuantityParts.graphql.swift; sourceTree = "<group>"; }; 9127D1352CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBalanceQuantityParts.graphql.swift; sourceTree = "<group>"; };
9173CEBB2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenBalanceParts.graphql.swift; sourceTree = "<group>"; };
91D501652CDBEAE700B09B7F /* TokenMarketParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenMarketParts.graphql.swift; sourceTree = "<group>"; }; 91D501652CDBEAE700B09B7F /* TokenMarketParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenMarketParts.graphql.swift; sourceTree = "<group>"; };
91D501662CDBEAE700B09B7F /* TokenBalanceMainParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBalanceMainParts.graphql.swift; sourceTree = "<group>"; }; 91D501662CDBEAE700B09B7F /* TokenBalanceMainParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBalanceMainParts.graphql.swift; sourceTree = "<group>"; };
91D501672CDBEAE700B09B7F /* TokenProjectMarketsParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenProjectMarketsParts.graphql.swift; sourceTree = "<group>"; }; 91D501672CDBEAE700B09B7F /* TokenProjectMarketsParts.graphql.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenProjectMarketsParts.graphql.swift; sourceTree = "<group>"; };
...@@ -771,6 +773,7 @@ ...@@ -771,6 +773,7 @@
91D5016A2CDBEAE700B09B7F /* TokenBalanceQuantityParts.graphql.swift */, 91D5016A2CDBEAE700B09B7F /* TokenBalanceQuantityParts.graphql.swift */,
91D501682CDBEAE700B09B7F /* TokenBasicInfoParts.graphql.swift */, 91D501682CDBEAE700B09B7F /* TokenBasicInfoParts.graphql.swift */,
91D501692CDBEAE700B09B7F /* TokenBasicProjectParts.graphql.swift */, 91D501692CDBEAE700B09B7F /* TokenBasicProjectParts.graphql.swift */,
9173CEBB2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift */,
91D5016F2CDBEAE700B09B7F /* TokenFeeDataParts.graphql.swift */, 91D5016F2CDBEAE700B09B7F /* TokenFeeDataParts.graphql.swift */,
91D501652CDBEAE700B09B7F /* TokenMarketParts.graphql.swift */, 91D501652CDBEAE700B09B7F /* TokenMarketParts.graphql.swift */,
91D5016C2CDBEAE700B09B7F /* TokenParts.graphql.swift */, 91D5016C2CDBEAE700B09B7F /* TokenParts.graphql.swift */,
...@@ -1923,6 +1926,7 @@ ...@@ -1923,6 +1926,7 @@
0DE251482C13B69D005F47F9 /* OnRampServiceProvider.graphql.swift in Sources */, 0DE251482C13B69D005F47F9 /* OnRampServiceProvider.graphql.swift in Sources */,
074322002A83E3CA00F8518D /* AssetChange.graphql.swift in Sources */, 074322002A83E3CA00F8518D /* AssetChange.graphql.swift in Sources */,
074322232A83E3CA00F8518D /* NftActivityConnection.graphql.swift in Sources */, 074322232A83E3CA00F8518D /* NftActivityConnection.graphql.swift in Sources */,
9173CEBC2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift in Sources */,
9127D1372CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift in Sources */, 9127D1372CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift in Sources */,
0DE251432C13B674005F47F9 /* OnRampTransactionsAuth.graphql.swift in Sources */, 0DE251432C13B674005F47F9 /* OnRampTransactionsAuth.graphql.swift in Sources */,
0743221B2A83E3CA00F8518D /* NftCollectionMarket.graphql.swift in Sources */, 0743221B2A83E3CA00F8518D /* NftCollectionMarket.graphql.swift in Sources */,
......
...@@ -2,19 +2,14 @@ ...@@ -2,19 +2,14 @@
// For example: https://reactnavigation.org/docs/testing/ // For example: https://reactnavigation.org/docs/testing/
import 'core-js' // necessary so setImmediate works in tests import 'core-js' // necessary so setImmediate works in tests
import 'uniswap/src/i18n/i18n' // Uses real translations for tests import 'utilities/jest-package-mocks'
import 'utilities/src/logger/mocks' import 'uniswap/jest-package-mocks'
import 'wallet/jest-package-mocks'
import 'ui/jest-package-mocks'
import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js' import 'uniswap/src/i18n' // Uses real translations for tests
import { localizeMock as mockRNLocalize } from 'react-native-localize/mock'
import { mockUIAssets } from 'ui/src/test/mocks/mockUIAssets'
import { mockLocalizationContext } from 'uniswap/src/test/mocks/locale'
import { mockSharedPersistQueryClientProvider } from 'uniswap/src/test/mocks/mockSharedPersistQueryClientProvider'
import { TextDecoder, TextEncoder } from 'util'
import { AppearanceSettingType } from 'wallet/src/features/appearance/slice'
global.TextEncoder = TextEncoder import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js'
global.TextDecoder = TextDecoder
jest.mock('@uniswap/client-explore/dist/uniswap/explore/v1/service-ExploreStatsService_connectquery', () => {}) jest.mock('@uniswap/client-explore/dist/uniswap/explore/v1/service-ExploreStatsService_connectquery', () => {})
...@@ -78,31 +73,10 @@ jest.mock('@react-navigation/elements', () => ({ ...@@ -78,31 +73,10 @@ jest.mock('@react-navigation/elements', () => ({
require('react-native-reanimated').setUpTests() require('react-native-reanimated').setUpTests()
jest.mock('expo-localization', () => ({
getLocales: () => [
{
languageCode: 'en',
languageTag: 'en-US',
regionCode: null,
currencyCode: null,
currencySymbol: null,
decimalSeparator: null,
digitGroupingSeparator: null,
textDirection: null,
measurementSystem: null,
temperatureUnit: null,
},
],
}))
jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext({}))
jest.mock('react-native/Libraries/Share/Share', () => ({ jest.mock('react-native/Libraries/Share/Share', () => ({
share: jest.fn(), share: jest.fn(),
})) }))
jest.mock('react-native-localize', () => mockRNLocalize)
jest.mock('@react-native-firebase/auth', () => () => ({ jest.mock('@react-native-firebase/auth', () => () => ({
signInAnonymously: jest.fn(), signInAnonymously: jest.fn(),
})) }))
...@@ -123,21 +97,7 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({ ...@@ -123,21 +97,7 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({
getInitialURL: jest.fn(), getInitialURL: jest.fn(),
})) }))
// Mock the appearance hook for all tests
const mockAppearanceSetting = AppearanceSettingType.System
jest.mock('wallet/src/features/appearance/hooks', () => {
return {
useCurrentAppearanceSetting: () => mockAppearanceSetting,
}
})
jest.mock('wallet/src/features/appearance/hooks', () => {
return {
useSelectedColorScheme: () => 'light',
}
})
jest.mock('openai') jest.mock('openai')
jest.mock('uniswap/src/data/apiClients/SharedPersistQueryClientProvider', () => mockSharedPersistQueryClientProvider)
mockUIAssets()
#!/bin/bash
# This script tests deep links for the Uniswap mobile app locally.
# Usage: ./testDeepLinks.sh <user_id>
# It opens a series of URLs in the iOS simulator and terminates the app after each URL is opened.
# Arguments:
# user_id: The user ID to be included in some of the URLs.
bundle_id="com.uniswap.mobile.dev"
if [ -z "$1" ]; then
echo "Usage: $0 <user_id>"
exit 1
fi
user_id="$1"
urls=(
"uniswap://wc?uri=wc:af098@2?relay-protocol=irn&symKey=51e"
"uniswap://wc:af098@2?relay-protocol=irn&symKey=51e"
"uniswap://scantastic?param=value"
"uniswap://uwulink?param=value"
"uniswap://redirect?screen=transaction&fiatOffRamp=true&userAddress=$user_id&externalTransactionId=123"
"https://uniswap.org/app?screen=swap&userAddress=$user_id&inputCurrencyId=1-0x6B175474E89094C44Da98b954EedeAC495271d0F&outputCurrencyId=1-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&currencyField=input&amount=100"
"https://uniswap.org/app?screen=transaction&fiatOnRamp=true&userAddress=$user_id"
"https://uniswap.org/app?screen=transaction&userAddress=$user_id"
"https://uniswap.org/app/wc?uri=wc:af098@2?relay-protocol=irn&symKey=51e"
"uniswap://app/fiatonramp?userAddress=$user_id&source=push"
"uniswap://app/tokendetails?currencyId=10-0x6fd9d7ad17242c41f7131d257212c54a0e816691&source=push"
)
xcrun simctl terminate booted "$bundle_id"
for url in "${urls[@]}"; do
echo "Opening URL: $url"
xcrun simctl openurl booted "$url"
sleep 10
echo "Terminating app with bundle ID: $bundle_id"
xcrun simctl terminate booted "$bundle_id"
done
import { ApolloProvider } from '@apollo/client' import { ApolloProvider } from '@apollo/client'
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev'
import { DdRum, DdSdkReactNative } from '@datadog/mobile-react-native' import { DdRum, DdSdkReactNative } from '@datadog/mobile-react-native'
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import { PerformanceProfiler, RenderPassReport } from '@shopify/react-native-performance' import { PerformanceProfiler, RenderPassReport } from '@shopify/react-native-performance'
...@@ -61,7 +62,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace' ...@@ -61,7 +62,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { CurrencyId } from 'uniswap/src/types/currency' import { CurrencyId } from 'uniswap/src/types/currency'
import { getUniqueId } from 'utilities/src/device/getUniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { isDetoxBuild } from 'utilities/src/environment/constants' import { isDetoxBuild } from 'utilities/src/environment/constants'
...@@ -71,12 +72,12 @@ import { logger } from 'utilities/src/logger/logger' ...@@ -71,12 +72,12 @@ import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext' import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext'
import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary' import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary'
import { selectAllowAnalytics } from 'wallet/src/features/telemetry/selectors'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient' import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient'
import { initFirebaseAppCheck } from 'wallet/src/features/appCheck/appCheck' import { initFirebaseAppCheck } from 'wallet/src/features/appCheck/appCheck'
import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks' import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks'
import { selectAllowAnalytics } from 'wallet/src/features/telemetry/selectors'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks'
import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater' import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater'
import { WalletUniswapProvider } from 'wallet/src/features/transactions/contexts/WalletUniswapContext' import { WalletUniswapProvider } from 'wallet/src/features/transactions/contexts/WalletUniswapContext'
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
...@@ -88,6 +89,8 @@ enableFreeze(true) ...@@ -88,6 +89,8 @@ enableFreeze(true)
if (__DEV__) { if (__DEV__) {
registerConsoleOverrides() registerConsoleOverrides()
loadDevMessages()
loadErrorMessages()
} }
// Keep the splash screen visible while we fetch resources until one of our landing pages loads // Keep the splash screen visible while we fetch resources until one of our landing pages loads
...@@ -180,6 +183,7 @@ function AppOuter(): JSX.Element | null { ...@@ -180,6 +183,7 @@ function AppOuter(): JSX.Element | null {
storageWrapper: new MMKVWrapper(new MMKV()), storageWrapper: new MMKVWrapper(new MMKV()),
maxCacheSizeInBytes: MAX_CACHE_SIZE_IN_BYTES, maxCacheSizeInBytes: MAX_CACHE_SIZE_IN_BYTES,
customEndpoint, customEndpoint,
reduxStore: store,
}) })
const onReportPrepared = useCallback((report: RenderPassReport) => { const onReportPrepared = useCallback((report: RenderPassReport) => {
......
...@@ -10,6 +10,12 @@ import { ErrorEventMapper } from '@datadog/mobile-react-native/lib/typescript/ru ...@@ -10,6 +10,12 @@ import { ErrorEventMapper } from '@datadog/mobile-react-native/lib/typescript/ru
import { PropsWithChildren, default as React } from 'react' import { PropsWithChildren, default as React } from 'react'
import { getDatadogEnvironment } from 'src/utils/version' import { getDatadogEnvironment } from 'src/utils/version'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import {
DatadogIgnoredErrorsConfigKey,
DatadogIgnoredErrorsValType,
DynamicConfigs,
} from 'uniswap/src/features/gating/configs'
import { getDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { datadogEnabled, isDetoxBuild, isJestRun, localDevDatadogEnabled } from 'utilities/src/environment/constants' import { datadogEnabled, isDetoxBuild, isJestRun, localDevDatadogEnabled } from 'utilities/src/environment/constants'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -29,10 +35,17 @@ Object.assign(datadogConfig, { ...@@ -29,10 +35,17 @@ Object.assign(datadogConfig, {
nativeCrashReportEnabled: true, nativeCrashReportEnabled: true,
verbosity: SdkVerbosity.INFO, verbosity: SdkVerbosity.INFO,
errorEventMapper: (event: ReturnType<ErrorEventMapper>) => { errorEventMapper: (event: ReturnType<ErrorEventMapper>) => {
// this is Sentry error, which is caused by the not complete closing of their SDK const ignoredErrors = getDynamicConfigValue<
if (event?.message.includes('Native is disabled')) { DynamicConfigs.DatadogIgnoredErrors,
return null DatadogIgnoredErrorsConfigKey,
DatadogIgnoredErrorsValType
>(DynamicConfigs.DatadogIgnoredErrors, DatadogIgnoredErrorsConfigKey.Errors, [])
const ignoredError = ignoredErrors.find(({ messageContains }) => event?.message.includes(messageContains))
if (ignoredError) {
return Math.random() < ignoredError.sampleRate ? event : null
} }
return event return event
}, },
}) })
......
import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
export const BUTTON_PADDING = 20 export const BUTTON_PADDING = 20
......
...@@ -13,6 +13,7 @@ import CameraScan from 'ui/src/assets/icons/camera-scan.svg' ...@@ -13,6 +13,7 @@ import CameraScan from 'ui/src/assets/icons/camera-scan.svg'
import { Global, PhotoStacked } from 'ui/src/components/icons' import { Global, PhotoStacked } from 'ui/src/components/icons'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
import { useSporeColorsForTheme } from 'ui/src/hooks/useSporeColors'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import PasteButton from 'uniswap/src/components/buttons/PasteButton' import PasteButton from 'uniswap/src/components/buttons/PasteButton'
import { DevelopmentOnly } from 'wallet/src/components/DevelopmentOnly/DevelopmentOnly' import { DevelopmentOnly } from 'wallet/src/components/DevelopmentOnly/DevelopmentOnly'
...@@ -43,7 +44,8 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -43,7 +44,8 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const isWalletConnectModal = isWalletConnect(props) const isWalletConnectModal = isWalletConnect(props)
const { t } = useTranslation() const { t } = useTranslation()
const colors = useSporeColors() const colors = useSporeColorsForTheme(theme)
const dimensions = useDeviceDimensions() const dimensions = useDeviceDimensions()
const [permissionResponse, requestPermissionResponse] = Camera.useCameraPermissions() const [permissionResponse, requestPermissionResponse] = Camera.useCameraPermissions()
...@@ -121,8 +123,6 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -121,8 +123,6 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const cameraHeight = CAMERA_ASPECT_RATIO * cameraWidth const cameraHeight = CAMERA_ASPECT_RATIO * cameraWidth
const scannerSize = Math.min(overlayWidth, cameraWidth) * SCAN_ICON_WIDTH_RATIO const scannerSize = Math.min(overlayWidth, cameraWidth) * SCAN_ICON_WIDTH_RATIO
const photoSelectBackgroundColor = useSporeColors(theme).surface1
/** /**
* Resets the camera auto focus to force the camera to refocus by toggling * Resets the camera auto focus to force the camera to refocus by toggling
* the auto focus off and on. This allows us to manually let the user refocus * the auto focus off and on. This allows us to manually let the user refocus
...@@ -184,7 +184,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -184,7 +184,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
width="100%" width="100%"
onLayout={(event: LayoutChangeEvent): void => setInfoLayout(event.nativeEvent.layout)} onLayout={(event: LayoutChangeEvent): void => setInfoLayout(event.nativeEvent.layout)}
> >
<Text color="$neutral1" variant="heading3"> <Text color={colors.neutral1.val} variant="heading3">
{t('qrScanner.title')} {t('qrScanner.title')}
</Text> </Text>
</Flex> </Flex>
...@@ -248,7 +248,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -248,7 +248,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
> >
<Flex <Flex
centered centered
backgroundColor={photoSelectBackgroundColor.val} backgroundColor={colors.surface1.val}
borderRadius="$roundedFull" borderRadius="$roundedFull"
p="$spacing12" p="$spacing12"
onPress={onPickImageFilePress} onPress={onPickImageFilePress}
...@@ -263,8 +263,9 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -263,8 +263,9 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
{isWalletConnectModal && props.numConnections > 0 && ( {isWalletConnectModal && props.numConnections > 0 && (
<Button <Button
fontFamily="$body" fontFamily="$body"
icon={<Global color="$neutral2" />} icon={<Global color={colors.neutral2.val} />}
theme="secondary" backgroundColor={colors.surface3.val}
color={colors.neutral1.val}
onPress={props.onPressConnections} onPress={props.onPressConnections}
> >
{t('qrScanner.button.connections', { count: props.numConnections })} {t('qrScanner.button.connections', { count: props.numConnections })}
......
...@@ -4,9 +4,10 @@ import { Alert } from 'react-native' ...@@ -4,9 +4,10 @@ import { Alert } from 'react-native'
import 'react-native-reanimated' import 'react-native-reanimated'
import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner' import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner'
import { getSupportedURI, URIType } from 'src/components/Requests/ScanSheet/util' import { getSupportedURI, URIType } from 'src/components/Requests/ScanSheet/util'
import { Flex, Text, TouchableArea, useIsDarkMode, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import Scan from 'ui/src/assets/icons/receive.svg' import Scan from 'ui/src/assets/icons/receive.svg'
import ScanQRIcon from 'ui/src/assets/icons/scan.svg' import ScanQRIcon from 'ui/src/assets/icons/scan.svg'
import { useSporeColorsForTheme } from 'ui/src/hooks/useSporeColors'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
...@@ -30,9 +31,8 @@ export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.E ...@@ -30,9 +31,8 @@ export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.E
const isScanningQr = currentScreenState === ScannerModalState.ScanQr const isScanningQr = currentScreenState === ScannerModalState.ScanQr
const darkColors = useSporeColors('dark') // We want to always show the QR Code Scanner in "dark mode"
const themeColors = useSporeColors() const colors = useSporeColorsForTheme(isScanningQr ? 'dark' : undefined)
const colors = isScanningQr ? darkColors : themeColors
const onScanCode = async (uri: string): Promise<void> => { const onScanCode = async (uri: string): Promise<void> => {
if (shouldFreezeCamera) { if (shouldFreezeCamera) {
......
...@@ -17,9 +17,10 @@ import { openDeepLink } from 'src/features/deepLinking/handleDeepLinkSaga' ...@@ -17,9 +17,10 @@ import { openDeepLink } from 'src/features/deepLinking/handleDeepLinkSaga'
import { useWalletConnect } from 'src/features/walletConnect/useWalletConnect' import { useWalletConnect } from 'src/features/walletConnect/useWalletConnect'
import { pairWithWalletConnectURI } from 'src/features/walletConnect/utils' import { pairWithWalletConnectURI } from 'src/features/walletConnect/utils'
import { addRequest } from 'src/features/walletConnect/walletConnectSlice' import { addRequest } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, Text, TouchableArea, useIsDarkMode, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import Scan from 'ui/src/assets/icons/receive.svg' import Scan from 'ui/src/assets/icons/receive.svg'
import ScanQRIcon from 'ui/src/assets/icons/scan.svg' import ScanQRIcon from 'ui/src/assets/icons/scan.svg'
import { useSporeColorsForTheme } from 'ui/src/hooks/useSporeColors'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
...@@ -64,9 +65,7 @@ export function WalletConnectModal({ ...@@ -64,9 +65,7 @@ export function WalletConnectModal({
const isScanningQr = currentScreenState === ScannerModalState.ScanQr const isScanningQr = currentScreenState === ScannerModalState.ScanQr
// We want to always show the QR Code Scanner in "dark mode" // We want to always show the QR Code Scanner in "dark mode"
const darkColors = useSporeColors('dark') const colors = useSporeColorsForTheme(isScanningQr ? 'dark' : undefined)
const themeColors = useSporeColors()
const colors = isScanningQr ? darkColors : themeColors
// Update QR scanner states when pending session error alert is shown from WCv2 saga event channel // Update QR scanner states when pending session error alert is shown from WCv2 saga event channel
useEffect(() => { useEffect(() => {
......
...@@ -89,6 +89,7 @@ exports[`TokenItem renders without error 1`] = ` ...@@ -89,6 +89,7 @@ exports[`TokenItem renders without error 1`] = `
</Text> </Text>
</View> </View>
<View <View
pointerEvents="auto"
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
...@@ -116,7 +117,7 @@ exports[`TokenItem renders without error 1`] = ` ...@@ -116,7 +117,7 @@ exports[`TokenItem renders without error 1`] = `
"position": "absolute", "position": "absolute",
"top": "2%", "top": "2%",
"width": "96%", "width": "96%",
"zIndex": -1, "zIndex": 0,
} }
} }
/> />
...@@ -134,6 +135,7 @@ exports[`TokenItem renders without error 1`] = ` ...@@ -134,6 +135,7 @@ exports[`TokenItem renders without error 1`] = `
"aspectRatio": undefined, "aspectRatio": undefined,
"borderRadius": 20, "borderRadius": 20,
"flex": undefined, "flex": undefined,
"zIndex": 1,
} }
} }
testID="img-token-image" testID="img-token-image"
......
...@@ -2,7 +2,7 @@ import { SearchHeader, SearchHeaderKey } from 'src/components/explore/search/typ ...@@ -2,7 +2,7 @@ import { SearchHeader, SearchHeaderKey } from 'src/components/explore/search/typ
import { Coin, Gallery, Person } from 'ui/src/components/icons' import { Coin, Gallery, Person } from 'ui/src/components/icons'
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
export const SEARCH_RESULT_HEADER_KEY: SearchHeaderKey = 'header' export const SEARCH_RESULT_HEADER_KEY: SearchHeaderKey = 'header'
......
import { ReactNavigationPerformanceView } from '@shopify/react-native-performance-navigation' import { ReactNavigationPerformanceView } from '@shopify/react-native-performance-navigation'
import { ForwardedRef, forwardRef, memo, useCallback, useEffect, useMemo, useState } from 'react' import { ForwardedRef, forwardRef, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, LayoutRectangle, RefreshControl } from 'react-native' import { FlatList, LayoutRectangle, RefreshControl } from 'react-native'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
...@@ -23,7 +24,6 @@ import { DynamicConfigs, HomeScreenExploreTokensConfigKey } from 'uniswap/src/fe ...@@ -23,7 +24,6 @@ import { DynamicConfigs, HomeScreenExploreTokensConfigKey } from 'uniswap/src/fe
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks' import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { useAppInsets } from 'uniswap/src/hooks/useAppInsets'
import { useTranslation } from 'uniswap/src/i18n'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid } from 'utilities/src/platform' import { isAndroid } from 'utilities/src/platform'
import { selectHasUsedExplore } from 'wallet/src/features/behaviorHistory/selectors' import { selectHasUsedExplore } from 'wallet/src/features/behaviorHistory/selectors'
......
import React, { PropsWithChildren, useCallback, useMemo } from 'react' import React, { PropsWithChildren, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, ImageBackground } from 'react-native' import { FlatList, ImageBackground } from 'react-native'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
...@@ -12,7 +13,6 @@ import { useCexTransferProviders } from 'uniswap/src/features/fiatOnRamp/useCexT ...@@ -12,7 +13,6 @@ import { useCexTransferProviders } from 'uniswap/src/features/fiatOnRamp/useCexT
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { useTranslation } from 'uniswap/src/i18n'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { ImageUri } from 'wallet/src/features/images/ImageUri' import { ImageUri } from 'wallet/src/features/images/ImageUri'
......
import { SharedEventName } from '@uniswap/analytics-events' import { SharedEventName } from '@uniswap/analytics-events'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { FundWalletModal } from 'src/components/home/introCards/FundWalletModal' import { FundWalletModal } from 'src/components/home/introCards/FundWalletModal'
...@@ -10,7 +11,6 @@ import { AccountType } from 'uniswap/src/features/accounts/types' ...@@ -10,7 +11,6 @@ import { AccountType } from 'uniswap/src/features/accounts/types'
import { ElementName, ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { OnboardingCardLoggingName } from 'uniswap/src/features/telemetry/types' import { OnboardingCardLoggingName } from 'uniswap/src/features/telemetry/types'
import { useTranslation } from 'uniswap/src/i18n'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { MobileScreens, OnboardingScreens, UnitagScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens, OnboardingScreens, UnitagScreens } from 'uniswap/src/types/screens/mobile'
import { import {
......
...@@ -55,7 +55,7 @@ export function CloudPasswordInput(): JSX.Element { ...@@ -55,7 +55,7 @@ export function CloudPasswordInput(): JSX.Element {
function PasswordStrengthText({ strength }: { strength: PasswordStrength }): JSX.Element { function PasswordStrengthText({ strength }: { strength: PasswordStrength }): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const { color } = getPasswordStrengthTextAndColor(strength) const { color } = getPasswordStrengthTextAndColor(t, strength)
const hasPassword = strength !== PasswordStrength.NONE const hasPassword = strength !== PasswordStrength.NONE
let strengthText: string = '' let strengthText: string = ''
......
import { Alert, Platform } from 'react-native' import { Alert, Platform } from 'react-native'
import { call, delay, put, select, takeLatest } from 'typed-redux-saga' import { call, delay, put, select, takeLatest } from 'typed-redux-saga'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { finalizeTransaction } from 'uniswap/src/features/transactions/slice' import { finalizeTransaction } from 'uniswap/src/features/transactions/slice'
import { TransactionStatus, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionStatus, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { openUri } from 'uniswap/src/utils/linking' import { openUri } from 'uniswap/src/utils/linking'
import { isJestRun } from 'utilities/src/environment/constants' import { isJestRun } from 'utilities/src/environment/constants'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -98,7 +98,7 @@ function* maybeRequestAppRating() { ...@@ -98,7 +98,7 @@ function* maybeRequestAppRating() {
// assume it was and mark rating as provided. // assume it was and mark rating as provided.
yield* put(setAppRating({ ratingProvided: true })) yield* put(setAppRating({ ratingProvided: true }))
sendAnalyticsEvent(MobileEventName.AppRating, { sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'store-review', type: 'store-review',
appRatingPromptedMs, appRatingPromptedMs,
appRatingProvidedMs, appRatingProvidedMs,
...@@ -110,7 +110,7 @@ function* maybeRequestAppRating() { ...@@ -110,7 +110,7 @@ function* maybeRequestAppRating() {
if (feedbackSent) { if (feedbackSent) {
yield* put(setAppRating({ feedbackProvided: true })) yield* put(setAppRating({ feedbackProvided: true }))
sendAnalyticsEvent(MobileEventName.AppRating, { sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'feedback-form', type: 'feedback-form',
appRatingPromptedMs, appRatingPromptedMs,
appRatingProvidedMs, appRatingProvidedMs,
...@@ -118,7 +118,7 @@ function* maybeRequestAppRating() { ...@@ -118,7 +118,7 @@ function* maybeRequestAppRating() {
} else { } else {
yield* put(setAppRating({ feedbackProvided: false })) yield* put(setAppRating({ feedbackProvided: false }))
sendAnalyticsEvent(MobileEventName.AppRating, { sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'remind', type: 'remind',
appRatingPromptedMs, appRatingPromptedMs,
appRatingProvidedMs, appRatingProvidedMs,
......
...@@ -5,7 +5,7 @@ import { ...@@ -5,7 +5,7 @@ import {
LocalAuthenticationResult, LocalAuthenticationResult,
} from 'expo-local-authentication' } from 'expo-local-authentication'
import { NativeModulesProxy } from 'expo-modules-core' import { NativeModulesProxy } from 'expo-modules-core'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
const ELA = NativeModulesProxy.ExpoLocalAuthentication const ELA = NativeModulesProxy.ExpoLocalAuthentication
......
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
export const UNISWAP_URL_SCHEME = 'uniswap://' export const UNISWAP_URL_SCHEME = 'uniswap://'
export const UNISWAP_APP_URL_SCHEME = 'uniswap://app'
export const UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM = 'uniswap://wc?uri=' export const UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM = 'uniswap://wc?uri='
export const UNISWAP_URL_SCHEME_SCANTASTIC = 'uniswap://scantastic?' export const UNISWAP_URL_SCHEME_SCANTASTIC = 'uniswap://scantastic?'
export const UNISWAP_WALLETCONNECT_URL = uniswapUrls.appBaseUrl + '/wc?uri=' export const UNISWAP_WALLETCONNECT_URL = uniswapUrls.appBaseUrl + '/wc?uri='
import { DeepLinkAction, parseDeepLinkUrl } from 'src/features/deepLinking/deepLinkUtils'
describe('getDeepLinkAction', () => {
it.each`
url | expected
${'https://app.uniswap.org/app?screen=transaction&fiatOnRamp=true&userAddress=0x123'} | ${DeepLinkAction.UniswapWebLink}
${'uniswap://wc?uri=wc:123@2?relay-protocol=irn&symKey=51e'} | ${DeepLinkAction.WalletConnectAsParam}
${'uniswap://wc:123@2?relay-protocol=irn&symKey=51e'} | ${DeepLinkAction.UniswapWalletConnect}
${'uniswap://widget/#/tokens/ethereum/0x...'} | ${DeepLinkAction.UniswapWidget}
${'uniswap://scantastic?param=value'} | ${DeepLinkAction.Scantastic}
${'uniswap://uwulink?param=value'} | ${DeepLinkAction.UwuLink}
${'https://uniswap.org/app?screen=transaction&fiatOnRamp=true&userAddress=0x123'} | ${DeepLinkAction.ShowTransactionAfterFiatOnRamp}
${'https://uniswap.org/app?screen=transaction&fiatOffRamp=true&userAddress=0x123'} | ${DeepLinkAction.ShowTransactionAfterFiatOffRampScreen}
${'https://uniswap.org/app?screen=transaction&userAddress=0x123'} | ${DeepLinkAction.TransactionScreen}
${'https://uniswap.org/app?screen=swap&userAddress=0x123'} | ${DeepLinkAction.SwapScreen}
${'uniswap://unsupported'} | ${DeepLinkAction.SkipNonWalletConnect}
${'https://uniswap.org/app/wc?uri=wc:123'} | ${DeepLinkAction.UniversalWalletConnectLink}
${'wc:123@2?relay-protocol=irn&symKey=51e'} | ${DeepLinkAction.WalletConnect}
${'https://uniswap.org/app?screen=unknown'} | ${DeepLinkAction.Unknown}
${'uniswap://app/fiatonramp?userAddress=0x123&source=push'} | ${DeepLinkAction.FiatOnRampScreen}
${'uniswap://app/tokendetails?currencyId=10-0x6fd9d7ad17242c41f7131d257212c54a0e816691&source=push'} | ${DeepLinkAction.TokenDetails}
`('url=$url should return expected=$expected', ({ url, expected }) => {
expect(parseDeepLinkUrl(url).action).toEqual(expected)
})
})
This diff is collapsed.
...@@ -6,6 +6,7 @@ import { UniverseChainId } from 'uniswap/src/features/chains/types' ...@@ -6,6 +6,7 @@ import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FiatOffRampMetaData, OffRampTransferDetailsResponse } from 'uniswap/src/features/fiatOnRamp/types' import { FiatOffRampMetaData, OffRampTransferDetailsResponse } from 'uniswap/src/features/fiatOnRamp/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TransactionScreen } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' import { TransactionScreen } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext'
import { forceFetchFiatOnRampTransactions } from 'uniswap/src/features/transactions/slice'
import { CurrencyField } from 'uniswap/src/types/currency' import { CurrencyField } from 'uniswap/src/types/currency'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { createTransactionId } from 'uniswap/src/utils/createTransactionId' import { createTransactionId } from 'uniswap/src/utils/createTransactionId'
...@@ -79,6 +80,7 @@ function* _handleOffRampReturnLink(url: URL) { ...@@ -79,6 +80,7 @@ function* _handleOffRampReturnLink(url: URL) {
sendScreen: TransactionScreen.Review, sendScreen: TransactionScreen.Review,
} }
yield* put(forceFetchFiatOnRampTransactions())
yield* call(navigate, MobileScreens.Home) yield* call(navigate, MobileScreens.Home)
yield* put(openModal({ name: ModalName.Send, initialState: initialSendState })) yield* put(openModal({ name: ModalName.Send, initialState: initialSendState }))
yield* call(dismissInAppBrowser) yield* call(dismissInAppBrowser)
......
import { Linking } from 'react-native' import { Linking } from 'react-native'
import OneSignal, { NotificationReceivedEvent, OpenedEvent } from 'react-native-onesignal' import OneSignal, { NotificationReceivedEvent, OpenedEvent } from 'react-native-onesignal'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import { GQL_QUERIES_TO_REFETCH_ON_TXN_UPDATE } from 'uniswap/src/features/transactions/refetchGQLQueriesSaga' import { GQL_QUERIES_TO_REFETCH_ON_TXN_UPDATE } from 'uniswap/src/features/portfolio/portfolioUpdates/constants'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient' import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient'
......
import React, { Dispatch, SetStateAction, useCallback } from 'react' import React, { Dispatch, SetStateAction, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button } from 'ui/src' import { Button } from 'ui/src'
import { WarningAction } from 'uniswap/src/components/modals/WarningModal/types' import { WarningLabel } from 'uniswap/src/components/modals/WarningModal/types'
import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountType } from 'uniswap/src/features/accounts/types'
import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency'
import { import {
TransactionScreen, TransactionScreen,
useTransactionModalContext, useTransactionModalContext,
...@@ -22,7 +23,12 @@ export function SendFormButton({ ...@@ -22,7 +23,12 @@ export function SendFormButton({
const { t } = useTranslation() const { t } = useTranslation()
const account = useActiveAccountWithThrow() const account = useActiveAccountWithThrow()
const { warnings, recipient, updateSendForm } = useSendContext() const {
warnings,
recipient,
updateSendForm,
derivedSendInfo: { chainId },
} = useSendContext()
const { setScreen, walletNeedsRestore } = useTransactionModalContext() const { setScreen, walletNeedsRestore } = useTransactionModalContext()
const isViewOnlyWallet = account.type === AccountType.Readonly const isViewOnlyWallet = account.type === AccountType.Readonly
...@@ -32,11 +38,9 @@ export function SendFormButton({ ...@@ -32,11 +38,9 @@ export function SendFormButton({
const isBlocked = isActiveBlocked || isRecipientBlocked const isBlocked = isActiveBlocked || isRecipientBlocked
const isBlockedLoading = isActiveBlockedLoading || isRecipientBlockedLoading const isBlockedLoading = isActiveBlockedLoading || isRecipientBlockedLoading
const actionButtonDisabled = const insufficientGasFunds = warnings.warnings.some((warning) => warning.type === WarningLabel.InsufficientGasFunds)
warnings.warnings.some((warning) => warning.action === WarningAction.DisableReview) ||
isBlocked || const actionButtonDisabled = !!warnings.blockingWarning || isBlocked || isBlockedLoading || walletNeedsRestore
isBlockedLoading ||
walletNeedsRestore
const goToNext = useCallback(() => { const goToNext = useCallback(() => {
const txId = createTransactionId() const txId = createTransactionId()
...@@ -52,6 +56,14 @@ export function SendFormButton({ ...@@ -52,6 +56,14 @@ export function SendFormButton({
} }
}, [isViewOnlyWallet, goToNext, setShowViewOnlyModal]) }, [isViewOnlyWallet, goToNext, setShowViewOnlyModal])
const nativeCurrencySymbol = NativeCurrency.onChain(chainId).symbol
const buttonText = insufficientGasFunds
? t('send.warning.insufficientFunds.title', {
currencySymbol: nativeCurrencySymbol,
})
: t('send.button.review')
return ( return (
<Button <Button
disabled={actionButtonDisabled && !isViewOnlyWallet} disabled={actionButtonDisabled && !isViewOnlyWallet}
...@@ -61,7 +73,7 @@ export function SendFormButton({ ...@@ -61,7 +73,7 @@ export function SendFormButton({
testID={TestID.ReviewTransfer} testID={TestID.ReviewTransfer}
onPress={onPressReview} onPress={onPressReview}
> >
{t('send.button.review')} {buttonText}
</Button> </Button>
) )
} }
...@@ -3,7 +3,7 @@ import { dispatchNavigationAction } from 'src/app/navigation/rootNavigation' ...@@ -3,7 +3,7 @@ import { dispatchNavigationAction } from 'src/app/navigation/rootNavigation'
import { call, put, takeEvery } from 'typed-redux-saga' import { call, put, takeEvery } from 'typed-redux-saga'
import { pushNotification } from 'uniswap/src/features/notifications/slice' import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types' import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { restoreMnemonicComplete } from 'wallet/src/features/wallet/slice' import { restoreMnemonicComplete } from 'wallet/src/features/wallet/slice'
......
...@@ -29,7 +29,7 @@ import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/type ...@@ -29,7 +29,7 @@ import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/type
import { getChainLabel } from 'uniswap/src/features/chains/utils' import { getChainLabel } from 'uniswap/src/features/chains/utils'
import { pushNotification } from 'uniswap/src/features/notifications/slice' import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types' import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { EthEvent, EthMethod, WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { EthEvent, EthMethod, WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
......
import { NativeStackScreenProps } from '@react-navigation/native-stack' import { NativeStackScreenProps } from '@react-navigation/native-stack'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { isEnrolledAsync } from 'expo-local-authentication' import { isEnrolledAsync } from 'expo-local-authentication'
import { t } from 'i18next'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { OnboardingStackParamList } from 'src/app/navigation/types' import { OnboardingStackParamList } from 'src/app/navigation/types'
import { SplashScreen } from 'src/features/appLoading/SplashScreen' import { SplashScreen } from 'src/features/appLoading/SplashScreen'
...@@ -36,6 +36,7 @@ type Props = NativeStackScreenProps<OnboardingStackParamList, OnboardingScreens. ...@@ -36,6 +36,7 @@ type Props = NativeStackScreenProps<OnboardingStackParamList, OnboardingScreens.
function useFinishAutomatedRecovery(navigation: Props['navigation']): { function useFinishAutomatedRecovery(navigation: Props['navigation']): {
finishRecovery: (mnemonicId: string, recoveryWalletInfos: RecoveryWalletInfo[]) => void finishRecovery: (mnemonicId: string, recoveryWalletInfos: RecoveryWalletInfo[]) => void
} { } {
const { t } = useTranslation()
const dispatch = useDispatch() const dispatch = useDispatch()
const { setRecoveredImportedAccounts, finishOnboarding } = useOnboardingContext() const { setRecoveredImportedAccounts, finishOnboarding } = useOnboardingContext()
...@@ -58,7 +59,7 @@ function useFinishAutomatedRecovery(navigation: Props['navigation']): { ...@@ -58,7 +59,7 @@ function useFinishAutomatedRecovery(navigation: Props['navigation']): {
}) })
setRecoveredImportedAccounts(accountsToImport) setRecoveredImportedAccounts(accountsToImport)
}, },
[setRecoveredImportedAccounts], [t, setRecoveredImportedAccounts],
) )
const finishRecovery = useCallback( const finishRecovery = useCallback(
......
...@@ -83,7 +83,7 @@ export function ExchangeTransferConnecting({ ...@@ -83,7 +83,7 @@ export function ExchangeTransferConnecting({
}) })
await openUri(widgetUrl).catch(onError) await openUri(widgetUrl).catch(onError)
dispatchAddTransaction() dispatchAddTransaction({ isOffRamp: false })
} }
if (timeoutElapsed && !widgetLoading && widgetData) { if (timeoutElapsed && !widgetLoading && widgetData) {
navigateToWidget(widgetData.widgetUrl).catch(() => undefined) navigateToWidget(widgetData.widgetUrl).catch(() => undefined)
......
...@@ -135,7 +135,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element | ...@@ -135,7 +135,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element |
}, },
) )
} }
dispatchAddTransaction() dispatchAddTransaction({ isOffRamp })
await openUri(widgetUrl).catch(onError) await openUri(widgetUrl).catch(onError)
dispatch(forceFetchFiatOnRampTransactions()) dispatch(forceFetchFiatOnRampTransactions())
} }
......
...@@ -114,7 +114,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX. ...@@ -114,7 +114,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
}) })
const { gqlChains } = useEnabledChains() const { gqlChains } = useEnabledChains()
const { data: nftData } = useNftsTabQuery({ const { data: nftData, loading: areNFsLoading } = useNftsTabQuery({
variables: { variables: {
ownerAddress: activeAccount.address, ownerAddress: activeAccount.address,
first: 1, first: 1,
...@@ -126,12 +126,15 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX. ...@@ -126,12 +126,15 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
}) })
const isNftBalance = !!nftData?.nftBalances?.edges.length const isNftBalance = !!nftData?.nftBalances?.edges.length
const { hasData: isActivity } = useFormattedTransactionDataForActivity({ const { hasData: isActivity, isLoading: isActivityLoading } = useFormattedTransactionDataForActivity({
address: activeAccount.address, address: activeAccount.address,
hideSpamTokens: true, hideSpamTokens: true,
pageSize: 1, pageSize: 1,
}) })
const isTabsDataCacheAvailable = !!balancesById || !!nftData || !!isActivity
const isTabsDataLoaded = isTabsDataCacheAvailable || (!areBalancesLoading && !areNFsLoading && !isActivityLoading)
const isTokenBalances = !!Object.entries(balancesById || {}).length const isTokenBalances = !!Object.entries(balancesById || {}).length
const showEmptyWalletState = !isTokenBalances && !isNftBalance && !isActivity const showEmptyWalletState = !isTokenBalances && !isNftBalance && !isActivity
...@@ -563,13 +566,10 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX. ...@@ -563,13 +566,10 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
const renderTabBar = useCallback( const renderTabBar = useCallback(
(sceneProps: SceneRendererProps) => { (sceneProps: SceneRendererProps) => {
const style: ViewStyle = { width: 'auto' } const style: ViewStyle = { width: 'auto' }
if (!isLayoutReady) {
return null
}
return ( return (
<>
<Animated.View style={headerContainerStyle} onLayout={handleHeaderLayout}>
{contentHeader}
</Animated.View>
{isLayoutReady && (
<Animated.View entering={FadeIn} style={[TAB_STYLES.header, tabBarStyle]}> <Animated.View entering={FadeIn} style={[TAB_STYLES.header, tabBarStyle]}>
<TabBar <TabBar
{...sceneProps} {...sceneProps}
...@@ -588,22 +588,9 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX. ...@@ -588,22 +588,9 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
tabStyle={style} tabStyle={style}
/> />
</Animated.View> </Animated.View>
)}
</>
) )
}, },
[ [colors.surface1, colors.surface3, isLayoutReady, renderTabLabel, routes, tabBarStyle, tabIndex],
colors.surface1,
colors.surface3,
contentHeader,
handleHeaderLayout,
headerContainerStyle,
isLayoutReady,
renderTabLabel,
routes,
tabBarStyle,
tabIndex,
],
) )
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
...@@ -742,6 +729,11 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX. ...@@ -742,6 +729,11 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
<Screen edges={['left', 'right']}> <Screen edges={['left', 'right']}>
{openAIAssistantEnabled && <AIAssistantOverlay />} {openAIAssistantEnabled && <AIAssistantOverlay />}
<View style={TAB_STYLES.container}> <View style={TAB_STYLES.container}>
<Animated.View style={headerContainerStyle} onLayout={handleHeaderLayout}>
{contentHeader}
</Animated.View>
{isTabsDataLoaded && (
<TraceTabView <TraceTabView
lazy lazy
initialLayout={{ initialLayout={{
...@@ -754,6 +746,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX. ...@@ -754,6 +746,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
screenName={MobileScreens.Home} screenName={MobileScreens.Home}
onIndexChange={setRouteTabIndex} onIndexChange={setRouteTabIndex}
/> />
)}
</View> </View>
<NavBar /> <NavBar />
<AnimatedFlex <AnimatedFlex
......
...@@ -13,7 +13,7 @@ import { ONBOARDING_NOTIFICATIONS_DARK, ONBOARDING_NOTIFICATIONS_LIGHT } from 'u ...@@ -13,7 +13,7 @@ import { ONBOARDING_NOTIFICATIONS_DARK, ONBOARDING_NOTIFICATIONS_LIGHT } from 'u
import { BellOn } from 'ui/src/components/icons' import { BellOn } from 'ui/src/components/icons'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n'
import { OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext' import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
......
...@@ -74,12 +74,9 @@ module.exports = { ...@@ -74,12 +74,9 @@ module.exports = {
'error', 'error',
{ {
paths: [ paths: [
{
name: 'react-i18next',
message: 'Import from `uniswap/src/i18n` instead.',
},
{ {
name: 'i18next', name: 'i18next',
importNames: ['i18n'],
message: 'Import from `uniswap/src/i18n` instead.', message: 'Import from `uniswap/src/i18n` instead.',
}, },
{ {
......
...@@ -112,13 +112,33 @@ module.exports = { ...@@ -112,13 +112,33 @@ module.exports = {
webpackConfig.resolve.extensions.unshift('.web.ts') webpackConfig.resolve.extensions.unshift('.web.ts')
webpackConfig.resolve.extensions.unshift('.web.js') webpackConfig.resolve.extensions.unshift('.web.js')
if (isProduction || process.env.UNISWAP_ANALYZE_BUNDLE_SIZE) { if (isProduction) {
// do bundle analysis // Configure bundle analysis based on environment variable
webpackConfig.plugins.push( const analyzerMode = process.env.UNISWAP_ANALYZE_BUNDLE_SIZE === 'static' ? 'static' : 'json'
new BundleAnalyzerPlugin({ const analyzerConfig = {
analyzerMode: 'json', analyzerMode,
...(analyzerMode === 'static' && {
reportFilename: 'report.html',
openAnalyzer: true,
generateStatsFile: true,
statsFilename: 'webpack-stats.json'
}) })
) }
webpackConfig.plugins.push(new BundleAnalyzerPlugin(analyzerConfig))
// Only include stats configuration if not in static analyzer mode
if (process.env.UNISWAP_ANALYZE_BUNDLE_SIZE !== 'static') {
webpackConfig.profile = true
webpackConfig.stats = {
usedExports: true,
optimizationBailout: true,
moduleTrace: true,
reasons: true,
chunks: true,
modules: true,
}
}
} }
// Configure webpack plugins: // Configure webpack plugins:
...@@ -246,6 +266,9 @@ module.exports = { ...@@ -246,6 +266,9 @@ module.exports = {
webpackConfig.module.rules[1].oneOf.unshift({ webpackConfig.module.rules[1].oneOf.unshift({
loader: 'babel-loader', loader: 'babel-loader',
include: (path) => /uniswap\/src.*\.(js|ts)x?$/.test(path), include: (path) => /uniswap\/src.*\.(js|ts)x?$/.test(path),
// Babel transpiles to cjs so any code that requires tree-shaking of it's dependencies
// must be excluded here and processed by swc instead.
exclude: (path) => ['chains'].some(p => path.includes(p)),
options: { options: {
presets: ['module:@react-native/babel-preset'], presets: ['module:@react-native/babel-preset'],
plugins: [ plugins: [
...@@ -279,7 +302,10 @@ module.exports = { ...@@ -279,7 +302,10 @@ module.exports = {
enforce: 'post', enforce: 'post',
test: /node_modules.*\.(js)$/, test: /node_modules.*\.(js)$/,
loader: path.join(__dirname, 'scripts/terser-loader.js'), loader: path.join(__dirname, 'scripts/terser-loader.js'),
options: { compress: true, mangle: false }, options: {
compress: true,
mangle: false,
},
}) })
// Configure webpack optimization: // Configure webpack optimization:
...@@ -287,12 +313,18 @@ module.exports = { ...@@ -287,12 +313,18 @@ module.exports = {
webpackConfig.optimization, webpackConfig.optimization,
isProduction isProduction
? { ? {
usedExports: true,
sideEffects: true,
// Optimize over all chunks, instead of async chunks (the default), so that initial chunks are also included. // Optimize over all chunks, instead of async chunks (the default), so that initial chunks are also included.
splitChunks: { chunks: 'all' }, splitChunks: { chunks: 'all' },
} }
: {} : {}
) )
if (isProduction) {
webpackConfig.mode = 'production'
}
// Configure webpack resolution. webpackConfig.cache is unused with swc-loader, but the resolver can still cache: // Configure webpack resolution. webpackConfig.cache is unused with swc-loader, but the resolver can still cache:
webpackConfig.resolve = Object.assign(webpackConfig.resolve, { unsafeCache: true }) webpackConfig.resolve = Object.assign(webpackConfig.resolve, { unsafeCache: true })
......
...@@ -27,6 +27,8 @@ Testing is done utilizing a custom jest environment as well as Cloudflare's loca ...@@ -27,6 +27,8 @@ Testing is done utilizing a custom jest environment as well as Cloudflare's loca
- Manually run `yarn start:cloud` to setup wrangler on `localhost:3000` and proxy on `localhost:3001` - Manually run `yarn start:cloud` to setup wrangler on `localhost:3000` and proxy on `localhost:3001`
- Run unit tests with `yarn test:cloud` - Run unit tests with `yarn test:cloud`
TODO(WEB-5914): as of 12/19/24, tests pass locally but fail on CI. Notes on investigation in issue
## Deployment ## Deployment
Functions will be deployed to Cloudflare where they will be ran automatically when the appropriate route is hit. Functions will be deployed to Cloudflare where they will be ran automatically when the appropriate route is hit.
......
...@@ -8,16 +8,9 @@ import getFont from '../../../utils/getFont' ...@@ -8,16 +8,9 @@ import getFont from '../../../utils/getFont'
import getNetworkLogoUrl from '../../../utils/getNetworkLogoURL' import getNetworkLogoUrl from '../../../utils/getNetworkLogoURL'
import { getRequest } from '../../../utils/getRequest' import { getRequest } from '../../../utils/getRequest'
function PoolImage({ function UnknownTokenImage({ symbol }: { symbol?: string }) {
token0Image, const ticker = symbol?.slice(0, 3)
token1Image, return (
children,
}: {
token0Image?: string
token1Image?: string
children?: React.ReactNode
}) {
const unknownTokenImage = (
<div <div
style={{ style={{
fontFamily: 'Inter', fontFamily: 'Inter',
...@@ -33,9 +26,28 @@ function PoolImage({ ...@@ -33,9 +26,28 @@ function PoolImage({
clipPath: 'polygon(0 0, 100% 0, 100% 100%, 0% 100%)', clipPath: 'polygon(0 0, 100% 0, 100% 100%, 0% 100%)',
}} }}
> >
UNK {ticker ?? 'UNK'}
</div> </div>
) )
}
function PoolImage({
token0ImageUrl,
token1ImageUrl,
tokenSymbol0,
tokenSymbol1,
children,
}: {
token0ImageUrl?: string
token1ImageUrl?: string
tokenSymbol0?: string
tokenSymbol1?: string
children?: React.ReactNode
}) {
// ImageResponse cannot handle webp images: https://github.com/vercel/satori/issues/273#issuecomment-1296323042
// TODO: remove this check logic once @vercel/og supports webp, which appears to be in-progress https://github.com/vercel/satori/pull/622
const token0Image = token0ImageUrl?.includes('.webp') ? undefined : token0ImageUrl
const token1Image = token1ImageUrl?.includes('.webp') ? undefined : token1ImageUrl
return ( return (
<div <div
...@@ -59,7 +71,7 @@ function PoolImage({ ...@@ -59,7 +71,7 @@ function PoolImage({
}} }}
/> />
) : ( ) : (
unknownTokenImage <UnknownTokenImage symbol={tokenSymbol0} />
)} )}
{token1Image ? ( {token1Image ? (
<div <div
...@@ -75,7 +87,7 @@ function PoolImage({ ...@@ -75,7 +87,7 @@ function PoolImage({
}} }}
/> />
) : ( ) : (
unknownTokenImage <UnknownTokenImage symbol={tokenSymbol1} />
)} )}
{children} {children}
</div> </div>
...@@ -134,7 +146,12 @@ export const onRequest: PagesFunction = async ({ params, request }) => { ...@@ -134,7 +146,12 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
gap: '54px', gap: '54px',
}} }}
> >
<PoolImage token0Image={data.poolData?.token0Image} token1Image={data.poolData?.token1Image}> <PoolImage
token0ImageUrl={data.poolData?.token0Image}
token1ImageUrl={data.poolData?.token1Image}
tokenSymbol0={data.poolData?.token0Symbol}
tokenSymbol1={data.poolData?.token1Symbol}
>
{networkLogo != '' && ( {networkLogo != '' && (
<img <img
src={networkLogo} src={networkLogo}
......
...@@ -30,6 +30,10 @@ export const onRequest: PagesFunction = async ({ params, request }) => { ...@@ -30,6 +30,10 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
const networkLogo = getNetworkLogoUrl(networkName.toUpperCase(), origin) const networkLogo = getNetworkLogoUrl(networkName.toUpperCase(), origin)
// ImageResponse cannot handle webp images: https://github.com/vercel/satori/issues/273#issuecomment-1296323042
// TODO: remove this check logic once @vercel/og supports webp, which appears to be in-progress https://github.com/vercel/satori/pull/622
const ogImage = data.ogImage?.includes('.webp') ? undefined : data.ogImage
return new ImageResponse( return new ImageResponse(
( (
<div <div
...@@ -60,8 +64,8 @@ export const onRequest: PagesFunction = async ({ params, request }) => { ...@@ -60,8 +64,8 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
color: 'white', color: 'white',
}} }}
> >
{data.ogImage ? ( {ogImage ? (
<img src={data.ogImage} width="144px" style={{ borderRadius: '100%' }}> <img src={ogImage} width="144px" style={{ borderRadius: '100%' }}>
{networkLogo != '' && ( {networkLogo != '' && (
<img <img
src={networkLogo} src={networkLogo}
......
...@@ -73,6 +73,17 @@ exports[`should inject metadata for valid pools 1`] = ` ...@@ -73,6 +73,17 @@ exports[`should inject metadata for valid pools 1`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -204,6 +215,17 @@ exports[`should inject metadata for valid pools 2`] = ` ...@@ -204,6 +215,17 @@ exports[`should inject metadata for valid pools 2`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -335,6 +357,17 @@ exports[`should inject metadata for valid pools 3`] = ` ...@@ -335,6 +357,17 @@ exports[`should inject metadata for valid pools 3`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
......
...@@ -73,6 +73,17 @@ exports[`should inject metadata for valid tokens 1`] = ` ...@@ -73,6 +73,17 @@ exports[`should inject metadata for valid tokens 1`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -119,7 +130,7 @@ exports[`should inject metadata for valid tokens 1`] = ` ...@@ -119,7 +130,7 @@ exports[`should inject metadata for valid tokens 1`] = `
} }
} }
</style> </style>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get POL on Uniswap" data-rh="true"><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE" data-rh="true"><meta property="og:image:width" content="1200" data-rh="true"><meta property="og:image:height" content="630" data-rh="true"><meta property="og:image:alt" content="Get POL on Uniswap" data-rh="true"><meta property="og:type" content="website" data-rh="true"><meta property="og:url" content="http://127.0.0.1:3000/explore/tokens/polygon/NATIVE" data-rh="true"><meta property="twitter:card" content="summary_large_image" data-rh="true"><meta property="twitter:title" content="Get POL on Uniswap" data-rh="true"><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE" data-rh="true"><meta property="twitter:image:alt" content="Get POL on Uniswap" data-rh="true"></head> <script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get USDC on Uniswap" data-rh="true"><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" data-rh="true"><meta property="og:image:width" content="1200" data-rh="true"><meta property="og:image:height" content="630" data-rh="true"><meta property="og:image:alt" content="Get USDC on Uniswap" data-rh="true"><meta property="og:type" content="website" data-rh="true"><meta property="og:url" content="http://127.0.0.1:3000/explore/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" data-rh="true"><meta property="twitter:card" content="summary_large_image" data-rh="true"><meta property="twitter:title" content="Get USDC on Uniswap" data-rh="true"><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" data-rh="true"><meta property="twitter:image:alt" content="Get USDC on Uniswap" data-rh="true"></head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
...@@ -204,6 +215,17 @@ exports[`should inject metadata for valid tokens 2`] = ` ...@@ -204,6 +215,17 @@ exports[`should inject metadata for valid tokens 2`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -335,6 +357,17 @@ exports[`should inject metadata for valid tokens 3`] = ` ...@@ -335,6 +357,17 @@ exports[`should inject metadata for valid tokens 3`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -466,6 +499,17 @@ exports[`should inject metadata for valid tokens 4`] = ` ...@@ -466,6 +499,17 @@ exports[`should inject metadata for valid tokens 4`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
......
...@@ -16,7 +16,7 @@ const tokens = [ ...@@ -16,7 +16,7 @@ const tokens = [
{ {
address: NATIVE_CHAIN_ID, address: NATIVE_CHAIN_ID,
network: 'polygon', network: 'polygon',
tokenData: { symbol: 'MATIC' }, tokenData: { symbol: 'POL' },
image: 'http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE', image: 'http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE',
}, },
{ {
......
...@@ -73,6 +73,17 @@ exports[`should inject metadata for valid assets: Azuki 1`] = ` ...@@ -73,6 +73,17 @@ exports[`should inject metadata for valid assets: Azuki 1`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -204,6 +215,17 @@ exports[`should inject metadata for valid assets: Bored Ape Yacht Club 1`] = ` ...@@ -204,6 +215,17 @@ exports[`should inject metadata for valid assets: Bored Ape Yacht Club 1`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -335,6 +357,17 @@ exports[`should inject metadata for valid assets: CryptoPunk 1`] = ` ...@@ -335,6 +357,17 @@ exports[`should inject metadata for valid assets: CryptoPunk 1`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
......
...@@ -73,6 +73,17 @@ exports[`should inject metadata for collections 1`] = ` ...@@ -73,6 +73,17 @@ exports[`should inject metadata for collections 1`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -204,6 +215,17 @@ exports[`should inject metadata for collections 2`] = ` ...@@ -204,6 +215,17 @@ exports[`should inject metadata for collections 2`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
...@@ -335,6 +357,17 @@ exports[`should inject metadata for collections 3`] = ` ...@@ -335,6 +357,17 @@ exports[`should inject metadata for collections 3`] = `
padding: 0; padding: 0;
} }
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button { button {
user-select: none; user-select: none;
} }
......
...@@ -4,6 +4,8 @@ interface TokenData { ...@@ -4,6 +4,8 @@ interface TokenData {
symbol: string symbol: string
} }
interface PoolData { interface PoolData {
token0Symbol?: string
token1Symbol?: string
feeTier: string feeTier: string
protocolVersion: ProtocolVersion protocolVersion: ProtocolVersion
token0Image?: string token0Image?: string
......
...@@ -46,6 +46,8 @@ export default async function getPool(networkName: string, poolAddress: string, ...@@ -46,6 +46,8 @@ export default async function getPool(networkName: string, poolAddress: string,
url, url,
name, name,
poolData: { poolData: {
token0Symbol: token0?.symbol,
token1Symbol: token1?.symbol,
feeTier, feeTier,
protocolVersion, protocolVersion,
token0Image: token0?.project?.logoUrl, token0Image: token0?.project?.logoUrl,
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"start": "craco start", "start": "craco start",
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 REACT_APP_SKIP_CSP=1 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start", "start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 REACT_APP_SKIP_CSP=1 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start",
"build:production": "craco build", "build:production": "craco build",
"build:production:analyze": "UNISWAP_ANALYZE_BUNDLE_SIZE=static craco build",
"analyze": "source-map-explorer 'build/static/js/*.js' --no-border-checks --gzip", "analyze": "source-map-explorer 'build/static/js/*.js' --no-border-checks --gzip",
"serve": "serve build -s -l 3000", "serve": "serve build -s -l 3000",
"format": "../../scripts/prettier.sh", "format": "../../scripts/prettier.sh",
...@@ -243,6 +244,7 @@ ...@@ -243,6 +244,7 @@
"focus-visible": "5.2.0", "focus-visible": "5.2.0",
"framer-motion": "10.17.6", "framer-motion": "10.17.6",
"graphql": "16.6.0", "graphql": "16.6.0",
"i18next": "23.10.0",
"immer": "9.0.6", "immer": "9.0.6",
"jotai": "1.3.7", "jotai": "1.3.7",
"jpeg-js": "0.4.4", "jpeg-js": "0.4.4",
...@@ -264,6 +266,7 @@ ...@@ -264,6 +266,7 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-feather": "2.0.10", "react-feather": "2.0.10",
"react-helmet-async": "2.0.4", "react-helmet-async": "2.0.4",
"react-i18next": "14.1.0",
"react-infinite-scroll-component": "6.1.0", "react-infinite-scroll-component": "6.1.0",
"react-is": "18.2.0", "react-is": "18.2.0",
"react-markdown": "4.3.1", "react-markdown": "4.3.1",
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="8" cy="8" r="8" stroke-width="3" fill="#98A1C0"></circle>
</svg>
...@@ -3,6 +3,7 @@ import { BigNumber } from 'ethers/lib/ethers' ...@@ -3,6 +3,7 @@ import { BigNumber } from 'ethers/lib/ethers'
import { useCurrency, useToken } from 'hooks/Tokens' import { useCurrency, useToken } from 'hooks/Tokens'
import useENSName from 'hooks/useENSName' import useENSName from 'hooks/useENSName'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { Trans } from 'react-i18next'
import { VoteOption } from 'state/governance/types' import { VoteOption } from 'state/governance/types'
import { import {
AddLiquidityV2PoolTransactionInfo, AddLiquidityV2PoolTransactionInfo,
...@@ -27,7 +28,6 @@ import { ...@@ -27,7 +28,6 @@ import {
WrapTransactionInfo, WrapTransactionInfo,
} from 'state/transactions/types' } from 'state/transactions/types'
import { nativeOnChain } from 'uniswap/src/constants/tokens' import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { Trans } from 'uniswap/src/i18n'
function formatAmount(amountRaw: string, decimals: number, sigFigs: number): string { function formatAmount(amountRaw: string, decimals: number, sigFigs: number): string {
return new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs) return new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
......
import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle' import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle'
import { useState } from 'react' import { useState } from 'react'
import { t } from 'uniswap/src/i18n' import { useTranslation } from 'react-i18next'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { analytics, getAnalyticsAtomDirect } from 'utilities/src/telemetry/analytics/analytics' import { analytics, getAnalyticsAtomDirect } from 'utilities/src/telemetry/analytics/analytics'
...@@ -8,6 +8,7 @@ export function AnalyticsToggle() { ...@@ -8,6 +8,7 @@ export function AnalyticsToggle() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [x, setCounter] = useState(0) const [x, setCounter] = useState(0)
const [allowAnalytics, setAllowAnalytics] = useState(true) const [allowAnalytics, setAllowAnalytics] = useState(true)
const { t } = useTranslation()
getAnalyticsAtomDirect(true).then((v: boolean) => setAllowAnalytics(v)) getAnalyticsAtomDirect(true).then((v: boolean) => setAllowAnalytics(v))
......
...@@ -22,6 +22,7 @@ import useENSName from 'hooks/useENSName' ...@@ -22,6 +22,7 @@ import useENSName from 'hooks/useENSName'
import { useIsUniExtensionAvailable } from 'hooks/useUniswapWalletOptions' import { useIsUniExtensionAvailable } from 'hooks/useUniswapWalletOptions'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useOpenModal, useToggleModal } from 'state/application/hooks' import { useOpenModal, useToggleModal } from 'state/application/hooks'
...@@ -34,7 +35,6 @@ import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledCh ...@@ -34,7 +35,6 @@ import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledCh
import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks'
import { Trans, t } from 'uniswap/src/i18n'
import { isPathBlocked } from 'utils/blockedPaths' import { isPathBlocked } from 'utils/blockedPaths'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
...@@ -99,6 +99,7 @@ const PortfolioDrawerContainer = styled(Column)` ...@@ -99,6 +99,7 @@ const PortfolioDrawerContainer = styled(Column)`
export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) { export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) {
const { disconnect } = useDisconnect() const { disconnect } = useDisconnect()
const { ENSName } = useENSName(account) const { ENSName } = useENSName(account)
const { t } = useTranslation()
const navigate = useNavigate() const navigate = useNavigate()
const openReceiveModal = useOpenModal({ name: ApplicationModal.RECEIVE_CRYPTO }) const openReceiveModal = useOpenModal({ name: ApplicationModal.RECEIVE_CRYPTO })
const shouldShowBuyFiatButton = !isPathBlocked('/buy') const shouldShowBuyFiatButton = !isPathBlocked('/buy')
......
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import useCopyClipboard from 'hooks/useCopyClipboard' import useCopyClipboard from 'hooks/useCopyClipboard'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { Trans } from 'react-i18next'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { Trans } from 'uniswap/src/i18n'
const Container = styled.div` const Container = styled.div`
width: 100%; width: 100%;
......
...@@ -2,12 +2,12 @@ import { InterfaceEventName } from '@uniswap/analytics-events' ...@@ -2,12 +2,12 @@ import { InterfaceEventName } from '@uniswap/analytics-events'
import { SlideOutMenu } from 'components/AccountDrawer/SlideOutMenu' import { SlideOutMenu } from 'components/AccountDrawer/SlideOutMenu'
import { MenuColumn, MenuItem } from 'components/AccountDrawer/shared' import { MenuColumn, MenuItem } from 'components/AccountDrawer/shared'
import { useLocationLinkProps } from 'hooks/useLocationLinkProps' import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
import { Trans } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { Language, WEB_SUPPORTED_LANGUAGES } from 'uniswap/src/features/language/constants' import { Language, WEB_SUPPORTED_LANGUAGES } from 'uniswap/src/features/language/constants'
import { useCurrentLanguage, useLanguageInfo } from 'uniswap/src/features/language/hooks' import { useCurrentLanguage, useLanguageInfo } from 'uniswap/src/features/language/hooks'
import { setCurrentLanguage } from 'uniswap/src/features/settings/slice' import { setCurrentLanguage } from 'uniswap/src/features/settings/slice'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans } from 'uniswap/src/i18n'
function LanguageMenuItem({ language }: { language: Language }) { function LanguageMenuItem({ language }: { language: Language }) {
const currentLanguage = useCurrentLanguage() const currentLanguage = useCurrentLanguage()
......
...@@ -4,9 +4,9 @@ import { getLocalCurrencyIcon } from 'constants/localCurrencies' ...@@ -4,9 +4,9 @@ import { getLocalCurrencyIcon } from 'constants/localCurrencies'
import { useLocalCurrencyLinkProps } from 'hooks/useLocalCurrencyLinkProps' import { useLocalCurrencyLinkProps } from 'hooks/useLocalCurrencyLinkProps'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { useMemo } from 'react' import { useMemo } from 'react'
import { Trans } from 'react-i18next'
import { FiatCurrency, ORDERED_CURRENCIES } from 'uniswap/src/features/fiatCurrency/constants' import { FiatCurrency, ORDERED_CURRENCIES } from 'uniswap/src/features/fiatCurrency/constants'
import { useAppFiatCurrency } from 'uniswap/src/features/fiatCurrency/hooks' import { useAppFiatCurrency } from 'uniswap/src/features/fiatCurrency/hooks'
import { Trans } from 'uniswap/src/i18n'
const StyledLocalCurrencyIcon = styled.div` const StyledLocalCurrencyIcon = styled.div`
width: 20px; width: 20px;
......
...@@ -19,6 +19,7 @@ import { ...@@ -19,6 +19,7 @@ import {
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { isHash } from 'viem'
const ActivityRowDescriptor = styled(ThemedText.BodySmall)` const ActivityRowDescriptor = styled(ThemedText.BodySmall)`
color: ${({ theme }) => theme.neutral2}; color: ${({ theme }) => theme.neutral2};
...@@ -57,13 +58,13 @@ export function ActivityRow({ activity }: { activity: Activity }) { ...@@ -57,13 +58,13 @@ export function ActivityRow({ activity }: { activity: Activity }) {
chainId, chainId,
title, title,
descriptor, descriptor,
logos,
otherAccount, otherAccount,
currencies, currencies,
hash, hash,
prefixIconSrc, prefixIconSrc,
suffixIconSrc, suffixIconSrc,
offchainOrderDetails, offchainOrderDetails,
logos,
type, type,
} = activity } = activity
...@@ -82,9 +83,13 @@ export function ActivityRow({ activity }: { activity: Activity }) { ...@@ -82,9 +83,13 @@ export function ActivityRow({ activity }: { activity: Activity }) {
}) })
return return
} }
// Do not allow FOR activity to be opened until confirmed on chain
if (activity.status === TransactionStatus.Pending && !isHash(hash)) {
return
}
window.open(getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION), '_blank') window.open(explorerUrl, '_blank')
}, [activity?.logos, chainId, hash, offchainOrderDetails, openOffchainActivityModal]) }, [activity?.logos, activity.status, explorerUrl, hash, offchainOrderDetails, openOffchainActivityModal])
return ( return (
<Trace <Trace
......
...@@ -41,6 +41,15 @@ jest.mock('components/AccountDrawer/MiniPortfolio/Activity/utils', () => ({ ...@@ -41,6 +41,15 @@ jest.mock('components/AccountDrawer/MiniPortfolio/Activity/utils', () => ({
useCreateCancelTransactionRequest: jest.fn(), useCreateCancelTransactionRequest: jest.fn(),
})) }))
jest.mock('utilities/src/logger/logger', () => ({
logger: {
error: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
debug: jest.fn(),
},
}))
describe('CancelOrdersDialog', () => { describe('CancelOrdersDialog', () => {
it('should render order cancel correctly', async () => { it('should render order cancel correctly', async () => {
const mockOnCancel = jest.fn() const mockOnCancel = jest.fn()
......
...@@ -8,15 +8,15 @@ import Row from 'components/deprecated/Row' ...@@ -8,15 +8,15 @@ import Row from 'components/deprecated/Row'
import { DetailLineItem } from 'components/swap/DetailLineItem' import { DetailLineItem } from 'components/swap/DetailLineItem'
import styled, { useTheme } from 'lib/styled-components' import styled, { useTheme } from 'lib/styled-components'
import { Slash } from 'react-feather' import { Slash } from 'react-feather'
import { Trans, useTranslation } from 'react-i18next'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types' import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
import { ExternalLink, ThemedText } from 'theme/components' import { ExternalLink, ThemedText } from 'theme/components'
import { Flex } from 'ui/src' import { Flex, Text } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { nativeOnChain } from 'uniswap/src/constants/tokens' import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice'
import { Plural, Trans, t } from 'uniswap/src/i18n'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
...@@ -82,6 +82,7 @@ export function CancelOrdersDialog( ...@@ -82,6 +82,7 @@ export function CancelOrdersDialog(
onConfirm: () => void onConfirm: () => void
}, },
) { ) {
const { t } = useTranslation()
const { orders, cancelState, cancelTxHash, onConfirm, onCancel } = props const { orders, cancelState, cancelTxHash, onConfirm, onCancel } = props
const { title, icon } = useCancelOrdersDialogContent(cancelState, orders) const { title, icon } = useCancelOrdersDialogContent(cancelState, orders)
...@@ -128,11 +129,7 @@ export function CancelOrdersDialog( ...@@ -128,11 +129,7 @@ export function CancelOrdersDialog(
title={title} title={title}
description={ description={
<Flex width="100%"> <Flex width="100%">
<Plural <Text>{t('swap.cancel.cannotExecute', { count: orders.length })}</Text>
value={orders.length}
one={t('swap.cancel.cannotExecute')}
other={t('swap.cancel.cannotExecute.plural')}
/>
<GasEstimateDisplay chainId={orders[0].chainId} gasEstimateValue={gasEstimate?.value} /> <GasEstimateDisplay chainId={orders[0].chainId} gasEstimateValue={gasEstimate?.value} />
</Flex> </Flex>
} }
......
...@@ -28,6 +28,7 @@ import { useAtomValue, useUpdateAtom } from 'jotai/utils' ...@@ -28,6 +28,7 @@ import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import styled, { useTheme } from 'lib/styled-components' import styled, { useTheme } from 'lib/styled-components'
import { ReactNode, useCallback, useMemo, useState } from 'react' import { ReactNode, useCallback, useMemo, useState } from 'react'
import { ArrowDown, X } from 'react-feather' import { ArrowDown, X } from 'react-feather'
import { Trans } from 'react-i18next'
import { useOrder } from 'state/signatures/hooks' import { useOrder } from 'state/signatures/hooks'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types' import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
import { Divider, ThemedText } from 'theme/components' import { Divider, ThemedText } from 'theme/components'
...@@ -36,7 +37,6 @@ import { Modal } from 'uniswap/src/components/modals/Modal' ...@@ -36,7 +37,6 @@ import { Modal } from 'uniswap/src/components/modals/Modal'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { InterfaceEventNameLocal, ModalName } from 'uniswap/src/features/telemetry/constants' import { InterfaceEventNameLocal, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans } from 'uniswap/src/i18n'
import { CurrencyField } from 'uniswap/src/types/currency' import { CurrencyField } from 'uniswap/src/types/currency'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -172,7 +172,6 @@ function getOrderTitle(order: UniswapXOrderDetails): ReactNode { ...@@ -172,7 +172,6 @@ function getOrderTitle(order: UniswapXOrderDetails): ReactNode {
export function OrderContent({ export function OrderContent({
order, order,
logos,
onCancel, onCancel,
}: { }: {
order: UniswapXOrderDetails order: UniswapXOrderDetails
...@@ -230,7 +229,6 @@ export function OrderContent({ ...@@ -230,7 +229,6 @@ export function OrderContent({
<PortfolioLogo <PortfolioLogo
chainId={amounts?.inputAmount.currency.chainId ?? UniverseChainId.Mainnet} chainId={amounts?.inputAmount.currency.chainId ?? UniverseChainId.Mainnet}
currencies={currencies} currencies={currencies}
images={[logos?.inputLogo, logos?.outputLogo]}
/> />
<Column> <Column>
<ThemedText.SubHeader fontWeight={500}>{getOrderTitle(order)}</ThemedText.SubHeader> <ThemedText.SubHeader fontWeight={500}>{getOrderTitle(order)}</ThemedText.SubHeader>
......
...@@ -2,9 +2,9 @@ import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core' ...@@ -2,9 +2,9 @@ import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'
import { formatTimestamp } from 'components/AccountDrawer/MiniPortfolio/formatTimestamp' import { formatTimestamp } from 'components/AccountDrawer/MiniPortfolio/formatTimestamp'
import { DetailLineItem, LineItemData } from 'components/swap/DetailLineItem' import { DetailLineItem, LineItemData } from 'components/swap/DetailLineItem'
import TradePrice from 'components/swap/TradePrice' import TradePrice from 'components/swap/TradePrice'
import { Trans } from 'react-i18next'
import { UniswapXOrderDetails } from 'state/signatures/types' import { UniswapXOrderDetails } from 'state/signatures/types'
import { ExternalLink } from 'theme/components' import { ExternalLink } from 'theme/components'
import { Trans } from 'uniswap/src/i18n'
import { ellipseMiddle } from 'utilities/src/addresses' import { ellipseMiddle } from 'utilities/src/addresses'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
......
...@@ -560,8 +560,13 @@ exports[`CancelOrdersDialog should render limit order text 1`] = ` ...@@ -560,8 +560,13 @@ exports[`CancelOrdersDialog should render limit order text 1`] = `
> >
<div <div
class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-column _width-10037" class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-column _width-10037"
>
<span
class="font_body _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _wordWrap-break-word _fontSize-f-size-medi3736 _lineHeight-f-lineHeigh507465454 _fontWeight-f-weight-bo3548"
data-disable-theme="true"
> >
Your swap could execute before cancellation is processed. Your network costs cannot be refunded. Do you wish to proceed? Your swap could execute before cancellation is processed. Your network costs cannot be refunded. Do you wish to proceed?
</span>
<div <div
class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-row _mt-16px _pt-16px _borderTopColor-surface3 _borderRightColor-transparent _borderBottomColor-transparent _borderLeftColor-transparent _borderTopWidth-1px _borderRightWidth-1px _borderBottomWidth-1px _borderLeftWidth-1px _width-10037 _borderBottomStyle-solid _borderTopStyle-solid _borderLeftStyle-solid _borderRightStyle-solid" class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-row _mt-16px _pt-16px _borderTopColor-surface3 _borderRightColor-transparent _borderBottomColor-transparent _borderLeftColor-transparent _borderTopWidth-1px _borderRightWidth-1px _borderBottomWidth-1px _borderLeftWidth-1px _width-10037 _borderBottomStyle-solid _borderTopStyle-solid _borderLeftStyle-solid _borderRightStyle-solid"
> >
...@@ -1179,8 +1184,13 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` ...@@ -1179,8 +1184,13 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = `
> >
<div <div
class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-column _width-10037" class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-column _width-10037"
>
<span
class="font_body _display-inline _boxSizing-border-box _whiteSpace-pre-wrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _wordWrap-break-word _fontSize-f-size-medi3736 _lineHeight-f-lineHeigh507465454 _fontWeight-f-weight-bo3548"
data-disable-theme="true"
> >
Your swap could execute before cancellation is processed. Your network costs cannot be refunded. Do you wish to proceed? Your swap could execute before cancellation is processed. Your network costs cannot be refunded. Do you wish to proceed?
</span>
<div <div
class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-row _mt-16px _pt-16px _borderTopColor-surface3 _borderRightColor-transparent _borderBottomColor-transparent _borderLeftColor-transparent _borderTopWidth-1px _borderRightWidth-1px _borderBottomWidth-1px _borderLeftWidth-1px _width-10037 _borderBottomStyle-solid _borderTopStyle-solid _borderLeftStyle-solid _borderRightStyle-solid" class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-row _mt-16px _pt-16px _borderTopColor-surface3 _borderRightColor-transparent _borderBottomColor-transparent _borderLeftColor-transparent _borderTopWidth-1px _borderRightWidth-1px _borderBottomWidth-1px _borderLeftWidth-1px _width-10037 _borderBottomStyle-solid _borderTopStyle-solid _borderLeftStyle-solid _borderRightStyle-solid"
> >
......
...@@ -37,7 +37,7 @@ import { nativeOnChain } from 'uniswap/src/constants/tokens' ...@@ -37,7 +37,7 @@ import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { t } from 'uniswap/src/i18n' import i18n from 'uniswap/src/i18n'
import { isAddress } from 'utilities/src/addresses' import { isAddress } from 'utilities/src/addresses'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
...@@ -57,25 +57,25 @@ function buildCurrencyDescriptor( ...@@ -57,25 +57,25 @@ function buildCurrencyDescriptor(
input: parseFloat(CurrencyAmount.fromRawAmount(currencyA, amtA).toSignificant()), input: parseFloat(CurrencyAmount.fromRawAmount(currencyA, amtA).toSignificant()),
type: NumberType.TokenNonTx, type: NumberType.TokenNonTx,
}) })
: t('common.unknown') : i18n.t('common.unknown')
const symbolA = currencyA?.symbol ? ` ${currencyA?.symbol}` : '' const symbolA = currencyA?.symbol ? ` ${currencyA?.symbol}` : ''
const formattedB = currencyB const formattedB = currencyB
? formatNumber({ ? formatNumber({
input: parseFloat(CurrencyAmount.fromRawAmount(currencyB, amtB).toSignificant()), input: parseFloat(CurrencyAmount.fromRawAmount(currencyB, amtB).toSignificant()),
type: NumberType.TokenNonTx, type: NumberType.TokenNonTx,
}) })
: t('common.unknown') : i18n.t('common.unknown')
const symbolB = currencyB?.symbol ? ` ${currencyB?.symbol}` : '' const symbolB = currencyB?.symbol ? ` ${currencyB?.symbol}` : ''
const amountWithSymbolA = `${formattedA}${symbolA}` const amountWithSymbolA = `${formattedA}${symbolA}`
const amountWithSymbolB = `${formattedB}${symbolB}` const amountWithSymbolB = `${formattedB}${symbolB}`
return isSwap return isSwap
? t('activity.transaction.swap.descriptor', { ? i18n.t('activity.transaction.swap.descriptor', {
amountWithSymbolA, amountWithSymbolA,
amountWithSymbolB, amountWithSymbolB,
}) })
: t('activity.transaction.tokens.descriptor', { : i18n.t('activity.transaction.tokens.descriptor', {
amountWithSymbolA, amountWithSymbolA,
amountWithSymbolB, amountWithSymbolB,
}) })
...@@ -117,13 +117,13 @@ async function parseBridge( ...@@ -117,13 +117,13 @@ async function parseBridge(
input: parseFloat(CurrencyAmount.fromRawAmount(tokenIn, bridge.inputCurrencyAmountRaw).toSignificant()), input: parseFloat(CurrencyAmount.fromRawAmount(tokenIn, bridge.inputCurrencyAmountRaw).toSignificant()),
type: NumberType.TokenNonTx, type: NumberType.TokenNonTx,
}) })
: t('common.unknown') : i18n.t('common.unknown')
const outputAmount = tokenOut const outputAmount = tokenOut
? formatNumber({ ? formatNumber({
input: parseFloat(CurrencyAmount.fromRawAmount(tokenOut, bridge.outputCurrencyAmountRaw).toSignificant()), input: parseFloat(CurrencyAmount.fromRawAmount(tokenOut, bridge.outputCurrencyAmountRaw).toSignificant()),
type: NumberType.TokenNonTx, type: NumberType.TokenNonTx,
}) })
: t('common.unknown') : i18n.t('common.unknown')
return { return {
descriptor: getBridgeDescriptor({ tokenIn, tokenOut, inputAmount, outputAmount }), descriptor: getBridgeDescriptor({ tokenIn, tokenOut, inputAmount, outputAmount }),
chainId: inputChainId, chainId: inputChainId,
...@@ -163,7 +163,7 @@ async function parseApproval( ...@@ -163,7 +163,7 @@ async function parseApproval(
status: TransactionStatus, status: TransactionStatus,
): Promise<Partial<Activity>> { ): Promise<Partial<Activity>> {
const currency = await getCurrency(approval.tokenAddress, chainId) const currency = await getCurrency(approval.tokenAddress, chainId)
const descriptor = currency?.symbol ?? currency?.name ?? t('common.unknown') const descriptor = currency?.symbol ?? currency?.name ?? i18n.t('common.unknown')
return { return {
title: getActivityTitle( title: getActivityTitle(
TransactionType.APPROVAL, TransactionType.APPROVAL,
...@@ -237,9 +237,9 @@ async function parseMigrateCreateV3( ...@@ -237,9 +237,9 @@ async function parseMigrateCreateV3(
getCurrency(lp.baseCurrencyId, chainId), getCurrency(lp.baseCurrencyId, chainId),
getCurrency(lp.quoteCurrencyId, chainId), getCurrency(lp.quoteCurrencyId, chainId),
]) ])
const baseSymbol = baseCurrency?.symbol ?? t('common.unknown') const baseSymbol = baseCurrency?.symbol ?? i18n.t('common.unknown')
const quoteSymbol = quoteCurrency?.symbol ?? t('common.unknown') const quoteSymbol = quoteCurrency?.symbol ?? i18n.t('common.unknown')
const descriptor = t('activity.transaction.tokens.descriptor', { const descriptor = i18n.t('activity.transaction.tokens.descriptor', {
amountWithSymbolA: baseSymbol, amountWithSymbolA: baseSymbol,
amountWithSymbolB: quoteSymbol, amountWithSymbolB: quoteSymbol,
}) })
...@@ -259,11 +259,11 @@ async function parseSend( ...@@ -259,11 +259,11 @@ async function parseSend(
input: parseFloat(CurrencyAmount.fromRawAmount(currency, amount).toSignificant()), input: parseFloat(CurrencyAmount.fromRawAmount(currency, amount).toSignificant()),
type: NumberType.TokenNonTx, type: NumberType.TokenNonTx,
}) })
: t('common.unknown') : i18n.t('common.unknown')
const otherAccount = isAddress(recipient) || undefined const otherAccount = isAddress(recipient) || undefined
return { return {
descriptor: t('activity.transaction.send.descriptor', { descriptor: i18n.t('activity.transaction.send.descriptor', {
amountWithSymbol: `${formattedAmount} ${currency?.symbol}`, amountWithSymbol: `${formattedAmount} ${currency?.symbol}`,
walletAddress: recipient, walletAddress: recipient,
}), }),
......
...@@ -20,7 +20,7 @@ import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__g ...@@ -20,7 +20,7 @@ import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__g
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants' import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { t } from 'uniswap/src/i18n' import i18n from 'uniswap/src/i18n'
import { getContract } from 'utilities/src/contracts/getContract' import { getContract } from 'utilities/src/contracts/getContract'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
...@@ -85,11 +85,11 @@ export const createGroups = (activities: Array<Activity> = [], hideSpam = false) ...@@ -85,11 +85,11 @@ export const createGroups = (activities: Array<Activity> = [], hideSpam = false)
.map((year) => ({ title: year, transactions: yearMap[year] })) .map((year) => ({ title: year, transactions: yearMap[year] }))
const transactionGroups: Array<ActivityGroup> = [ const transactionGroups: Array<ActivityGroup> = [
{ title: t('common.pending'), transactions: pending.sort(sortActivities) }, { title: i18n.t('common.pending'), transactions: pending.sort(sortActivities) },
{ title: t('common.today'), transactions: today.sort(sortActivities) }, { title: i18n.t('common.today'), transactions: today.sort(sortActivities) },
{ title: t('common.thisWeek'), transactions: currentWeek.sort(sortActivities) }, { title: i18n.t('common.thisWeek'), transactions: currentWeek.sort(sortActivities) },
{ title: t('common.thisMonth'), transactions: last30Days.sort(sortActivities) }, { title: i18n.t('common.thisMonth'), transactions: last30Days.sort(sortActivities) },
{ title: t('common.thisYear'), transactions: currentYear.sort(sortActivities) }, { title: i18n.t('common.thisYear'), transactions: currentYear.sort(sortActivities) },
...sortedYears, ...sortedYears,
] ]
......
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Flex, Text, useIsDarkMode } from 'ui/src' import { Flex, Text, useIsDarkMode } from 'ui/src'
import { CRYPTO_PURCHASE_BACKGROUND_DARK, CRYPTO_PURCHASE_BACKGROUND_LIGHT } from 'ui/src/assets' import { CRYPTO_PURCHASE_BACKGROUND_DARK, CRYPTO_PURCHASE_BACKGROUND_LIGHT } from 'ui/src/assets'
import { ArrowDownCircle } from 'ui/src/components/icons/ArrowDownCircle' import { ArrowDownCircle } from 'ui/src/components/icons/ArrowDownCircle'
import { Buy as BuyIcon } from 'ui/src/components/icons/Buy' import { Buy as BuyIcon } from 'ui/src/components/icons/Buy'
import { ActionCard, ActionCardItem } from 'uniswap/src/components/misc/ActionCard' import { ActionCard, ActionCardItem } from 'uniswap/src/components/misc/ActionCard'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { Trans, t } from 'uniswap/src/i18n'
export const EmptyWallet = ({ export const EmptyWallet = ({
handleBuyCryptoClick, handleBuyCryptoClick,
...@@ -15,6 +15,7 @@ export const EmptyWallet = ({ ...@@ -15,6 +15,7 @@ export const EmptyWallet = ({
handleBuyCryptoClick: () => void handleBuyCryptoClick: () => void
handleReceiveCryptoClick: () => void handleReceiveCryptoClick: () => void
}) => { }) => {
const { t } = useTranslation()
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const BackgroundImageWrapperCallback = useCallback( const BackgroundImageWrapperCallback = useCallback(
...@@ -46,7 +47,7 @@ export const EmptyWallet = ({ ...@@ -46,7 +47,7 @@ export const EmptyWallet = ({
onPress: handleReceiveCryptoClick, onPress: handleReceiveCryptoClick,
}, },
], ],
[BackgroundImageWrapperCallback, handleBuyCryptoClick, handleReceiveCryptoClick], [BackgroundImageWrapperCallback, handleBuyCryptoClick, handleReceiveCryptoClick, t],
) )
return ( return (
......
...@@ -3,8 +3,8 @@ import Row from 'components/deprecated/Row' ...@@ -3,8 +3,8 @@ import Row from 'components/deprecated/Row'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { PropsWithChildren } from 'react' import { PropsWithChildren } from 'react'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
import { useTranslation } from 'react-i18next'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { t } from 'uniswap/src/i18n'
const ExpandIcon = styled(ChevronDown)<{ $expanded: boolean }>` const ExpandIcon = styled(ChevronDown)<{ $expanded: boolean }>`
color: ${({ theme }) => theme.neutral2}; color: ${({ theme }) => theme.neutral2};
...@@ -24,15 +24,17 @@ const ToggleButton = styled(Row)` ...@@ -24,15 +24,17 @@ const ToggleButton = styled(Row)`
} }
` `
const Wrapper = styled(Column)<{ numItems: number; isExpanded: boolean }>` const Wrapper = styled(Column)<{ isExpanded: boolean }>`
height: ${({ numItems, isExpanded }) => (isExpanded ? numItems * 68 + 'px' : 0)}; height: ${({ isExpanded }) => (isExpanded ? '100%' : 0)};
transition: ${({ theme }) => `height ${theme.transition.duration.medium} ease-in-out`}; transition: ${({ theme }) => `height ${theme.transition.duration.medium} ease-in-out`};
overflow: hidden; overflow: hidden;
` `
// TODO(WEB-1982): Replace this component to use `components/Expand` under the hood // TODO(WEB-1982): Replace this component to use `components/Expand` under the hood
type ExpandoRowProps = PropsWithChildren<{ title?: string; numItems: number; isExpanded: boolean; toggle: () => void }> type ExpandoRowProps = PropsWithChildren<{ title?: string; numItems: number; isExpanded: boolean; toggle: () => void }>
export function ExpandoRow({ title = t('common.hidden'), numItems, isExpanded, toggle, children }: ExpandoRowProps) { export function ExpandoRow({ title, numItems, isExpanded, toggle, children }: ExpandoRowProps) {
const { t } = useTranslation()
const titleWithFallback = title ?? t('common.hidden')
if (numItems === 0) { if (numItems === 0) {
return null return null
} }
...@@ -40,7 +42,7 @@ export function ExpandoRow({ title = t('common.hidden'), numItems, isExpanded, t ...@@ -40,7 +42,7 @@ export function ExpandoRow({ title = t('common.hidden'), numItems, isExpanded, t
<> <>
<Row align="center" justify="space-between" padding="16px"> <Row align="center" justify="space-between" padding="16px">
<ThemedText.SubHeader color="neutral2" variant="subheadSmall"> <ThemedText.SubHeader color="neutral2" variant="subheadSmall">
{`${title} (${numItems})`} {`${titleWithFallback} (${numItems})`}
</ThemedText.SubHeader> </ThemedText.SubHeader>
<ToggleButton align="center" onClick={toggle}> <ToggleButton align="center" onClick={toggle}>
<ThemedText.LabelSmall color="neutral2" variant="buttonLabelSmall"> <ThemedText.LabelSmall color="neutral2" variant="buttonLabelSmall">
...@@ -49,9 +51,7 @@ export function ExpandoRow({ title = t('common.hidden'), numItems, isExpanded, t ...@@ -49,9 +51,7 @@ export function ExpandoRow({ title = t('common.hidden'), numItems, isExpanded, t
<ExpandIcon $expanded={isExpanded} /> <ExpandIcon $expanded={isExpanded} />
</ToggleButton> </ToggleButton>
</Row> </Row>
<Wrapper numItems={numItems} isExpanded={isExpanded}> <Wrapper isExpanded={isExpanded}>{children}</Wrapper>
{children}
</Wrapper>
</> </>
) )
} }
...@@ -8,15 +8,14 @@ import { ExtensionRequestMethods, useUniswapExtensionConnector } from 'component ...@@ -8,15 +8,14 @@ import { ExtensionRequestMethods, useUniswapExtensionConnector } from 'component
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { useTheme } from 'lib/styled-components' import { useTheme } from 'lib/styled-components'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Button, Flex, Image, Text } from 'ui/src' import { Button, Flex, Image, Text } from 'ui/src'
import { UNISWAP_LOGO } from 'ui/src/assets' import { UNISWAP_LOGO } from 'ui/src/assets'
import { ArrowRightToLine } from 'ui/src/components/icons/ArrowRightToLine' import { ArrowRightToLine } from 'ui/src/components/icons/ArrowRightToLine'
import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron' import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron'
import { TimePast } from 'ui/src/components/icons/TimePast' import { TimePast } from 'ui/src/components/icons/TimePast'
import { iconSizes } from 'ui/src/theme/iconSizes' import { iconSizes } from 'ui/src/theme/iconSizes'
import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions'
import { t } from 'uniswap/src/i18n'
const UnreadIndicator = () => { const UnreadIndicator = () => {
const theme = useTheme() const theme = useTheme()
...@@ -53,6 +52,7 @@ const DeepLinkButton = ({ Icon, Label, onPress }: { Icon: JSX.Element; Label: st ...@@ -53,6 +52,7 @@ const DeepLinkButton = ({ Icon, Label, onPress }: { Icon: JSX.Element; Label: st
} }
export function ExtensionDeeplinks({ account }: { account: string }) { export function ExtensionDeeplinks({ account }: { account: string }) {
const { t } = useTranslation()
const theme = useTheme() const theme = useTheme()
const uniswapExtensionConnector = useUniswapExtensionConnector() const uniswapExtensionConnector = useUniswapExtensionConnector()
const accountDrawer = useAccountDrawer() const accountDrawer = useAccountDrawer()
......
...@@ -14,10 +14,10 @@ import { useScreenSize } from 'hooks/screenSize/useScreenSize' ...@@ -14,10 +14,10 @@ import { useScreenSize } from 'hooks/screenSize/useScreenSize'
import styled, { useTheme } from 'lib/styled-components' import styled, { useTheme } from 'lib/styled-components'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { ArrowRight } from 'react-feather' import { ArrowRight } from 'react-feather'
import { Trans } from 'react-i18next'
import { EllipsisStyle, ThemedText } from 'theme/components' import { EllipsisStyle, ThemedText } from 'theme/components'
import { UniswapXOrderStatus } from 'types/uniswapx' import { UniswapXOrderStatus } from 'types/uniswapx'
import { Checkbox } from 'ui/src' import { Checkbox } from 'ui/src'
import { Trans } from 'uniswap/src/i18n'
import { useFormatter } from 'utils/formatNumbers' import { useFormatter } from 'utils/formatNumbers'
const StyledPortfolioRow = styled(PortfolioRow)` const StyledPortfolioRow = styled(PortfolioRow)`
......
...@@ -15,8 +15,8 @@ import Column from 'components/deprecated/Column' ...@@ -15,8 +15,8 @@ import Column from 'components/deprecated/Column'
import { LimitDisclaimer } from 'components/swap/LimitDisclaimer' import { LimitDisclaimer } from 'components/swap/LimitDisclaimer'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { UniswapXOrderDetails } from 'state/signatures/types' import { UniswapXOrderDetails } from 'state/signatures/types'
import { Trans, t } from 'uniswap/src/i18n'
const Container = styled(Column)` const Container = styled(Column)`
height: 100%; height: 100%;
...@@ -34,6 +34,7 @@ const StyledLimitsDisclaimer = styled(LimitDisclaimer)` ...@@ -34,6 +34,7 @@ const StyledLimitsDisclaimer = styled(LimitDisclaimer)`
` `
export function LimitsMenu({ onClose, account }: { account: string; onClose: () => void }) { export function LimitsMenu({ onClose, account }: { account: string; onClose: () => void }) {
const { t } = useTranslation()
const { openLimitOrders } = useOpenLimitOrders(account) const { openLimitOrders } = useOpenLimitOrders(account)
const [selectedOrdersByHash, setSelectedOrdersByHash] = useState<Record<string, UniswapXOrderDetails>>({}) const [selectedOrdersByHash, setSelectedOrdersByHash] = useState<Record<string, UniswapXOrderDetails>>({})
const [cancelState, setCancelState] = useState(CancellationState.NOT_STARTED) const [cancelState, setCancelState] = useState(CancellationState.NOT_STARTED)
......
...@@ -2,7 +2,7 @@ import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activ ...@@ -2,7 +2,7 @@ import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activ
import { TabButton } from 'components/AccountDrawer/MiniPortfolio/shared' import { TabButton } from 'components/AccountDrawer/MiniPortfolio/shared'
import { useTheme } from 'lib/styled-components' import { useTheme } from 'lib/styled-components'
import { Clock } from 'react-feather' import { Clock } from 'react-feather'
import { Trans, useTranslation } from 'uniswap/src/i18n' import { Trans, useTranslation } from 'react-i18next'
function getExtraWarning(openLimitOrders: any[]) { function getExtraWarning(openLimitOrders: any[]) {
if (openLimitOrders.length >= 100) { if (openLimitOrders.length >= 100) {
......
...@@ -9,14 +9,14 @@ import { LoaderV2 } from 'components/Icons/LoadingSpinner' ...@@ -9,14 +9,14 @@ import { LoaderV2 } from 'components/Icons/LoadingSpinner'
import Column from 'components/deprecated/Column' import Column from 'components/deprecated/Column'
import { AutoRow } from 'components/deprecated/Row' import { AutoRow } from 'components/deprecated/Row'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
import { useIsNftPage } from 'hooks/useIsNftPage' import { PageType, useIsPage } from 'hooks/useIsPage'
import { atom, useAtom } from 'jotai' import { atom, useAtom } from 'jotai'
import styled, { useTheme } from 'lib/styled-components' import styled, { useTheme } from 'lib/styled-components'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Trans } from 'react-i18next'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { Trans } from 'uniswap/src/i18n'
const lastPageAtom = atom(0) const lastPageAtom = atom(0)
...@@ -99,10 +99,10 @@ const Pages: Array<Page> = [ ...@@ -99,10 +99,10 @@ const Pages: Array<Page> = [
export default function MiniPortfolio({ account }: { account: string }) { export default function MiniPortfolio({ account }: { account: string }) {
const theme = useTheme() const theme = useTheme()
const isNftPage = useIsNftPage() const isNFTPage = useIsPage(PageType.NFTS)
const [lastPage, setLastPage] = useAtom(lastPageAtom) const [lastPage, setLastPage] = useAtom(lastPageAtom)
// Resumes at the last viewed page, unless you are on an NFT page // Resumes at the last viewed page, unless you are on an NFT page
const [currentPage, setCurrentPage] = useState(isNftPage ? 1 : lastPage) const [currentPage, setCurrentPage] = useState(isNFTPage ? 1 : lastPage)
useEffect(() => void setLastPage(currentPage), [currentPage, setLastPage]) useEffect(() => void setLastPage(currentPage), [currentPage, setLastPage])
const shouldDisableNFTRoutes = useDisableNFTRoutes() const shouldDisableNFTRoutes = useDisableNFTRoutes()
......
...@@ -9,6 +9,7 @@ import { NftCard } from 'nft/components/card' ...@@ -9,6 +9,7 @@ import { NftCard } from 'nft/components/card'
import { detailsHref } from 'nft/components/card/utils' import { detailsHref } from 'nft/components/card/utils'
import { VerifiedIcon } from 'nft/components/icons' import { VerifiedIcon } from 'nft/components/icons'
import { WalletAsset } from 'nft/types' import { WalletAsset } from 'nft/types'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { capitalize } from 'tsafe' import { capitalize } from 'tsafe'
...@@ -16,7 +17,6 @@ import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/t ...@@ -16,7 +17,6 @@ import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/t
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { GqlChainId } from 'uniswap/src/features/chains/types' import { GqlChainId } from 'uniswap/src/features/chains/types'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { t } from 'uniswap/src/i18n'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
...@@ -53,6 +53,7 @@ export function NFT({ ...@@ -53,6 +53,7 @@ export function NFT({
mediaShouldBePlaying: boolean mediaShouldBePlaying: boolean
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
}) { }) {
const { t } = useTranslation()
const { isTestnetModeEnabled, gqlChains } = useEnabledChains() const { isTestnetModeEnabled, gqlChains } = useEnabledChains()
const accountDrawer = useAccountDrawer() const accountDrawer = useAccountDrawer()
const navigate = useNavigate() const navigate = useNavigate()
......
...@@ -9,6 +9,7 @@ import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletConten ...@@ -9,6 +9,7 @@ import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletConten
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks' import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { ProfilePageStateType } from 'nft/types' import { ProfilePageStateType } from 'nft/types'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import InfiniteScroll from 'react-infinite-scroll-component' import InfiniteScroll from 'react-infinite-scroll-component'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Gallery } from 'ui/src/components/icons/Gallery' import { Gallery } from 'ui/src/components/icons/Gallery'
...@@ -16,7 +17,6 @@ import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/t ...@@ -16,7 +17,6 @@ import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/t
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { t } from 'uniswap/src/i18n'
const StyledTabButton = styled(TabButton)` const StyledTabButton = styled(TabButton)`
width: calc(100% - 32px); width: calc(100% - 32px);
...@@ -24,6 +24,7 @@ const StyledTabButton = styled(TabButton)` ...@@ -24,6 +24,7 @@ const StyledTabButton = styled(TabButton)`
` `
export default function NFTs({ account }: { account: string }) { export default function NFTs({ account }: { account: string }) {
const { t } = useTranslation()
const accountDrawer = useAccountDrawer() const accountDrawer = useAccountDrawer()
const navigate = useNavigate() const navigate = useNavigate()
const setSellPageState = useProfilePageState((state) => state.setProfilePageState) const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment