ci(release): publish latest release

parent 7bda1988
IPFS hash of the deployment:
- CIDv0: `QmWaBTFEnY4LqKBdeyyRBmwhCuphBhe3p3CKmxnnuNhY5z`
- CIDv1: `bafybeid2kthnxmhk5tcm2ogpsjzooeo3dj6bt2oh6tk3vbrsqf4sc2ogju`
- CIDv0: `Qma3xNtyKF992ESqe25eV3JTR8LGZwuLLx2HyfJ2LwcGRw`
- CIDv1: `bafybeifoavmpqbbjcli2mn7q35itqfnsuiitrvfxvxkikguokyij7cqbhi`
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.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeid2kthnxmhk5tcm2ogpsjzooeo3dj6bt2oh6tk3vbrsqf4sc2ogju.ipfs.dweb.link/
- https://bafybeid2kthnxmhk5tcm2ogpsjzooeo3dj6bt2oh6tk3vbrsqf4sc2ogju.ipfs.cf-ipfs.com/
- [ipfs://QmWaBTFEnY4LqKBdeyyRBmwhCuphBhe3p3CKmxnnuNhY5z/](ipfs://QmWaBTFEnY4LqKBdeyyRBmwhCuphBhe3p3CKmxnnuNhY5z/)
- https://bafybeifoavmpqbbjcli2mn7q35itqfnsuiitrvfxvxkikguokyij7cqbhi.ipfs.dweb.link/
- https://bafybeifoavmpqbbjcli2mn7q35itqfnsuiitrvfxvxkikguokyij7cqbhi.ipfs.cf-ipfs.com/
- [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
* **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
\ No newline at end of file
web/5.64.0
\ 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 { AppearanceSettingType } from 'wallet/src/features/appearance/slice'
import { TextEncoder, TextDecoder } from 'util'
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
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
const ignoreLogs = {
error: [
// We need to use _persist property to ensure that the state is properly
......@@ -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'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
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 { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary'
import { SharedWalletProvider } from 'wallet/src/providers/SharedWalletProvider'
......
......@@ -23,7 +23,7 @@ import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
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 { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger'
......
......@@ -46,7 +46,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
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 { isDevEnv } from 'utilities/src/environment/env'
import { logger } from 'utilities/src/logger/logger'
......
......@@ -31,7 +31,7 @@ import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext'
import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext'
import Trace from 'uniswap/src/features/telemetry/Trace'
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 { logger } from 'utilities/src/logger/logger'
import { usePrevious } from 'utilities/src/react/hooks'
......
import { ApolloProvider } from '@apollo/client'
import { PropsWithChildren } from 'react'
import { localStorage } from 'redux-persist-webextension-storage'
import { getReduxStore } from 'src/store/store'
// eslint-disable-next-line no-restricted-imports
import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient'
......@@ -11,6 +12,7 @@ export function GraphqlProvider({ children }: PropsWithChildren<unknown>): JSX.E
const apolloClient = usePersistedApolloClient({
storageWrapper: localStorage,
maxCacheSizeInBytes: MAX_CACHE_SIZE_IN_BYTES,
reduxStore: getReduxStore(),
})
if (!apolloClient) {
......
import { forwardRef } from 'react'
import { useTranslation } from 'react-i18next'
import { TextInput } from 'react-native'
import { Input, InputProps } from 'src/app/components/Input'
import { Button, Flex, FlexProps, IconProps, Text } from 'ui/src'
......@@ -50,11 +51,12 @@ export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function
})
function StrengthIndicator({ strength }: { strength: PasswordStrength }): JSX.Element | null {
const { t } = useTranslation()
if (strength === PasswordStrength.NONE) {
return null
}
const { text, color } = getPasswordStrengthTextAndColor(strength)
const { text, color } = getPasswordStrengthTextAndColor(t, strength)
return (
<Flex position="absolute" right="$spacing24">
......
......@@ -2,8 +2,14 @@ import { datadogLogs } from '@datadog/browser-logs'
import { datadogRum } from '@datadog/browser-rum'
import { getDatadogEnvironment } from 'src/app/version'
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 { 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 { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger'
......@@ -39,12 +45,24 @@ export async function initializeDatadog(appName: string): Promise<void> {
if (event.error.source === 'console') {
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', {
value: event.error.stack?.replace(/chrome-extension:\/\/[a-z]{32}/gi, ''),
writable: false,
configurable: true,
})
}
return true
},
})
......
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 { Feedback, LikeSquare, MessageText, X } from 'ui/src/components/icons'
import { IconSizeTokens, zIndices } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useTranslation } from 'uniswap/src/i18n'
import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
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'
interface AppRatingModalProps {
......@@ -23,6 +25,26 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
const { t } = useTranslation()
const [state, setState] = useState(State.Initial)
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 = {
[State.Initial]: {
......@@ -42,11 +64,16 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
primaryButtonText: t('appRating.feedback.button.send'),
Icon: MessageText,
iconSize: '$icon.18' as IconSizeTokens,
onSecondaryButtonPress: () => onClose(),
onSecondaryButtonPress: onRemindLater,
onPrimaryButtonPress: (): void => {
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(uniswapUrls.walletFeedbackForm)
dispatch(setAppRating({ feedbackProvided: true }))
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'feedback-form',
appRatingPromptedMs,
appRatingProvidedMs,
})
onClose()
},
},
......@@ -57,11 +84,16 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
primaryButtonText: t('common.button.review'),
Icon: Feedback,
iconSize: '$icon.24' as IconSizeTokens,
onSecondaryButtonPress: () => onClose(),
onSecondaryButtonPress: onRemindLater,
onPrimaryButtonPress: (): void => {
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(`https://chromewebstore.google.com/detail/uniswap-extension/${chrome.runtime.id}/reviews`)
dispatch(setAppRating({ ratingProvided: true }))
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'store-review',
appRatingPromptedMs,
appRatingProvidedMs: Date.now(), // to avoid race condition with updates from redux
})
onClose()
},
},
......@@ -84,8 +116,8 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
}, [dispatch])
return (
<Modal isDismissible isModalOpen name={ModalName.TokenWarningModal} backgroundColor="$surface1" onClose={onClose}>
<TouchableArea p="$spacing16" position="absolute" right={0} top={0} zIndex={zIndices.default} onPress={onClose}>
<Modal isDismissible isModalOpen name={ModalName.TokenWarningModal} backgroundColor="$surface1" onClose={close}>
<TouchableArea p="$spacing16" position="absolute" right={0} top={0} zIndex={zIndices.default} onPress={close}>
<X color="$neutral2" size="$icon.20" />
</TouchableArea>
<Flex alignItems="center" gap="$spacing8" pt="$spacing16">
......
......@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { OnboardingScreenProps } from 'src/app/features/onboarding/OnboardingScreenProps'
import { Button, Flex, Text, TouchableArea } from 'ui/src'
import { BackArrow } from 'ui/src/components/icons'
import i18n from 'uniswap/src/i18n/i18n'
import i18n from 'uniswap/src/i18n'
export function OnboardingScreenFrame({
Icon,
......
......@@ -7,7 +7,7 @@ import { Settings } from 'ui/src/components/icons'
import { Language, WALLET_SUPPORTED_LANGUAGES } from 'uniswap/src/features/language/constants'
import { getLanguageInfo, useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks'
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'
export function DevMenuScreen(): JSX.Element {
......
import { t } from 'i18next'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { ChangePasswordForm } from 'src/app/features/settings/password/ChangePasswordForm'
import { EnterPasswordForm } from 'src/app/features/settings/password/EnterPasswordForm'
......@@ -12,6 +12,7 @@ enum Step {
}
export function SettingsChangePasswordScreen(): JSX.Element {
const { t } = useTranslation()
const [currentStep, setCurrentStep] = useState(Step.EnterPassword)
const { navigateBack } = useExtensionNavigation()
......
import { t } from 'i18next'
import { useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext'
......@@ -16,6 +16,7 @@ import { UnitagChooseProfilePicContent } from 'wallet/src/features/unitags/Unita
import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks'
export function UnitagChooseProfilePicScreen(): JSX.Element {
const { t } = useTranslation()
const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const { unitag, entryPoint, setProfilePicUri } = useUnitagClaimContext()
const address = useAccountAddressFromUrlWithThrow()
......
import { t } from 'i18next'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext'
......@@ -14,6 +14,7 @@ import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/ho
type onNavigateContinueType = Exclude<ClaimUnitagContentProps['onNavigateContinue'], undefined>
export function UnitagCreateUsernameScreen(): JSX.Element {
const { t } = useTranslation()
const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const { setUnitag, setEntryPoint } = useUnitagClaimContext()
const address = useAccountAddressFromUrlWithThrow()
......
......@@ -164,6 +164,7 @@
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 */; };
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 */; };
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 */; };
......@@ -501,6 +502,7 @@
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>"; };
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>"; };
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>"; };
......@@ -771,6 +773,7 @@
91D5016A2CDBEAE700B09B7F /* TokenBalanceQuantityParts.graphql.swift */,
91D501682CDBEAE700B09B7F /* TokenBasicInfoParts.graphql.swift */,
91D501692CDBEAE700B09B7F /* TokenBasicProjectParts.graphql.swift */,
9173CEBB2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift */,
91D5016F2CDBEAE700B09B7F /* TokenFeeDataParts.graphql.swift */,
91D501652CDBEAE700B09B7F /* TokenMarketParts.graphql.swift */,
91D5016C2CDBEAE700B09B7F /* TokenParts.graphql.swift */,
......@@ -1923,6 +1926,7 @@
0DE251482C13B69D005F47F9 /* OnRampServiceProvider.graphql.swift in Sources */,
074322002A83E3CA00F8518D /* AssetChange.graphql.swift in Sources */,
074322232A83E3CA00F8518D /* NftActivityConnection.graphql.swift in Sources */,
9173CEBC2D03C6F30036DA28 /* TokenBalanceParts.graphql.swift in Sources */,
9127D1372CC2D3D00096F134 /* TokenBalanceQuantityParts.graphql.swift in Sources */,
0DE251432C13B674005F47F9 /* OnRampTransactionsAuth.graphql.swift in Sources */,
0743221B2A83E3CA00F8518D /* NftCollectionMarket.graphql.swift in Sources */,
......
......@@ -2,19 +2,14 @@
// For example: https://reactnavigation.org/docs/testing/
import 'core-js' // necessary so setImmediate works in tests
import 'uniswap/src/i18n/i18n' // Uses real translations for tests
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 mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js'
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'
import 'uniswap/src/i18n' // Uses real translations for tests
global.TextEncoder = TextEncoder
global.TextDecoder = TextDecoder
import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js'
jest.mock('@uniswap/client-explore/dist/uniswap/explore/v1/service-ExploreStatsService_connectquery', () => {})
......@@ -78,31 +73,10 @@ jest.mock('@react-navigation/elements', () => ({
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', () => ({
share: jest.fn(),
}))
jest.mock('react-native-localize', () => mockRNLocalize)
jest.mock('@react-native-firebase/auth', () => () => ({
signInAnonymously: jest.fn(),
}))
......@@ -123,21 +97,7 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({
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('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 { loadDevMessages, loadErrorMessages } from '@apollo/client/dev'
import { DdRum, DdSdkReactNative } from '@datadog/mobile-react-native'
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import { PerformanceProfiler, RenderPassReport } from '@shopify/react-native-performance'
......@@ -61,7 +62,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
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 { getUniqueId } from 'utilities/src/device/getUniqueId'
import { isDetoxBuild } from 'utilities/src/environment/constants'
......@@ -71,12 +72,12 @@ import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext'
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
import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient'
import { initFirebaseAppCheck } from 'wallet/src/features/appCheck/appCheck'
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 { WalletUniswapProvider } from 'wallet/src/features/transactions/contexts/WalletUniswapContext'
import { Account } from 'wallet/src/features/wallet/accounts/types'
......@@ -88,6 +89,8 @@ enableFreeze(true)
if (__DEV__) {
registerConsoleOverrides()
loadDevMessages()
loadErrorMessages()
}
// 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 {
storageWrapper: new MMKVWrapper(new MMKV()),
maxCacheSizeInBytes: MAX_CACHE_SIZE_IN_BYTES,
customEndpoint,
reduxStore: store,
})
const onReportPrepared = useCallback((report: RenderPassReport) => {
......
......@@ -10,6 +10,12 @@ import { ErrorEventMapper } from '@datadog/mobile-react-native/lib/typescript/ru
import { PropsWithChildren, default as React } from 'react'
import { getDatadogEnvironment } from 'src/utils/version'
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 { logger } from 'utilities/src/logger/logger'
......@@ -29,10 +35,17 @@ Object.assign(datadogConfig, {
nativeCrashReportEnabled: true,
verbosity: SdkVerbosity.INFO,
errorEventMapper: (event: ReturnType<ErrorEventMapper>) => {
// this is Sentry error, which is caused by the not complete closing of their SDK
if (event?.message.includes('Native is disabled')) {
return null
const ignoredErrors = getDynamicConfigValue<
DynamicConfigs.DatadogIgnoredErrors,
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
},
})
......
import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
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
......
......@@ -13,6 +13,7 @@ import CameraScan from 'ui/src/assets/icons/camera-scan.svg'
import { Global, PhotoStacked } from 'ui/src/components/icons'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
import { useSporeColorsForTheme } from 'ui/src/hooks/useSporeColors'
import { iconSizes, spacing } from 'ui/src/theme'
import PasteButton from 'uniswap/src/components/buttons/PasteButton'
import { DevelopmentOnly } from 'wallet/src/components/DevelopmentOnly/DevelopmentOnly'
......@@ -43,7 +44,8 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const isWalletConnectModal = isWalletConnect(props)
const { t } = useTranslation()
const colors = useSporeColors()
const colors = useSporeColorsForTheme(theme)
const dimensions = useDeviceDimensions()
const [permissionResponse, requestPermissionResponse] = Camera.useCameraPermissions()
......@@ -121,8 +123,6 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const cameraHeight = CAMERA_ASPECT_RATIO * cameraWidth
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
* 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
width="100%"
onLayout={(event: LayoutChangeEvent): void => setInfoLayout(event.nativeEvent.layout)}
>
<Text color="$neutral1" variant="heading3">
<Text color={colors.neutral1.val} variant="heading3">
{t('qrScanner.title')}
</Text>
</Flex>
......@@ -248,7 +248,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
>
<Flex
centered
backgroundColor={photoSelectBackgroundColor.val}
backgroundColor={colors.surface1.val}
borderRadius="$roundedFull"
p="$spacing12"
onPress={onPickImageFilePress}
......@@ -263,8 +263,9 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
{isWalletConnectModal && props.numConnections > 0 && (
<Button
fontFamily="$body"
icon={<Global color="$neutral2" />}
theme="secondary"
icon={<Global color={colors.neutral2.val} />}
backgroundColor={colors.surface3.val}
color={colors.neutral1.val}
onPress={props.onPressConnections}
>
{t('qrScanner.button.connections', { count: props.numConnections })}
......
......@@ -4,9 +4,10 @@ import { Alert } from 'react-native'
import 'react-native-reanimated'
import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner'
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 ScanQRIcon from 'ui/src/assets/icons/scan.svg'
import { useSporeColorsForTheme } from 'ui/src/hooks/useSporeColors'
import { iconSizes } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
......@@ -30,9 +31,8 @@ export function RecipientScanModal({ onSelectRecipient, onClose }: Props): JSX.E
const isScanningQr = currentScreenState === ScannerModalState.ScanQr
const darkColors = useSporeColors('dark')
const themeColors = useSporeColors()
const colors = isScanningQr ? darkColors : themeColors
// We want to always show the QR Code Scanner in "dark mode"
const colors = useSporeColorsForTheme(isScanningQr ? 'dark' : undefined)
const onScanCode = async (uri: string): Promise<void> => {
if (shouldFreezeCamera) {
......
......@@ -17,9 +17,10 @@ import { openDeepLink } from 'src/features/deepLinking/handleDeepLinkSaga'
import { useWalletConnect } from 'src/features/walletConnect/useWalletConnect'
import { pairWithWalletConnectURI } from 'src/features/walletConnect/utils'
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 ScanQRIcon from 'ui/src/assets/icons/scan.svg'
import { useSporeColorsForTheme } from 'ui/src/hooks/useSporeColors'
import { iconSizes } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
......@@ -64,9 +65,7 @@ export function WalletConnectModal({
const isScanningQr = currentScreenState === ScannerModalState.ScanQr
// We want to always show the QR Code Scanner in "dark mode"
const darkColors = useSporeColors('dark')
const themeColors = useSporeColors()
const colors = isScanningQr ? darkColors : themeColors
const colors = useSporeColorsForTheme(isScanningQr ? 'dark' : undefined)
// Update QR scanner states when pending session error alert is shown from WCv2 saga event channel
useEffect(() => {
......
......@@ -89,6 +89,7 @@ exports[`TokenItem renders without error 1`] = `
</Text>
</View>
<View
pointerEvents="auto"
style={
{
"alignItems": "center",
......@@ -116,7 +117,7 @@ exports[`TokenItem renders without error 1`] = `
"position": "absolute",
"top": "2%",
"width": "96%",
"zIndex": -1,
"zIndex": 0,
}
}
/>
......@@ -134,6 +135,7 @@ exports[`TokenItem renders without error 1`] = `
"aspectRatio": undefined,
"borderRadius": 20,
"flex": undefined,
"zIndex": 1,
}
}
testID="img-token-image"
......
......@@ -2,7 +2,7 @@ import { SearchHeader, SearchHeaderKey } from 'src/components/explore/search/typ
import { Coin, Gallery, Person } from 'ui/src/components/icons'
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
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'
......
import { ReactNavigationPerformanceView } from '@shopify/react-native-performance-navigation'
import { ForwardedRef, forwardRef, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, LayoutRectangle, RefreshControl } from 'react-native'
import Animated from 'react-native-reanimated'
import { useSelector } from 'react-redux'
......@@ -23,7 +24,6 @@ import { DynamicConfigs, HomeScreenExploreTokensConfigKey } from 'uniswap/src/fe
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { useAppInsets } from 'uniswap/src/hooks/useAppInsets'
import { useTranslation } from 'uniswap/src/i18n'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid } from 'utilities/src/platform'
import { selectHasUsedExplore } from 'wallet/src/features/behaviorHistory/selectors'
......
import React, { PropsWithChildren, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, ImageBackground } from 'react-native'
import { useDispatch } from 'react-redux'
import { openModal } from 'src/features/modals/modalSlice'
......@@ -12,7 +13,6 @@ import { useCexTransferProviders } from 'uniswap/src/features/fiatOnRamp/useCexT
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { useTranslation } from 'uniswap/src/i18n'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { ImageUri } from 'wallet/src/features/images/ImageUri'
......
import { SharedEventName } from '@uniswap/analytics-events'
import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation'
import { FundWalletModal } from 'src/components/home/introCards/FundWalletModal'
......@@ -10,7 +11,6 @@ import { AccountType } from 'uniswap/src/features/accounts/types'
import { ElementName, ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { OnboardingCardLoggingName } from 'uniswap/src/features/telemetry/types'
import { useTranslation } from 'uniswap/src/i18n'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { MobileScreens, OnboardingScreens, UnitagScreens } from 'uniswap/src/types/screens/mobile'
import {
......
......@@ -55,7 +55,7 @@ export function CloudPasswordInput(): JSX.Element {
function PasswordStrengthText({ strength }: { strength: PasswordStrength }): JSX.Element {
const { t } = useTranslation()
const { color } = getPasswordStrengthTextAndColor(strength)
const { color } = getPasswordStrengthTextAndColor(t, strength)
const hasPassword = strength !== PasswordStrength.NONE
let strengthText: string = ''
......
import { Alert, Platform } from 'react-native'
import { call, delay, put, select, takeLatest } from 'typed-redux-saga'
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 { finalizeTransaction } from 'uniswap/src/features/transactions/slice'
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 { isJestRun } from 'utilities/src/environment/constants'
import { logger } from 'utilities/src/logger/logger'
......@@ -98,7 +98,7 @@ function* maybeRequestAppRating() {
// assume it was and mark rating as provided.
yield* put(setAppRating({ ratingProvided: true }))
sendAnalyticsEvent(MobileEventName.AppRating, {
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'store-review',
appRatingPromptedMs,
appRatingProvidedMs,
......@@ -110,7 +110,7 @@ function* maybeRequestAppRating() {
if (feedbackSent) {
yield* put(setAppRating({ feedbackProvided: true }))
sendAnalyticsEvent(MobileEventName.AppRating, {
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'feedback-form',
appRatingPromptedMs,
appRatingProvidedMs,
......@@ -118,7 +118,7 @@ function* maybeRequestAppRating() {
} else {
yield* put(setAppRating({ feedbackProvided: false }))
sendAnalyticsEvent(MobileEventName.AppRating, {
sendAnalyticsEvent(WalletEventName.AppRating, {
type: 'remind',
appRatingPromptedMs,
appRatingProvidedMs,
......
......@@ -5,7 +5,7 @@ import {
LocalAuthenticationResult,
} from 'expo-local-authentication'
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'
const ELA = NativeModulesProxy.ExpoLocalAuthentication
......
import { uniswapUrls } from 'uniswap/src/constants/urls'
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_SCANTASTIC = 'uniswap://scantastic?'
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'
import { FiatOffRampMetaData, OffRampTransferDetailsResponse } from 'uniswap/src/features/fiatOnRamp/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TransactionScreen } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext'
import { forceFetchFiatOnRampTransactions } from 'uniswap/src/features/transactions/slice'
import { CurrencyField } from 'uniswap/src/types/currency'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { createTransactionId } from 'uniswap/src/utils/createTransactionId'
......@@ -79,6 +80,7 @@ function* _handleOffRampReturnLink(url: URL) {
sendScreen: TransactionScreen.Review,
}
yield* put(forceFetchFiatOnRampTransactions())
yield* call(navigate, MobileScreens.Home)
yield* put(openModal({ name: ModalName.Send, initialState: initialSendState }))
yield* call(dismissInAppBrowser)
......
import { Linking } from 'react-native'
import OneSignal, { NotificationReceivedEvent, OpenedEvent } from 'react-native-onesignal'
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 { ONE_SECOND_MS } from 'utilities/src/time/time'
import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient'
......
import React, { Dispatch, SetStateAction, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
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 { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency'
import {
TransactionScreen,
useTransactionModalContext,
......@@ -22,7 +23,12 @@ export function SendFormButton({
const { t } = useTranslation()
const account = useActiveAccountWithThrow()
const { warnings, recipient, updateSendForm } = useSendContext()
const {
warnings,
recipient,
updateSendForm,
derivedSendInfo: { chainId },
} = useSendContext()
const { setScreen, walletNeedsRestore } = useTransactionModalContext()
const isViewOnlyWallet = account.type === AccountType.Readonly
......@@ -32,11 +38,9 @@ export function SendFormButton({
const isBlocked = isActiveBlocked || isRecipientBlocked
const isBlockedLoading = isActiveBlockedLoading || isRecipientBlockedLoading
const actionButtonDisabled =
warnings.warnings.some((warning) => warning.action === WarningAction.DisableReview) ||
isBlocked ||
isBlockedLoading ||
walletNeedsRestore
const insufficientGasFunds = warnings.warnings.some((warning) => warning.type === WarningLabel.InsufficientGasFunds)
const actionButtonDisabled = !!warnings.blockingWarning || isBlocked || isBlockedLoading || walletNeedsRestore
const goToNext = useCallback(() => {
const txId = createTransactionId()
......@@ -52,6 +56,14 @@ export function SendFormButton({
}
}, [isViewOnlyWallet, goToNext, setShowViewOnlyModal])
const nativeCurrencySymbol = NativeCurrency.onChain(chainId).symbol
const buttonText = insufficientGasFunds
? t('send.warning.insufficientFunds.title', {
currencySymbol: nativeCurrencySymbol,
})
: t('send.button.review')
return (
<Button
disabled={actionButtonDisabled && !isViewOnlyWallet}
......@@ -61,7 +73,7 @@ export function SendFormButton({
testID={TestID.ReviewTransfer}
onPress={onPressReview}
>
{t('send.button.review')}
{buttonText}
</Button>
)
}
......@@ -3,7 +3,7 @@ import { dispatchNavigationAction } from 'src/app/navigation/rootNavigation'
import { call, put, takeEvery } from 'typed-redux-saga'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
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 { restoreMnemonicComplete } from 'wallet/src/features/wallet/slice'
......
......@@ -29,7 +29,7 @@ import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/type
import { getChainLabel } from 'uniswap/src/features/chains/utils'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
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 { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
......
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import dayjs from 'dayjs'
import { isEnrolledAsync } from 'expo-local-authentication'
import { t } from 'i18next'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { OnboardingStackParamList } from 'src/app/navigation/types'
import { SplashScreen } from 'src/features/appLoading/SplashScreen'
......@@ -36,6 +36,7 @@ type Props = NativeStackScreenProps<OnboardingStackParamList, OnboardingScreens.
function useFinishAutomatedRecovery(navigation: Props['navigation']): {
finishRecovery: (mnemonicId: string, recoveryWalletInfos: RecoveryWalletInfo[]) => void
} {
const { t } = useTranslation()
const dispatch = useDispatch()
const { setRecoveredImportedAccounts, finishOnboarding } = useOnboardingContext()
......@@ -58,7 +59,7 @@ function useFinishAutomatedRecovery(navigation: Props['navigation']): {
})
setRecoveredImportedAccounts(accountsToImport)
},
[setRecoveredImportedAccounts],
[t, setRecoveredImportedAccounts],
)
const finishRecovery = useCallback(
......
......@@ -83,7 +83,7 @@ export function ExchangeTransferConnecting({
})
await openUri(widgetUrl).catch(onError)
dispatchAddTransaction()
dispatchAddTransaction({ isOffRamp: false })
}
if (timeoutElapsed && !widgetLoading && widgetData) {
navigateToWidget(widgetData.widgetUrl).catch(() => undefined)
......
......@@ -135,7 +135,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element |
},
)
}
dispatchAddTransaction()
dispatchAddTransaction({ isOffRamp })
await openUri(widgetUrl).catch(onError)
dispatch(forceFetchFiatOnRampTransactions())
}
......
......@@ -114,7 +114,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
})
const { gqlChains } = useEnabledChains()
const { data: nftData } = useNftsTabQuery({
const { data: nftData, loading: areNFsLoading } = useNftsTabQuery({
variables: {
ownerAddress: activeAccount.address,
first: 1,
......@@ -126,12 +126,15 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
})
const isNftBalance = !!nftData?.nftBalances?.edges.length
const { hasData: isActivity } = useFormattedTransactionDataForActivity({
const { hasData: isActivity, isLoading: isActivityLoading } = useFormattedTransactionDataForActivity({
address: activeAccount.address,
hideSpamTokens: true,
pageSize: 1,
})
const isTabsDataCacheAvailable = !!balancesById || !!nftData || !!isActivity
const isTabsDataLoaded = isTabsDataCacheAvailable || (!areBalancesLoading && !areNFsLoading && !isActivityLoading)
const isTokenBalances = !!Object.entries(balancesById || {}).length
const showEmptyWalletState = !isTokenBalances && !isNftBalance && !isActivity
......@@ -563,47 +566,31 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
const renderTabBar = useCallback(
(sceneProps: SceneRendererProps) => {
const style: ViewStyle = { width: 'auto' }
if (!isLayoutReady) {
return null
}
return (
<>
<Animated.View style={headerContainerStyle} onLayout={handleHeaderLayout}>
{contentHeader}
</Animated.View>
{isLayoutReady && (
<Animated.View entering={FadeIn} style={[TAB_STYLES.header, tabBarStyle]}>
<TabBar
{...sceneProps}
indicatorStyle={TAB_STYLES.activeTabIndicator}
navigationState={{ index: tabIndex, routes }}
pressColor="transparent" // Android only
renderLabel={renderTabLabel}
style={[
TAB_STYLES.tabBar,
{
backgroundColor: colors.surface1.get(),
borderBottomColor: colors.surface3.get(),
paddingLeft: spacing.spacing12,
},
]}
tabStyle={style}
/>
</Animated.View>
)}
</>
<Animated.View entering={FadeIn} style={[TAB_STYLES.header, tabBarStyle]}>
<TabBar
{...sceneProps}
indicatorStyle={TAB_STYLES.activeTabIndicator}
navigationState={{ index: tabIndex, routes }}
pressColor="transparent" // Android only
renderLabel={renderTabLabel}
style={[
TAB_STYLES.tabBar,
{
backgroundColor: colors.surface1.get(),
borderBottomColor: colors.surface3.get(),
paddingLeft: spacing.spacing12,
},
]}
tabStyle={style}
/>
</Animated.View>
)
},
[
colors.surface1,
colors.surface3,
contentHeader,
handleHeaderLayout,
headerContainerStyle,
isLayoutReady,
renderTabLabel,
routes,
tabBarStyle,
tabIndex,
],
[colors.surface1, colors.surface3, isLayoutReady, renderTabLabel, routes, tabBarStyle, tabIndex],
)
const [refreshing, setRefreshing] = useState(false)
......@@ -742,18 +729,24 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
<Screen edges={['left', 'right']}>
{openAIAssistantEnabled && <AIAssistantOverlay />}
<View style={TAB_STYLES.container}>
<TraceTabView
lazy
initialLayout={{
height: dimensions.fullHeight,
width: dimensions.fullWidth,
}}
navigationState={{ index: tabIndex, routes }}
renderScene={renderTab}
renderTabBar={renderTabBar}
screenName={MobileScreens.Home}
onIndexChange={setRouteTabIndex}
/>
<Animated.View style={headerContainerStyle} onLayout={handleHeaderLayout}>
{contentHeader}
</Animated.View>
{isTabsDataLoaded && (
<TraceTabView
lazy
initialLayout={{
height: dimensions.fullHeight,
width: dimensions.fullWidth,
}}
navigationState={{ index: tabIndex, routes }}
renderScene={renderTab}
renderTabBar={renderTabBar}
screenName={MobileScreens.Home}
onIndexChange={setRouteTabIndex}
/>
)}
</View>
<NavBar />
<AnimatedFlex
......
......@@ -13,7 +13,7 @@ import { ONBOARDING_NOTIFICATIONS_DARK, ONBOARDING_NOTIFICATIONS_LIGHT } from 'u
import { BellOn } from 'ui/src/components/icons'
import Trace from 'uniswap/src/features/telemetry/Trace'
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 { OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
......
......@@ -74,12 +74,9 @@ module.exports = {
'error',
{
paths: [
{
name: 'react-i18next',
message: 'Import from `uniswap/src/i18n` instead.',
},
{
name: 'i18next',
importNames: ['i18n'],
message: 'Import from `uniswap/src/i18n` instead.',
},
{
......
......@@ -112,13 +112,33 @@ module.exports = {
webpackConfig.resolve.extensions.unshift('.web.ts')
webpackConfig.resolve.extensions.unshift('.web.js')
if (isProduction || process.env.UNISWAP_ANALYZE_BUNDLE_SIZE) {
// do bundle analysis
webpackConfig.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'json',
if (isProduction) {
// Configure bundle analysis based on environment variable
const analyzerMode = process.env.UNISWAP_ANALYZE_BUNDLE_SIZE === 'static' ? 'static' : 'json'
const analyzerConfig = {
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:
......@@ -246,6 +266,9 @@ module.exports = {
webpackConfig.module.rules[1].oneOf.unshift({
loader: 'babel-loader',
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: {
presets: ['module:@react-native/babel-preset'],
plugins: [
......@@ -279,7 +302,10 @@ module.exports = {
enforce: 'post',
test: /node_modules.*\.(js)$/,
loader: path.join(__dirname, 'scripts/terser-loader.js'),
options: { compress: true, mangle: false },
options: {
compress: true,
mangle: false,
},
})
// Configure webpack optimization:
......@@ -287,12 +313,18 @@ module.exports = {
webpackConfig.optimization,
isProduction
? {
usedExports: true,
sideEffects: true,
// Optimize over all chunks, instead of async chunks (the default), so that initial chunks are also included.
splitChunks: { chunks: 'all' },
}
: {}
)
if (isProduction) {
webpackConfig.mode = 'production'
}
// Configure webpack resolution. webpackConfig.cache is unused with swc-loader, but the resolver can still cache:
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
- Manually run `yarn start:cloud` to setup wrangler on `localhost:3000` and proxy on `localhost:3001`
- 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
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'
import getNetworkLogoUrl from '../../../utils/getNetworkLogoURL'
import { getRequest } from '../../../utils/getRequest'
function PoolImage({
token0Image,
token1Image,
children,
}: {
token0Image?: string
token1Image?: string
children?: React.ReactNode
}) {
const unknownTokenImage = (
function UnknownTokenImage({ symbol }: { symbol?: string }) {
const ticker = symbol?.slice(0, 3)
return (
<div
style={{
fontFamily: 'Inter',
......@@ -33,9 +26,28 @@ function PoolImage({
clipPath: 'polygon(0 0, 100% 0, 100% 100%, 0% 100%)',
}}
>
UNK
{ticker ?? 'UNK'}
</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 (
<div
......@@ -59,7 +71,7 @@ function PoolImage({
}}
/>
) : (
unknownTokenImage
<UnknownTokenImage symbol={tokenSymbol0} />
)}
{token1Image ? (
<div
......@@ -75,7 +87,7 @@ function PoolImage({
}}
/>
) : (
unknownTokenImage
<UnknownTokenImage symbol={tokenSymbol1} />
)}
{children}
</div>
......@@ -134,7 +146,12 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
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 != '' && (
<img
src={networkLogo}
......
......@@ -30,6 +30,10 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
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(
(
<div
......@@ -60,8 +64,8 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
color: 'white',
}}
>
{data.ogImage ? (
<img src={data.ogImage} width="144px" style={{ borderRadius: '100%' }}>
{ogImage ? (
<img src={ogImage} width="144px" style={{ borderRadius: '100%' }}>
{networkLogo != '' && (
<img
src={networkLogo}
......
......@@ -73,6 +73,17 @@ exports[`should inject metadata for valid pools 1`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -204,6 +215,17 @@ exports[`should inject metadata for valid pools 2`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -335,6 +357,17 @@ exports[`should inject metadata for valid pools 3`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......
......@@ -73,6 +73,17 @@ exports[`should inject metadata for valid tokens 1`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -119,7 +130,7 @@ exports[`should inject metadata for valid tokens 1`] = `
}
}
</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>
<noscript>You need to enable JavaScript to run this app.</noscript>
......@@ -204,6 +215,17 @@ exports[`should inject metadata for valid tokens 2`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -335,6 +357,17 @@ exports[`should inject metadata for valid tokens 3`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -466,6 +499,17 @@ exports[`should inject metadata for valid tokens 4`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......
......@@ -16,7 +16,7 @@ const tokens = [
{
address: NATIVE_CHAIN_ID,
network: 'polygon',
tokenData: { symbol: 'MATIC' },
tokenData: { symbol: 'POL' },
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`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -204,6 +215,17 @@ exports[`should inject metadata for valid assets: Bored Ape Yacht Club 1`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -335,6 +357,17 @@ exports[`should inject metadata for valid assets: CryptoPunk 1`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......
......@@ -73,6 +73,17 @@ exports[`should inject metadata for collections 1`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -204,6 +215,17 @@ exports[`should inject metadata for collections 2`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......@@ -335,6 +357,17 @@ exports[`should inject metadata for collections 3`] = `
padding: 0;
}
/* Only apply overflow-x: hidden on desktop */
/* This is to prevent ugly horizontal scrollbar from appearing on desktop */
/* We need to set it on html element specifically because otherwise we break */
/* sticky positioning of some child elements. */
/* Applying this on mobile breaks tamagui/remove-scroll. */
@media (min-width: 768px) {
html {
overflow-x: hidden;
}
}
button {
user-select: none;
}
......
......@@ -4,6 +4,8 @@ interface TokenData {
symbol: string
}
interface PoolData {
token0Symbol?: string
token1Symbol?: string
feeTier: string
protocolVersion: ProtocolVersion
token0Image?: string
......
......@@ -46,6 +46,8 @@ export default async function getPool(networkName: string, poolAddress: string,
url,
name,
poolData: {
token0Symbol: token0?.symbol,
token1Symbol: token1?.symbol,
feeTier,
protocolVersion,
token0Image: token0?.project?.logoUrl,
......
......@@ -11,6 +11,7 @@
"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",
"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",
"serve": "serve build -s -l 3000",
"format": "../../scripts/prettier.sh",
......@@ -243,6 +244,7 @@
"focus-visible": "5.2.0",
"framer-motion": "10.17.6",
"graphql": "16.6.0",
"i18next": "23.10.0",
"immer": "9.0.6",
"jotai": "1.3.7",
"jpeg-js": "0.4.4",
......@@ -264,6 +266,7 @@
"react-dom": "18.2.0",
"react-feather": "2.0.10",
"react-helmet-async": "2.0.4",
"react-i18next": "14.1.0",
"react-infinite-scroll-component": "6.1.0",
"react-is": "18.2.0",
"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'
import { useCurrency, useToken } from 'hooks/Tokens'
import useENSName from 'hooks/useENSName'
import JSBI from 'jsbi'
import { Trans } from 'react-i18next'
import { VoteOption } from 'state/governance/types'
import {
AddLiquidityV2PoolTransactionInfo,
......@@ -27,7 +28,6 @@ import {
WrapTransactionInfo,
} from 'state/transactions/types'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { Trans } from 'uniswap/src/i18n'
function formatAmount(amountRaw: string, decimals: number, sigFigs: number): string {
return new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
......
import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle'
import { useState } from 'react'
import { t } from 'uniswap/src/i18n'
import { useTranslation } from 'react-i18next'
// eslint-disable-next-line no-restricted-imports
import { analytics, getAnalyticsAtomDirect } from 'utilities/src/telemetry/analytics/analytics'
......@@ -8,6 +8,7 @@ export function AnalyticsToggle() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [x, setCounter] = useState(0)
const [allowAnalytics, setAllowAnalytics] = useState(true)
const { t } = useTranslation()
getAnalyticsAtomDirect(true).then((v: boolean) => setAllowAnalytics(v))
......
......@@ -22,6 +22,7 @@ import useENSName from 'hooks/useENSName'
import { useIsUniExtensionAvailable } from 'hooks/useUniswapWalletOptions'
import styled from 'lib/styled-components'
import { useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom'
import { useOpenModal, useToggleModal } from 'state/application/hooks'
......@@ -34,7 +35,6 @@ import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledCh
import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks'
import { Trans, t } from 'uniswap/src/i18n'
import { isPathBlocked } from 'utils/blockedPaths'
import { NumberType, useFormatter } from 'utils/formatNumbers'
......@@ -99,6 +99,7 @@ const PortfolioDrawerContainer = styled(Column)`
export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) {
const { disconnect } = useDisconnect()
const { ENSName } = useENSName(account)
const { t } = useTranslation()
const navigate = useNavigate()
const openReceiveModal = useOpenModal({ name: ApplicationModal.RECEIVE_CRYPTO })
const shouldShowBuyFiatButton = !isPathBlocked('/buy')
......
import Tooltip from 'components/Tooltip'
import useCopyClipboard from 'hooks/useCopyClipboard'
import styled from 'lib/styled-components'
import { Trans } from 'react-i18next'
import { ThemedText } from 'theme/components'
import { Trans } from 'uniswap/src/i18n'
const Container = styled.div`
width: 100%;
......
......@@ -2,12 +2,12 @@ import { InterfaceEventName } from '@uniswap/analytics-events'
import { SlideOutMenu } from 'components/AccountDrawer/SlideOutMenu'
import { MenuColumn, MenuItem } from 'components/AccountDrawer/shared'
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
import { Trans } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { Language, WEB_SUPPORTED_LANGUAGES } from 'uniswap/src/features/language/constants'
import { useCurrentLanguage, useLanguageInfo } from 'uniswap/src/features/language/hooks'
import { setCurrentLanguage } from 'uniswap/src/features/settings/slice'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans } from 'uniswap/src/i18n'
function LanguageMenuItem({ language }: { language: Language }) {
const currentLanguage = useCurrentLanguage()
......
......@@ -4,9 +4,9 @@ import { getLocalCurrencyIcon } from 'constants/localCurrencies'
import { useLocalCurrencyLinkProps } from 'hooks/useLocalCurrencyLinkProps'
import styled from 'lib/styled-components'
import { useMemo } from 'react'
import { Trans } from 'react-i18next'
import { FiatCurrency, ORDERED_CURRENCIES } from 'uniswap/src/features/fiatCurrency/constants'
import { useAppFiatCurrency } from 'uniswap/src/features/fiatCurrency/hooks'
import { Trans } from 'uniswap/src/i18n'
const StyledLocalCurrencyIcon = styled.div`
width: 20px;
......
......@@ -19,6 +19,7 @@ import {
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { isHash } from 'viem'
const ActivityRowDescriptor = styled(ThemedText.BodySmall)`
color: ${({ theme }) => theme.neutral2};
......@@ -57,13 +58,13 @@ export function ActivityRow({ activity }: { activity: Activity }) {
chainId,
title,
descriptor,
logos,
otherAccount,
currencies,
hash,
prefixIconSrc,
suffixIconSrc,
offchainOrderDetails,
logos,
type,
} = activity
......@@ -82,9 +83,13 @@ export function ActivityRow({ activity }: { activity: Activity }) {
})
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')
}, [activity?.logos, chainId, hash, offchainOrderDetails, openOffchainActivityModal])
window.open(explorerUrl, '_blank')
}, [activity?.logos, activity.status, explorerUrl, hash, offchainOrderDetails, openOffchainActivityModal])
return (
<Trace
......
......@@ -41,6 +41,15 @@ jest.mock('components/AccountDrawer/MiniPortfolio/Activity/utils', () => ({
useCreateCancelTransactionRequest: jest.fn(),
}))
jest.mock('utilities/src/logger/logger', () => ({
logger: {
error: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
debug: jest.fn(),
},
}))
describe('CancelOrdersDialog', () => {
it('should render order cancel correctly', async () => {
const mockOnCancel = jest.fn()
......
......@@ -8,15 +8,15 @@ import Row from 'components/deprecated/Row'
import { DetailLineItem } from 'components/swap/DetailLineItem'
import styled, { useTheme } from 'lib/styled-components'
import { Slash } from 'react-feather'
import { Trans, useTranslation } from 'react-i18next'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
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 { nativeOnChain } from 'uniswap/src/constants/tokens'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice'
import { Plural, Trans, t } from 'uniswap/src/i18n'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { NumberType, useFormatter } from 'utils/formatNumbers'
......@@ -82,6 +82,7 @@ export function CancelOrdersDialog(
onConfirm: () => void
},
) {
const { t } = useTranslation()
const { orders, cancelState, cancelTxHash, onConfirm, onCancel } = props
const { title, icon } = useCancelOrdersDialogContent(cancelState, orders)
......@@ -128,11 +129,7 @@ export function CancelOrdersDialog(
title={title}
description={
<Flex width="100%">
<Plural
value={orders.length}
one={t('swap.cancel.cannotExecute')}
other={t('swap.cancel.cannotExecute.plural')}
/>
<Text>{t('swap.cancel.cannotExecute', { count: orders.length })}</Text>
<GasEstimateDisplay chainId={orders[0].chainId} gasEstimateValue={gasEstimate?.value} />
</Flex>
}
......
......@@ -28,6 +28,7 @@ import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import styled, { useTheme } from 'lib/styled-components'
import { ReactNode, useCallback, useMemo, useState } from 'react'
import { ArrowDown, X } from 'react-feather'
import { Trans } from 'react-i18next'
import { useOrder } from 'state/signatures/hooks'
import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types'
import { Divider, ThemedText } from 'theme/components'
......@@ -36,7 +37,6 @@ import { Modal } from 'uniswap/src/components/modals/Modal'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { InterfaceEventNameLocal, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { Trans } from 'uniswap/src/i18n'
import { CurrencyField } from 'uniswap/src/types/currency'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { logger } from 'utilities/src/logger/logger'
......@@ -172,7 +172,6 @@ function getOrderTitle(order: UniswapXOrderDetails): ReactNode {
export function OrderContent({
order,
logos,
onCancel,
}: {
order: UniswapXOrderDetails
......@@ -230,7 +229,6 @@ export function OrderContent({
<PortfolioLogo
chainId={amounts?.inputAmount.currency.chainId ?? UniverseChainId.Mainnet}
currencies={currencies}
images={[logos?.inputLogo, logos?.outputLogo]}
/>
<Column>
<ThemedText.SubHeader fontWeight={500}>{getOrderTitle(order)}</ThemedText.SubHeader>
......
......@@ -2,9 +2,9 @@ import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'
import { formatTimestamp } from 'components/AccountDrawer/MiniPortfolio/formatTimestamp'
import { DetailLineItem, LineItemData } from 'components/swap/DetailLineItem'
import TradePrice from 'components/swap/TradePrice'
import { Trans } from 'react-i18next'
import { UniswapXOrderDetails } from 'state/signatures/types'
import { ExternalLink } from 'theme/components'
import { Trans } from 'uniswap/src/i18n'
import { ellipseMiddle } from 'utilities/src/addresses'
import { NumberType, useFormatter } from 'utils/formatNumbers'
......
......@@ -561,7 +561,12 @@ exports[`CancelOrdersDialog should render limit order text 1`] = `
<div
class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-column _width-10037"
>
Your swap could execute before cancellation is processed. Your network costs cannot be refunded. Do you wish to proceed?
<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?
</span>
<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"
>
......@@ -1180,7 +1185,12 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = `
<div
class="_display-flex _alignItems-stretch _flexBasis-auto _boxSizing-border-box _position-relative _minHeight-0px _minWidth-0px _flexShrink-0 _flexDirection-column _width-10037"
>
Your swap could execute before cancellation is processed. Your network costs cannot be refunded. Do you wish to proceed?
<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?
</span>
<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"
>
......
......@@ -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 { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
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 { logger } from 'utilities/src/logger/logger'
import { NumberType, useFormatter } from 'utils/formatNumbers'
......@@ -57,25 +57,25 @@ function buildCurrencyDescriptor(
input: parseFloat(CurrencyAmount.fromRawAmount(currencyA, amtA).toSignificant()),
type: NumberType.TokenNonTx,
})
: t('common.unknown')
: i18n.t('common.unknown')
const symbolA = currencyA?.symbol ? ` ${currencyA?.symbol}` : ''
const formattedB = currencyB
? formatNumber({
input: parseFloat(CurrencyAmount.fromRawAmount(currencyB, amtB).toSignificant()),
type: NumberType.TokenNonTx,
})
: t('common.unknown')
: i18n.t('common.unknown')
const symbolB = currencyB?.symbol ? ` ${currencyB?.symbol}` : ''
const amountWithSymbolA = `${formattedA}${symbolA}`
const amountWithSymbolB = `${formattedB}${symbolB}`
return isSwap
? t('activity.transaction.swap.descriptor', {
? i18n.t('activity.transaction.swap.descriptor', {
amountWithSymbolA,
amountWithSymbolB,
})
: t('activity.transaction.tokens.descriptor', {
: i18n.t('activity.transaction.tokens.descriptor', {
amountWithSymbolA,
amountWithSymbolB,
})
......@@ -117,13 +117,13 @@ async function parseBridge(
input: parseFloat(CurrencyAmount.fromRawAmount(tokenIn, bridge.inputCurrencyAmountRaw).toSignificant()),
type: NumberType.TokenNonTx,
})
: t('common.unknown')
: i18n.t('common.unknown')
const outputAmount = tokenOut
? formatNumber({
input: parseFloat(CurrencyAmount.fromRawAmount(tokenOut, bridge.outputCurrencyAmountRaw).toSignificant()),
type: NumberType.TokenNonTx,
})
: t('common.unknown')
: i18n.t('common.unknown')
return {
descriptor: getBridgeDescriptor({ tokenIn, tokenOut, inputAmount, outputAmount }),
chainId: inputChainId,
......@@ -163,7 +163,7 @@ async function parseApproval(
status: TransactionStatus,
): Promise<Partial<Activity>> {
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 {
title: getActivityTitle(
TransactionType.APPROVAL,
......@@ -237,9 +237,9 @@ async function parseMigrateCreateV3(
getCurrency(lp.baseCurrencyId, chainId),
getCurrency(lp.quoteCurrencyId, chainId),
])
const baseSymbol = baseCurrency?.symbol ?? t('common.unknown')
const quoteSymbol = quoteCurrency?.symbol ?? t('common.unknown')
const descriptor = t('activity.transaction.tokens.descriptor', {
const baseSymbol = baseCurrency?.symbol ?? i18n.t('common.unknown')
const quoteSymbol = quoteCurrency?.symbol ?? i18n.t('common.unknown')
const descriptor = i18n.t('activity.transaction.tokens.descriptor', {
amountWithSymbolA: baseSymbol,
amountWithSymbolB: quoteSymbol,
})
......@@ -259,11 +259,11 @@ async function parseSend(
input: parseFloat(CurrencyAmount.fromRawAmount(currency, amount).toSignificant()),
type: NumberType.TokenNonTx,
})
: t('common.unknown')
: i18n.t('common.unknown')
const otherAccount = isAddress(recipient) || undefined
return {
descriptor: t('activity.transaction.send.descriptor', {
descriptor: i18n.t('activity.transaction.send.descriptor', {
amountWithSymbol: `${formattedAmount} ${currency?.symbol}`,
walletAddress: recipient,
}),
......
......@@ -20,7 +20,7 @@ import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__g
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants'
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 { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
......@@ -85,11 +85,11 @@ export const createGroups = (activities: Array<Activity> = [], hideSpam = false)
.map((year) => ({ title: year, transactions: yearMap[year] }))
const transactionGroups: Array<ActivityGroup> = [
{ title: t('common.pending'), transactions: pending.sort(sortActivities) },
{ title: t('common.today'), transactions: today.sort(sortActivities) },
{ title: t('common.thisWeek'), transactions: currentWeek.sort(sortActivities) },
{ title: t('common.thisMonth'), transactions: last30Days.sort(sortActivities) },
{ title: t('common.thisYear'), transactions: currentYear.sort(sortActivities) },
{ title: i18n.t('common.pending'), transactions: pending.sort(sortActivities) },
{ title: i18n.t('common.today'), transactions: today.sort(sortActivities) },
{ title: i18n.t('common.thisWeek'), transactions: currentWeek.sort(sortActivities) },
{ title: i18n.t('common.thisMonth'), transactions: last30Days.sort(sortActivities) },
{ title: i18n.t('common.thisYear'), transactions: currentYear.sort(sortActivities) },
...sortedYears,
]
......
import styled from 'lib/styled-components'
import { useCallback, useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Flex, Text, useIsDarkMode } from 'ui/src'
import { CRYPTO_PURCHASE_BACKGROUND_DARK, CRYPTO_PURCHASE_BACKGROUND_LIGHT } from 'ui/src/assets'
import { ArrowDownCircle } from 'ui/src/components/icons/ArrowDownCircle'
import { Buy as BuyIcon } from 'ui/src/components/icons/Buy'
import { ActionCard, ActionCardItem } from 'uniswap/src/components/misc/ActionCard'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { Trans, t } from 'uniswap/src/i18n'
export const EmptyWallet = ({
handleBuyCryptoClick,
......@@ -15,6 +15,7 @@ export const EmptyWallet = ({
handleBuyCryptoClick: () => void
handleReceiveCryptoClick: () => void
}) => {
const { t } = useTranslation()
const isDarkMode = useIsDarkMode()
const BackgroundImageWrapperCallback = useCallback(
......@@ -46,7 +47,7 @@ export const EmptyWallet = ({
onPress: handleReceiveCryptoClick,
},
],
[BackgroundImageWrapperCallback, handleBuyCryptoClick, handleReceiveCryptoClick],
[BackgroundImageWrapperCallback, handleBuyCryptoClick, handleReceiveCryptoClick, t],
)
return (
......
......@@ -3,8 +3,8 @@ import Row from 'components/deprecated/Row'
import styled from 'lib/styled-components'
import { PropsWithChildren } from 'react'
import { ChevronDown } from 'react-feather'
import { useTranslation } from 'react-i18next'
import { ThemedText } from 'theme/components'
import { t } from 'uniswap/src/i18n'
const ExpandIcon = styled(ChevronDown)<{ $expanded: boolean }>`
color: ${({ theme }) => theme.neutral2};
......@@ -24,15 +24,17 @@ const ToggleButton = styled(Row)`
}
`
const Wrapper = styled(Column)<{ numItems: number; isExpanded: boolean }>`
height: ${({ numItems, isExpanded }) => (isExpanded ? numItems * 68 + 'px' : 0)};
const Wrapper = styled(Column)<{ isExpanded: boolean }>`
height: ${({ isExpanded }) => (isExpanded ? '100%' : 0)};
transition: ${({ theme }) => `height ${theme.transition.duration.medium} ease-in-out`};
overflow: hidden;
`
// TODO(WEB-1982): Replace this component to use `components/Expand` under the hood
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) {
return null
}
......@@ -40,7 +42,7 @@ export function ExpandoRow({ title = t('common.hidden'), numItems, isExpanded, t
<>
<Row align="center" justify="space-between" padding="16px">
<ThemedText.SubHeader color="neutral2" variant="subheadSmall">
{`${title} (${numItems})`}
{`${titleWithFallback} (${numItems})`}
</ThemedText.SubHeader>
<ToggleButton align="center" onClick={toggle}>
<ThemedText.LabelSmall color="neutral2" variant="buttonLabelSmall">
......@@ -49,9 +51,7 @@ export function ExpandoRow({ title = t('common.hidden'), numItems, isExpanded, t
<ExpandIcon $expanded={isExpanded} />
</ToggleButton>
</Row>
<Wrapper numItems={numItems} isExpanded={isExpanded}>
{children}
</Wrapper>
<Wrapper isExpanded={isExpanded}>{children}</Wrapper>
</>
)
}
......@@ -8,15 +8,14 @@ import { ExtensionRequestMethods, useUniswapExtensionConnector } from 'component
import { useUpdateAtom } from 'jotai/utils'
import { useTheme } from 'lib/styled-components'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Button, Flex, Image, Text } from 'ui/src'
import { UNISWAP_LOGO } from 'ui/src/assets'
import { ArrowRightToLine } from 'ui/src/components/icons/ArrowRightToLine'
import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron'
import { TimePast } from 'ui/src/components/icons/TimePast'
import { iconSizes } from 'ui/src/theme/iconSizes'
import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions'
import { t } from 'uniswap/src/i18n'
const UnreadIndicator = () => {
const theme = useTheme()
......@@ -53,6 +52,7 @@ const DeepLinkButton = ({ Icon, Label, onPress }: { Icon: JSX.Element; Label: st
}
export function ExtensionDeeplinks({ account }: { account: string }) {
const { t } = useTranslation()
const theme = useTheme()
const uniswapExtensionConnector = useUniswapExtensionConnector()
const accountDrawer = useAccountDrawer()
......
......@@ -14,10 +14,10 @@ import { useScreenSize } from 'hooks/screenSize/useScreenSize'
import styled, { useTheme } from 'lib/styled-components'
import { useMemo, useState } from 'react'
import { ArrowRight } from 'react-feather'
import { Trans } from 'react-i18next'
import { EllipsisStyle, ThemedText } from 'theme/components'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { Checkbox } from 'ui/src'
import { Trans } from 'uniswap/src/i18n'
import { useFormatter } from 'utils/formatNumbers'
const StyledPortfolioRow = styled(PortfolioRow)`
......
......@@ -15,8 +15,8 @@ import Column from 'components/deprecated/Column'
import { LimitDisclaimer } from 'components/swap/LimitDisclaimer'
import styled from 'lib/styled-components'
import { useCallback, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { UniswapXOrderDetails } from 'state/signatures/types'
import { Trans, t } from 'uniswap/src/i18n'
const Container = styled(Column)`
height: 100%;
......@@ -34,6 +34,7 @@ const StyledLimitsDisclaimer = styled(LimitDisclaimer)`
`
export function LimitsMenu({ onClose, account }: { account: string; onClose: () => void }) {
const { t } = useTranslation()
const { openLimitOrders } = useOpenLimitOrders(account)
const [selectedOrdersByHash, setSelectedOrdersByHash] = useState<Record<string, UniswapXOrderDetails>>({})
const [cancelState, setCancelState] = useState(CancellationState.NOT_STARTED)
......
......@@ -2,7 +2,7 @@ import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activ
import { TabButton } from 'components/AccountDrawer/MiniPortfolio/shared'
import { useTheme } from 'lib/styled-components'
import { Clock } from 'react-feather'
import { Trans, useTranslation } from 'uniswap/src/i18n'
import { Trans, useTranslation } from 'react-i18next'
function getExtraWarning(openLimitOrders: any[]) {
if (openLimitOrders.length >= 100) {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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