ci(release): publish latest release

parent 103d0f71
......@@ -41,7 +41,7 @@ apps/extension/dev
packages/uniswap/codegen.ts
# eslint partials
# eslint
packages/eslint-config/restrictedImports.js
......
* @uniswap/web-admins
IPFS hash of the deployment:
- CIDv0: `QmcmxzrNZV6PSQUvi77szywWtT7oG5MB13GwMoaN7nbpBU`
- CIDv1: `bafybeigwp7iptpdnrs7ot27t2aibq7d6mgmptfe6tivot3wp2lvfig3wtm`
We are back with some new updates! Here’s the latest:
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
Settings Refresh: Enjoy a more streamlined settings page. Also note that Manage Connections has now moved out of settings and can be accessed by clicking your profile image.
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeigwp7iptpdnrs7ot27t2aibq7d6mgmptfe6tivot3wp2lvfig3wtm.ipfs.dweb.link/
- [ipfs://QmcmxzrNZV6PSQUvi77szywWtT7oG5MB13GwMoaN7nbpBU/](ipfs://QmcmxzrNZV6PSQUvi77szywWtT7oG5MB13GwMoaN7nbpBU/)
## 5.77.0 (2025-03-19)
### Features
* **web:** [creating pool] modify setting initial price style (#17104) 2a3047e
* **web:** [lp] add migrate button on v3 positions (#17301) 48dcf56
* **web:** [lp] add warnings based on price differential (#17163) e655444
* **web:** add wallet connection/status to datadog issues (#17125) 3ea2492
* **web:** avoid non-actionable rpc errors for testnet chains (#17188) cfdd7af
* **web:** delete legacy swap :O (#16378) 246d6da
* **web:** initialize creating a pool with current price info (#17075) 9c2e992
### Bug Fixes
* **web:** [lp] mweb cleanups (#17260) 5327e78
* **web:** add back h1 to swap tab header (#16941) c12b85d
* **web:** add missing version to DD initialization (#17314) e51f608
* **web:** dont fail test if snapshot revert fails (#17230) 4034ce9
* **web:** filter internal RPC errors in wrapSaga on testnets (#16952) e8264f2
* **web:** fix swapping on TDP (#17379) fa90c53
* **web:** fix X IconButton in enter hook address form (#17261) 54bbce1
* **web:** gap in positions cards as a result of using AdaptiveDropdown, and some more minor enhancements (#16384) b56607b
* **web:** remove sentry from error boundary to fix double render (#17… (#17524) af665cd
* **web:** remove sprinkles from NFT common.css (#16753) f359718
* **web:** remove sprinkles from NFT index.css file (#16755) 1c71867
* **web:** remove sprinkles from nft loading.css (#16754) 55d7321
* **web:** remove sprinkles from web app (#16756) 2dd0b72
* **web:** remove styled from ActivitySwitcher (#17002) 64151b1
* **web:** remove styled from NFT CollectionSearch (#17003) 1097730
* **web:** remove unused dep - immer (#17187) 2c3d3ab
* **web:** remove vanilla-extract from Cells.css (#17168) 0f96de7
* **web:** remove vanilla-extract from CollectionDetails (#17005) 5e720d9
* **web:** remove vanilla-extract from FilterButton (#17006) 32a583c
* **web:** remove vanilla-extract from Filters.css (#17007) de5aa23
* **web:** remove vanilla-extract from LoadingSparkle (#17166) 42f2485
* **web:** remove vanilla-extract from NFT Activity (#17004) 480bce6
* **web:** remove vanilla-extract from PriceRange (#17008) 0689ffa
* **web:** remove vanilla-extract from SortDropdown (#17167) 82a18ee
* **web:** reverse animation direction for extension (#17110) c0758d2
* **web:** send do not allow fiat input testnet (#16675) 16ee4be
* **web:** tweak stagehand landing page commands (#17281) b64a525
### Continuous Integration
* **web:** update sitemaps e4d288c
### Code Refactoring
* **web:** buy e2e test (#17164) ca807e8
* **web:** landing page e2e test migration to playwright (#17207) 23c402d
Dapp Connection Refresh: Once connected to a dapp, click the dapp’s icon in the Extension and view a more condensed network selector, as well as other options for managing that dapp connection.
Other changes:
- More helpcenter articles
- Various bug fixes and performance improvements
\ No newline at end of file
web/5.77.0
\ No newline at end of file
extension/1.18.0
\ No newline at end of file
......@@ -18,7 +18,7 @@ import { DevMenuScreen } from 'src/app/features/settings/DevMenuScreen'
import { SettingsManageConnectionsScreen } from 'src/app/features/settings/SettingsManageConnectionsScreen/SettingsManageConnectionsScreen'
import { RemoveRecoveryPhraseVerify } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/RemoveRecoveryPhraseVerify'
import { RemoveRecoveryPhraseWallets } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/RemoveRecoveryPhraseWallets'
import { SettingsViewRecoveryPhraseScreen } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/ViewRecoveryPhraseScreen'
import { ViewRecoveryPhraseScreen } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/ViewRecoveryPhraseScreen'
import { SettingsScreen } from 'src/app/features/settings/SettingsScreen'
import { SettingsScreenWrapper } from 'src/app/features/settings/SettingsScreenWrapper'
import { SettingsChangePasswordScreen } from 'src/app/features/settings/password/SettingsChangePasswordScreen'
......@@ -80,7 +80,7 @@ const router = createHashRouter([
: {},
{
path: SettingsRoutes.ViewRecoveryPhrase,
element: <SettingsViewRecoveryPhraseScreen />,
element: <ViewRecoveryPhraseScreen />,
},
{
path: SettingsRoutes.RemoveRecoveryPhrase,
......
......@@ -33,7 +33,7 @@ import { ContextMenu } from 'wallet/src/components/menu/ContextMenu'
import { MenuContent } from 'wallet/src/components/menu/MenuContent'
import { MenuContentItem } from 'wallet/src/components/menu/types'
import { createOnboardingAccount } from 'wallet/src/features/onboarding/createOnboardingAccount'
import { useCanActiveAddressClaimUnitag } from 'wallet/src/features/unitags/hooks'
import { useCanActiveAddressClaimUnitag } from 'wallet/src/features/unitags/hooks/useCanActiveAddressClaimUnitag'
import { BackupType, SignerMnemonicAccount } from 'wallet/src/features/wallet/accounts/types'
import { createAccountsActions } from 'wallet/src/features/wallet/create/createAccountsSaga'
import {
......
......@@ -14,7 +14,7 @@ import { UNITAG_SUFFIX_NO_LEADING_DOT } from 'uniswap/src/features/unitags/const
import { shortenAddress } from 'utilities/src/addresses'
import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon'
import { CardType, IntroCard, IntroCardGraphicType } from 'wallet/src/components/introCards/IntroCard'
import { useCanActiveAddressClaimUnitag } from 'wallet/src/features/unitags/hooks'
import { useCanActiveAddressClaimUnitag } from 'wallet/src/features/unitags/hooks/useCanActiveAddressClaimUnitag'
import { EditAccountAction, editAccountActions } from 'wallet/src/features/wallet/accounts/editAccountSaga'
import { useDisplayName } from 'wallet/src/features/wallet/hooks'
import { DisplayNameType } from 'wallet/src/features/wallet/types'
......
......@@ -230,6 +230,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
<button
class=" is_Button t_group_item"
data-testid="account-card"
dd-action-name="Edit label"
style="display: flex; flex-basis: 0px; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 1; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; align-self: stretch; flex-grow: 1; outline-color: rgba(0, 0, 0, 0); padding: 8px 12px 8px 12px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 8px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
......@@ -523,6 +524,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
<button
class=" is_Button t_group_item"
data-testid="account-card"
dd-action-name="Edit label"
style="display: flex; flex-basis: 0px; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 1; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; align-self: stretch; flex-grow: 1; outline-color: rgba(0, 0, 0, 0); padding: 8px 12px 8px 12px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 8px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
......
import { useTranslation } from 'react-i18next'
import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { SeedPhraseDisplay } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/SeedPhraseDisplay'
import { Flex, Text } from 'ui/src'
import { GraduationCap } from 'ui/src/components/icons/GraduationCap'
import { ViewRecoveryPhraseScreen } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/ViewRecoveryPhraseScreen'
import { ForceUpgrade } from 'wallet/src/features/forceUpgrade/ForceUpgrade'
function SeedPhraseModalContent({ mnemonicId, onDismiss }: { mnemonicId: string; onDismiss: () => void }): JSX.Element {
const { t } = useTranslation()
return (
<Flex fill gap="$spacing24" py="$spacing8">
<ScreenHeader title={t('forceUpgrade.label.recoveryPhrase')} onBackClick={onDismiss} />
<Flex gap="$spacing16">
<SeedPhraseDisplay mnemonicId={mnemonicId} />
<Flex
row
alignItems="center"
backgroundColor="$surface2"
borderRadius="$rounded16"
gap="$spacing8"
p="$spacing12"
>
<Flex>
<GraduationCap color="$neutral2" size="$icon.20" />
</Flex>
<Flex shrink>
<Text color="$neutral2" variant="body4">
{t('onboarding.backup.manual.banner')}
</Text>
</Flex>
</Flex>
</Flex>
</Flex>
)
return <ViewRecoveryPhraseScreen mnemonicId={mnemonicId} showRemoveButton={false} onBackClick={onDismiss} />
}
export function ForceUpgradeModal(): JSX.Element {
......
......@@ -255,7 +255,12 @@ export function ImportMnemonic(): JSX.Element {
}
iconPosition="after"
emphasis="text-only"
onPress={(): void => setExpanded(!expanded)}
onPress={(): void => {
if (expanded) {
setMnemonic([...mnemonic.slice(0, 12), ...Array(12).fill('')])
}
setExpanded(!expanded)
}}
>
{expanded
? t('onboarding.importMnemonic.button.default')
......
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { Link } from 'react-router-dom'
import { useDappContext } from 'src/app/features/dapp/DappContext'
import { removeDappConnection } from 'src/app/features/dapp/actions'
import { SwitchNetworksModal } from 'src/app/features/home/SwitchNetworksModal'
......@@ -21,6 +22,7 @@ import {
import { Power, RotatableChevron, X } from 'ui/src/components/icons'
import { borderRadii, iconSizes } from 'ui/src/theme'
import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { getChainLabel } from 'uniswap/src/features/chains/utils'
import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types'
......@@ -114,7 +116,7 @@ export function ConnectPopupContent({
</TouchableArea>
)}
</Flex>
<Flex row py="$padding16" pl="$padding8" justifyContent="space-between" alignItems="center">
<Flex row pt="$padding16" justifyContent="space-between" alignItems="center">
<Flex row gap="$gap8">
<Flex borderRadius="$roundedFull" alignItems="center" justifyContent="center">
<Circle
......@@ -158,7 +160,27 @@ export function ConnectPopupContent({
</TouchableArea>
)}
</Flex>
<Flex gap="$spacing8">
{!isConnected && (
<Flex pt="$padding6">
<Link
style={{ textDecoration: 'none' }}
target="_blank"
to={uniswapUrls.helpArticleUrls.extensionDappTroubleshooting}
onClick={() =>
sendAnalyticsEvent(ExtensionEventName.DappTroubleConnecting, {
dappUrl,
})
}
>
<Text color="$accent1" variant="buttonLabel4">
{t('extension.connection.popup.trouble')}
</Text>
</Link>
</Flex>
)}
<Flex gap="$spacing8" pt="$padding12">
<Popover.Close onPress={openManageConnections}>
<Flex row>
<Button size="small" variant="default" emphasis="tertiary">
......
......@@ -6011,6 +6011,7 @@ exports[`ReceiveScreen renders without error 1`] = `
<button
aria-label="Networks"
class=" is_Button t_group_item"
dd-action-name="Networks"
style="display: flex; flex-basis: auto; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 0; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; outline-color: rgba(0, 0, 0, 0); padding: 4px 6px 4px 6px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 4px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
......@@ -12048,6 +12049,7 @@ exports[`ReceiveScreen renders without error 1`] = `
<button
aria-label="Networks"
class=" is_Button t_group_item"
dd-action-name="Networks"
style="display: flex; flex-basis: auto; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 0; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; outline-color: rgba(0, 0, 0, 0); padding: 4px 6px 4px 6px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 4px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
......
......@@ -4,11 +4,11 @@ import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { SettingsItemWithDropdown } from 'src/app/features/settings/SettingsItemWithDropdown'
import { Accordion, Flex, ScrollView } from 'ui/src'
import { Settings } from 'ui/src/components/icons'
import { GatingOverrides } from 'uniswap/src/components/gating/GatingOverrides'
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'
import { GatingOverrides } from 'wallet/src/components/gating/GatingOverrides'
export function DevMenuScreen(): JSX.Element {
const { t } = useTranslation()
......
......@@ -19,15 +19,30 @@ const enum ViewStep {
Reveal = 2,
}
export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
/**
* This screen is rendered both as a settings route and as a modal in the force upgrade prompt.
* When making UI changes, please verify both versions look good.
*/
export function ViewRecoveryPhraseScreen({
mnemonicId: mnemonicIdProp,
showRemoveButton = true,
onBackClick,
}: {
mnemonicId?: string
showRemoveButton?: boolean
onBackClick?: () => void
}): JSX.Element {
const { t } = useTranslation()
const [viewStep, setViewStep] = useState(ViewStep.Warning)
const mnemonicAccounts = useSignerAccounts()
const mnemonicAccount = mnemonicAccounts[0]
if (!mnemonicAccount) {
throw new Error('Screen should not be accessed unless mnemonic account exists')
const mnemonicId = mnemonicIdProp ?? mnemonicAccount?.mnemonicId
if (!mnemonicId) {
throw new Error('Invalid render of `ViewRecoveryPhraseScreen` without `mnemonicId`')
}
const showPasswordModal = (): void => {
......@@ -49,7 +64,8 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
return (
<Flex grow backgroundColor="$surface1">
<ScreenHeader title={t('settings.setting.recoveryPhrase.title')} />
<ScreenHeader title={t('settings.setting.recoveryPhrase.title')} onBackClick={onBackClick} />
{viewStep !== ViewStep.Reveal ? (
<SettingsRecoveryPhrase
icon={<AlertTriangleFilled color="$statusCritical" size="$icon.24" />}
......@@ -65,6 +81,7 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
onClose={() => setViewStep(ViewStep.Warning)}
onNext={() => setViewStep(ViewStep.Reveal)}
/>
<Flex
alignItems="flex-start"
borderColor="$surface3"
......@@ -72,6 +89,7 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
borderWidth="$spacing1"
gap="$spacing24"
p="$spacing12"
mb="$spacing12"
>
<Flex row alignItems="center" gap="$spacing12">
<Flex p={6}>
......@@ -81,6 +99,7 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
{t('setting.recoveryPhrase.view.warning.message2')}
</Text>
</Flex>
<Flex row alignItems="center" gap="$spacing12" width="100%">
<Flex p={6}>
<Key color="$statusCritical" size="$icon.24" />
......@@ -89,6 +108,7 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
{t('setting.recoveryPhrase.view.warning.message3')}
</Text>
</Flex>
<Flex row alignItems="center" gap="$spacing12">
<Flex p={6}>
<Laptop color="$statusCritical" size="$icon.24" />
......@@ -101,28 +121,32 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
</SettingsRecoveryPhrase>
) : (
<Flex fill gap="$spacing24" pt="$spacing36">
<SeedPhraseDisplay mnemonicId={mnemonicAccount.mnemonicId} />
<SeedPhraseDisplay mnemonicId={mnemonicId} />
<Flex alignItems="center" gap="$spacing8">
<Text color="$neutral2" textAlign="center" variant="body3">
{t('setting.recoveryPhrase.warning.view.message')}
</Text>
</Flex>
<Flex fill justifyContent="flex-end">
<Flex row>
<Button
variant="critical"
emphasis="secondary"
onPress={(): void =>
navigate(
`${AppRoutes.Settings}/${SettingsRoutes.RemoveRecoveryPhrase}/${RemoveRecoveryPhraseRoutes.Wallets}`,
{ replace: true },
)
}
>
{t('setting.recoveryPhrase.remove')}
</Button>
{showRemoveButton && (
<Flex fill justifyContent="flex-end">
<Flex row>
<Button
variant="critical"
emphasis="secondary"
onPress={(): void =>
navigate(
`${AppRoutes.Settings}/${SettingsRoutes.RemoveRecoveryPhrase}/${RemoveRecoveryPhraseRoutes.Wallets}`,
{ replace: true },
)
}
>
{t('setting.recoveryPhrase.remove')}
</Button>
</Flex>
</Flex>
</Flex>
)}
</Flex>
)}
</Flex>
......
......@@ -49,12 +49,16 @@ export function UnitagConfirmationScreen(): JSX.Element {
</Text>
</Flex>
<Flex gap="$spacing12" pt="$spacing12">
<Button size="medium" variant="branded" emphasis="primary" onPress={closeCurrentTab}>
{t('common.button.done')}
</Button>
<Button size="medium" emphasis="secondary" onPress={onPressCustomize}>
{t('unitags.claim.confirmation.customize')}
</Button>
<Flex row>
<Button size="medium" variant="branded" emphasis="primary" onPress={closeCurrentTab}>
{t('common.button.done')}
</Button>
</Flex>
<Flex row>
<Button size="medium" emphasis="secondary" onPress={onPressCustomize}>
{t('unitags.claim.confirmation.customize')}
</Button>
</Flex>
</Flex>
</Flex>
</OnboardingScreen>
......
......@@ -6,6 +6,7 @@ import {
isValidContentScriptToProxyEmission,
isValidWindowEthereumConfigResponse,
} from 'src/contentScript/types'
import { isDevEnv } from 'utilities/src/environment/env'
import { logger } from 'utilities/src/logger/logger'
import { v4 as uuid } from 'uuid'
......@@ -30,7 +31,8 @@ const UNISWAP_RDNS = 'org.uniswap.app'
declare global {
interface Window {
isStretchInstalled?: boolean
ethereum?: unknown
// We declare this as readonly to force the use of `assignWindowEthereum` to override it.
readonly ethereum?: unknown
}
}
......@@ -46,10 +48,25 @@ interface EIP6963ProviderInfo {
rdns: string
}
function assignWindowEthereum(provider: unknown): void {
try {
// We need to try/catch this because some sneaky wallet extensions set `window.ethereum` to a getter,
// which throws an error when trying to override it.
// In these cases, our wallet will only work with dapps that suppport EIP-6963.
// @ts-expect-error: we're intentionally trying to override this.
window.ethereum = provider
} catch (error) {
if (isDevEnv()) {
// Only log in dev env for debugging purposes to avoid spamming DD with these errors.
logger.error(error, { tags: { file: 'ethereum.ts', function: 'assignWindowEthereum' } })
}
}
}
const oldProvider = window.ethereum
const uniswapProvider = new WindowEthereumProxy()
window.ethereum = uniswapProvider
assignWindowEthereum(uniswapProvider)
addWindowMessageListener(isValidContentScriptToProxyEmission, (message) => {
logger.debug('ethereum.ts', `Emitting ${message.emitKey} via WindowEthereumProxy`, message.emitValue)
......@@ -105,8 +122,7 @@ addWindowMessageListener<WindowEthereumConfigResponse>(
if (isDefaultProvider === false) {
uniswapProvider.isMetaMask = false
if (oldProvider) {
// typing isn't exact here but the idea is that we are injecting some 1193 provider
window.ethereum = oldProvider
assignWindowEthereum(oldProvider)
create6963Listener()
}
}
......
......@@ -119,3 +119,5 @@ ios/WidgetsCore/MobileSchema/*
ios/WidgetsCore/Env.swift
ios/OneSignalNotificationServiceExtension/Env.swift
# Expo
.expo/
......@@ -11,7 +11,6 @@
},
"plugins": [
"expo-localization",
"expo-barcode-scanner",
"expo-camera",
"expo-local-authentication"
]
......
import UserNotifications
import OneSignalExtension
import Statsig
class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
let userInfo = request.content.userInfo
// Fields per OneSignal docs
let custom = userInfo["custom"] as? [String: Any]
let additionalData = custom?["a"] as? [String: Any]
let notificationType = additionalData?[Constants.fieldNotificationType] as? String
let isGatedNotification = notificationType == Constants.typeUnfundedWallet
|| notificationType == Constants.typePriceAlert
if (!isGatedNotification) {
OneSignalExtension.didReceiveNotificationExtensionRequest(request, with: bestAttemptContent, withContentHandler: contentHandler)
return
}
func handleGatedNotification() {
let enabled: Bool
switch notificationType {
case Constants.typeUnfundedWallet:
enabled = Statsig.checkGate(Constants.gateUnfundedWallet)
case Constants.typePriceAlert:
enabled = Statsig.checkGate(Constants.gatePriceAlert)
default:
enabled = true
}
// Passing in empty notification content will skip the notif
OneSignalExtension.didReceiveNotificationExtensionRequest(
request,
with: enabled ? bestAttemptContent : UNMutableNotificationContent(),
withContentHandler: contentHandler)
}
if (!Statsig.isInitialized()) {
// The real sdk key is needed on iOS even though it's substituted in proxy
// Because the key is used to hash the feature gate names and wouldn't work properly otherwise
let statsigSdkKey = Env.STATSIG_API_KEY
let statsigUser = StatsigUser(
userID: UIDevice.current.identifierForVendor?.uuidString,
custom: [
"app": "mobile"
])
Statsig.initialize(
sdkKey: statsigSdkKey,
user: statsigUser,
options: StatsigOptions(
environment: StatsigEnvironment(tier: getStatsigEnvironemntTier()),
initializationURL: URL(string: "\(Constants.statsigProxyHost)/v1/statsig-proxy/initialize"),
eventLoggingURL: URL(string: "\(Constants.statsigProxyHost)/v1/statsig-proxy/rgstr")
)) { _errorMessage in
handleGatedNotification()
}
} else {
handleGatedNotification()
var contentHandler: ((UNNotificationContent) -> Void)?
var receivedRequest: UNNotificationRequest!
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.receivedRequest = request
self.contentHandler = contentHandler
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
/* DEBUGGING: Uncomment the 2 lines below to check this extension is executing
Note, this extension only runs when mutable-content is set
Setting an attachment or action buttons automatically adds this */
// print("Running NotificationServiceExtension")
// bestAttemptContent.body = "[Modified] " + bestAttemptContent.body
OneSignalExtension.didReceiveNotificationExtensionRequest(self.receivedRequest, with: bestAttemptContent, withContentHandler: self.contentHandler)
}
}
}
func getStatsigEnvironemntTier() -> String {
let bundleSuffix = Bundle.main.object(forInfoDictionaryKey: "BUNDLE_ID_SUFFIX") as? String
switch bundleSuffix {
case ".dev":
return "development"
case ".beta":
return "beta"
default:
return "production"
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
OneSignalExtension.serviceExtensionTimeWillExpireRequest(self.receivedRequest, with: self.bestAttemptContent)
contentHandler(bestAttemptContent)
}
}
}
}
struct Constants {
static let statsigProxyHost = "https://gating.ios.wallet.gateway.uniswap.org"
static let fieldNotificationType = "notification_type"
static let typeUnfundedWallet = "unfunded_wallet_reminder"
static let typePriceAlert = "price_alert"
static let gateUnfundedWallet = "notification_unfunded_wallet_ios"
static let gatePriceAlert = "notification_price_alerts_ios"
}
......@@ -58,9 +58,7 @@ end
target 'OneSignalNotificationServiceExtension' do
use_frameworks! :linkage => :static
pod 'OneSignalXCFramework', '3.12.6'
pod 'Statsig', '1.49.0'
pod 'OneSignalXCFramework', '>= 5.0.0', '< 6.0'
end
def prepare_target_commons
......
......@@ -1430,16 +1430,52 @@ PODS:
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- OneSignalXCFramework (3.12.6):
- OneSignalXCFramework/OneSignalCore (= 3.12.6)
- OneSignalXCFramework/OneSignalExtension (= 3.12.6)
- OneSignalXCFramework/OneSignalOutcomes (= 3.12.6)
- OneSignalXCFramework/OneSignalCore (3.12.6)
- OneSignalXCFramework/OneSignalExtension (3.12.6):
- OneSignalXCFramework (5.2.10):
- OneSignalXCFramework/OneSignalComplete (= 5.2.10)
- OneSignalXCFramework/OneSignal (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalExtension
- OneSignalXCFramework/OneSignalLiveActivities
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalOutcomes (3.12.6):
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalComplete (5.2.10):
- OneSignalXCFramework/OneSignal
- OneSignalXCFramework/OneSignalInAppMessages
- OneSignalXCFramework/OneSignalLocation
- OneSignalXCFramework/OneSignalCore (5.2.10)
- OneSignalXCFramework/OneSignalExtension (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalInAppMessages (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalLiveActivities (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalLocation (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalUser
- OneSignalXCFramework/OneSignalNotifications (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalExtension
- OneSignalXCFramework/OneSignalOutcomes
- OneSignalXCFramework/OneSignalOSCore (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalOutcomes (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalUser (5.2.10):
- OneSignalXCFramework/OneSignalCore
- OneSignalXCFramework/OneSignalNotifications
- OneSignalXCFramework/OneSignalOSCore
- OneSignalXCFramework/OneSignalOutcomes
- OpenTelemetrySwiftApi (1.6.0)
- PLCrashReporter (1.11.2)
- RCT-Folly (2024.01.01.00):
......@@ -2731,8 +2767,8 @@ PODS:
- React-Core
- react-native-netinfo (11.4.1):
- React-Core
- react-native-onesignal (4.5.2):
- OneSignalXCFramework (= 3.12.6)
- react-native-onesignal (5.2.9):
- OneSignalXCFramework (= 5.2.10)
- React (< 1.0.0, >= 0.13.0)
- react-native-pager-view (6.5.1):
- DoubleConversion
......@@ -2759,7 +2795,7 @@ PODS:
- React-Core
- react-native-safe-area-context (4.12.0):
- React-Core
- react-native-skia (1.6.0):
- react-native-skia (1.7.2):
- DoubleConversion
- glog
- hermes-engine
......@@ -3311,7 +3347,6 @@ PODS:
- SocketRocket (0.7.1)
- sparkfabrik-react-native-idfa-aaid (1.2.0):
- React
- Statsig (1.49.0)
- UIImageColors (2.1.0)
- Yoga (0.0.0)
- ZXingObjC (3.6.9):
......@@ -3352,7 +3387,7 @@ DEPENDENCIES:
- fmt (from `../../../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- OneSignalXCFramework (= 3.12.6)
- OneSignalXCFramework (< 6.0, >= 5.0.0)
- RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
......@@ -3446,7 +3481,6 @@ DEPENDENCIES:
- RNScreens (from `../../../node_modules/react-native-screens`)
- RNSVG (from `../../../node_modules/react-native-svg`)
- "sparkfabrik-react-native-idfa-aaid (from `../../../node_modules/@sparkfabrik/react-native-idfa-aaid`)"
- Statsig (= 1.49.0)
- UIImageColors (= 2.1.0)
- Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`)
......@@ -3490,7 +3524,6 @@ SPEC REPOS:
- SDWebImage
- SDWebImageWebPCoder
- SocketRocket
- Statsig
- UIImageColors
- ZXingObjC
......@@ -3791,7 +3824,7 @@ SPEC CHECKSUMS:
MMKV: 3eacda84cd1c4fc95cf848d3ecb69d85ed56006c
MMKVCore: 508b4d3a8ce031f1b5c8bd235f0517fb3f4c73a9
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
OneSignalXCFramework: ff1c970b7aeb4ac0fe48fb35393eb5d8bf378135
OneSignalXCFramework: 1a3b28dfbff23aabce585796d23c1bef37772774
OpenTelemetrySwiftApi: 657da8071c2908caecce11548e006f779924ff9c
PLCrashReporter: 499c53b0104f95c302d94fd723ebb03c56d9bac8
RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648
......@@ -3830,11 +3863,11 @@ SPEC CHECKSUMS:
react-native-image-picker: 00f0e4aae2710ad1ffbc72f65dfe0e396f6b6508
react-native-mmkv: dea675cf9697ad35940f1687e98e133e1358ef9f
react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac
react-native-onesignal: ab800900cffeca4d9db70a05244013fc8a36ceb8
react-native-onesignal: 33ade92bd91578374c31c5a5a91f45f49c2d6614
react-native-pager-view: 16ba891ddbceb61ee19e6de8ab884793452e7089
react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162
react-native-safe-area-context: 142fade490cbebbe428640b8cbdb09daf17e8191
react-native-skia: 97ee91100acc4f2498d9092ef6f13947d502b617
react-native-skia: 20722d5387a02e4904090fd1a24aa7f6975ca527
react-native-slider: 552edab18aa6e2a52e220a066cc6bf28b6181ac9
react-native-webview: 90edc35a70c053b9b44a07cdd0e22414d433f48c
react-native-widgetkit: efb6680df237463bbe1be3a4d1a1578a1b0bb08f
......@@ -3889,11 +3922,10 @@ SPEC CHECKSUMS:
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
sparkfabrik-react-native-idfa-aaid: 1b72a6264a2175473e309ffa6434db87c58af264
Statsig: 970abcd107e8e64bb68f6b8504a94c39d7f9e318
UIImageColors: d2ef3b0877d203cbb06489eeb78ea8b7788caabe
Yoga: fd4ca32b81c4069b4175ad9460b042e17db94b66
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
PODFILE CHECKSUM: 9f95e7afd1cac471c11319c56b11cea6044f2ecc
PODFILE CHECKSUM: eda9e221a3f51c82395761c3808502347630fff1
COCOAPODS: 1.14.3
......@@ -1363,7 +1363,6 @@
F35AFD3727EE49990011A725 /* Sources */,
F35AFD3827EE49990011A725 /* Frameworks */,
F35AFD3927EE49990011A725 /* Resources */,
40C5355998936D0E3933937A /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -1606,23 +1605,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
40C5355998936D0E3933937A /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension-resources.sh\"\n";
showEnvVarsInLog = 0;
};
420594480338AF2E35636F6B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
......
......@@ -20,13 +20,24 @@ jest.mock('src/lib/RNEthersRs')
// Mock OneSignal package
jest.mock('react-native-onesignal', () => {
return {
setLogLevel: jest.fn(),
setAppId: jest.fn(),
promptForPushNotificationsWithUserResponse: jest.fn(),
setNotificationWillShowInForegroundHandler: jest.fn(),
setNotificationOpenedHandler: jest.fn(),
sendTag: jest.fn(),
getDeviceState: () => ({ userId: 'dummyUserId', pushToken: 'dummyPushToken' }),
OneSignal: {
Debug: {
setLogLevel: jest.fn(),
},
initialize: jest.fn(),
Notifications: {
addEventListener: jest.fn(),
requestPermission: jest.fn(),
},
User: {
addTag: jest.fn(),
addTags: jest.fn(),
getOnesignalId: jest.fn(() => 'dummyUserId'),
pushSubscription: {
getTokenAsync: jest.fn(() => 'dummyPushToken'),
}
},
}
}
})
......
......@@ -83,7 +83,7 @@
"@shopify/flash-list": "1.7.1",
"@shopify/react-native-performance": "4.1.2",
"@shopify/react-native-performance-navigation": "3.0.0",
"@shopify/react-native-skia": "1.6.0",
"@shopify/react-native-skia": "1.7.2",
"@sparkfabrik/react-native-idfa-aaid": "1.2.0",
"@tanstack/react-query": "5.51.16",
"@testing-library/react-hooks": "8.0.1",
......@@ -136,7 +136,7 @@
"react-native-localize": "2.2.6",
"react-native-markdown-display": "7.0.0-alpha.2",
"react-native-mmkv": "2.10.1",
"react-native-onesignal": "4.5.2",
"react-native-onesignal": "5.2.9",
"react-native-pager-view": "6.5.1",
"react-native-permissions": "4.1.5",
"react-native-reanimated": "3.16.7",
......@@ -160,6 +160,7 @@
"typed-redux-saga": "1.5.0",
"uniswap": "workspace:^",
"utilities": "workspace:^",
"uuid": "9.0.0",
"wallet": "workspace:^"
},
"devDependencies": {
......
......@@ -11,7 +11,7 @@ import appsFlyer from 'react-native-appsflyer'
import DeviceInfo from 'react-native-device-info'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { MMKV } from 'react-native-mmkv'
import OneSignal from 'react-native-onesignal'
import { OneSignal } from 'react-native-onesignal'
import { configureReanimatedLogger } from 'react-native-reanimated'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { enableFreeze } from 'react-native-screens'
......@@ -27,6 +27,7 @@ import { persistor, store } from 'src/app/store'
import { TraceUserProperties } from 'src/components/Trace/TraceUserProperties'
import { OfflineBanner } from 'src/components/banners/OfflineBanner'
import { initAppsFlyer } from 'src/features/analytics/appsflyer'
import { useLogMissingMnemonic } from 'src/features/analytics/useLogMissingMnemonic'
import { NotificationToastWrapper } from 'src/features/notifications/NotificationToastWrapper'
import { initOneSignal } from 'src/features/notifications/Onesignal'
import { OneSignalUserTagField } from 'src/features/notifications/constants'
......@@ -263,7 +264,7 @@ function AppOuter(): JSX.Element | null {
const notificationsPriceAlertsEnabled = getFeatureFlag(FeatureFlags.NotificationPriceAlertsIOS)
const notificationsUnfundedWalletEnabled = getFeatureFlag(FeatureFlags.NotificationUnfundedWalletsIOS)
OneSignal.sendTags({
OneSignal.User.addTags({
[OneSignalUserTagField.GatingPriceAlertsEnabled]: notificationsPriceAlertsEnabled ? 'true' : 'false',
[OneSignalUserTagField.GatingUnfundedWalletsEnabled]: notificationsUnfundedWalletEnabled ? 'true' : 'false',
})
......@@ -345,6 +346,8 @@ function AppInner(): JSX.Element {
NativeModules.ThemeModule.setColorScheme(themeSetting)
}, [themeSetting])
useLogMissingMnemonic()
return (
<>
<DevAIAssistantScreen />
......
......@@ -4,10 +4,10 @@ import { ServerOverrides } from 'src/components/experiments/ServerOverrides'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
import { Accordion, Separator } from 'ui/src'
import { spacing } from 'ui/src/theme'
import { GatingOverrides } from 'uniswap/src/components/gating/GatingOverrides'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useAppInsets } from 'uniswap/src/hooks/useAppInsets'
import { GatingOverrides } from 'wallet/src/components/gating/GatingOverrides'
export function ExperimentsModal(): JSX.Element {
const insets = useAppInsets()
......
import { DdRum } from '@datadog/mobile-react-native'
import React, { useCallback, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { BiometricsIconProps, useBiometricsIcon } from 'src/components/icons/useBiometricsIcon'
......@@ -13,6 +14,9 @@ import { updateSwapStartTimestamp } from 'uniswap/src/features/timing/slice'
import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState'
import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow'
/* Need to track the swap modal manually until it's integrated in to react-navigation */
const DATADOG_VIEW_KEY = 'global-swap-modal'
export function SwapModal(): JSX.Element {
const appDispatch = useDispatch()
const { initialState } = useSelector(selectModalState(ModalName.Swap))
......@@ -20,11 +24,14 @@ export function SwapModal(): JSX.Element {
const onClose = useCallback((): void => {
appDispatch(closeModal({ name: ModalName.Swap }))
DdRum.stopView(DATADOG_VIEW_KEY, {}, Date.now()).catch(() => undefined)
}, [appDispatch])
// Update flow start timestamp every time modal is opened for logging
useEffect(() => {
appDispatch(updateSwapStartTimestamp({ timestamp: Date.now() }))
const timestamp = Date.now()
DdRum.startView(DATADOG_VIEW_KEY, ModalName.Swap, {}, timestamp).catch(() => undefined)
appDispatch(updateSwapStartTimestamp({ timestamp }))
}, [appDispatch])
const { openWalletRestoreModal, walletNeedsRestore } = useWalletRestore()
......
......@@ -291,6 +291,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
>
<View
collapsable={false}
dd-action-name="Wallet settings"
focusVisibleStyle={
{
"backgroundColor": "rgba(34,34,34,0.09)",
......
......@@ -253,6 +253,8 @@ function ExploreTabBarButton({ activeScale = 0.98, onLayout, isNarrow }: Explore
onLayout(e)
}
const Wrapper = isIOS ? BlurView : Flex
return (
<TouchableArea activeOpacity={1} style={[styles.searchBar, { borderRadius: borderRadii.roundedFull }]}>
<TestnetModeModal unsupported isOpen={isTestnetWarningModalOpen} onClose={handleTestnetWarningModalClose} />
......@@ -264,7 +266,7 @@ function ExploreTabBarButton({ activeScale = 0.98, onLayout, isNarrow }: Explore
width={isNarrow ? height : undefined}
onLayout={internalOnLayout}
>
<BlurView intensity={isIOS ? 100 : 0}>
<Wrapper {...(isIOS ? { intensity: 100 } : {})}>
<Flex
{...contentProps}
fill
......@@ -294,7 +296,7 @@ function ExploreTabBarButton({ activeScale = 0.98, onLayout, isNarrow }: Explore
</Text>
)}
</Flex>
</BlurView>
</Wrapper>
</AnimatedFlex>
</TapGestureHandler>
</TouchableArea>
......
import isEqual from 'lodash/isEqual'
import { useCallback, useLayoutEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { View } from 'react-native'
import { Flex, GeneratedIcon, Portal, Text, TouchableArea, useWindowDimensions } from 'ui/src'
import { AnimatePresence, Flex, GeneratedIcon, Portal, Text, TouchableArea, useWindowDimensions } from 'ui/src'
import { zIndexes } from 'ui/src/theme'
import { logger } from 'utilities/src/logger/logger'
......@@ -14,6 +14,7 @@ const MENU_PADDING = 8
// used for animation
const ANIMATION_START_POINT = 10
const ANIMATION_TIME = 200
function MenuOption({
title,
......@@ -54,6 +55,7 @@ type StyledContextMenuProps = {
actions: StyledContextMenuAction[]
children: JSX.Element
isAboveTrigger?: boolean
isLeftOfTrigger?: boolean
isOpen: boolean
closeMenu: () => void
openMenu?: () => void
......@@ -82,6 +84,7 @@ export function StyledContextMenu({
closeMenu,
openMenu,
isAboveTrigger = false,
isLeftOfTrigger = false,
}: StyledContextMenuProps): JSX.Element {
const { width: screenWidth } = useWindowDimensions()
......@@ -94,16 +97,24 @@ export function StyledContextMenu({
const recalculateMenuPosition = useCallback((): void => {
if (triggerRef.current) {
triggerRef.current.measure((_fx, _fy, _triggerWidth, triggerHeight, triggerX, triggerY) => {
triggerRef.current.measure((_fx, _fy, triggerWidth, triggerHeight, triggerX, triggerY) => {
const maxUsableWidth = screenWidth - MIN_MENU_PADDING
const pxOverflowRight = triggerX + CONTEXT_MENU_WIDTH - maxUsableWidth
const left = pxOverflowRight > 0 ? triggerX - pxOverflowRight : triggerX
const getLeft = (): number => {
if (isLeftOfTrigger) {
return triggerX - CONTEXT_MENU_WIDTH + triggerWidth
} else {
const pxOverflowRight = triggerX + CONTEXT_MENU_WIDTH - maxUsableWidth
return pxOverflowRight > 0 ? triggerX - pxOverflowRight : triggerX
}
}
// lazy eval to avoid unnecessary calculations
const estimatedMenuHeight =
actions.length * MENU_OPTION_HEIGHT + MENU_OPTION_GAP * (actions.length - 1) + MENU_PADDING * 2
const top = isAboveTrigger ? triggerY - estimatedMenuHeight - MIN_MENU_PADDING : triggerY + triggerHeight
const left = getLeft()
setPosition((prev) => {
const updated = { ...prev }
......@@ -120,7 +131,22 @@ export function StyledContextMenu({
})
})
}
}, [screenWidth, isAboveTrigger, actions.length])
}, [screenWidth, isAboveTrigger, isLeftOfTrigger, actions.length])
// used to delay unmount of the menu until the animation is done
const [isMenuVisible, setIsMenuVisible] = useState(false)
useEffect(() => {
if (isOpen) {
setIsMenuVisible(true)
}
}, [isOpen])
const handleMenuClose = useCallback(() => {
closeMenu()
setTimeout(() => {
setIsMenuVisible(false)
}, ANIMATION_TIME)
}, [closeMenu, setIsMenuVisible])
useLayoutEffect(() => {
if (isOpen) {
......@@ -132,7 +158,7 @@ export function StyledContextMenu({
const action = actions?.[index]
if (!action) {
return closeMenu
return handleMenuClose
}
const { title, onPress: onPressAction } = action
......@@ -147,7 +173,7 @@ export function StyledContextMenu({
})
} finally {
// close the menu no matter what
closeMenu()
handleMenuClose()
}
}
}
......@@ -157,7 +183,16 @@ export function StyledContextMenu({
// since only one of them can be pressed at a time, we don't have to worry about the event being propagated
return (
<>
<Portal display={isOpen ? 'flex' : 'none'} contain="none" position="unset" onPress={(e) => e.stopPropagation()}>
<Portal
display={isOpen || isMenuVisible ? 'flex' : 'none'}
contain="none"
position="unset"
// pass events through if menu is fading out
pointerEvents={!isOpen ? 'none' : 'auto'}
onPress={(e) => {
e.stopPropagation()
}}
>
<Flex
height="100%"
width="100%"
......@@ -166,52 +201,54 @@ export function StyledContextMenu({
backgroundColor="transparent"
style={{ position: 'fixed' }}
zIndex={zIndexes.overlay}
onPress={closeMenu}
onPress={handleMenuClose}
>
{isOpen && (
<TouchableArea
flex={1}
justifyContent="flex-start"
alignItems="flex-start"
backgroundColor="$transparent"
top={position.top}
left={position.left}
position="absolute"
animation="200ms"
enterStyle={{
opacity: 0,
y: isAboveTrigger ? ANIMATION_START_POINT : -ANIMATION_START_POINT,
}}
exitStyle={{
opacity: 0,
y: isAboveTrigger ? ANIMATION_START_POINT : -ANIMATION_START_POINT,
}}
>
<Flex
backgroundColor="$surface1"
p={MENU_PADDING}
borderRadius="$rounded20"
borderColor="$surface3"
borderWidth="$spacing1"
gap={MENU_OPTION_GAP}
<AnimatePresence>
{isOpen && (
<TouchableArea
flex={1}
justifyContent="flex-start"
alignItems="flex-start"
width={CONTEXT_MENU_WIDTH}
shadowRadius="$spacing4"
shadowColor="$shadowColor"
backgroundColor="$transparent"
top={position.top}
left={position.left}
position="absolute"
animation="200ms"
enterStyle={{
opacity: 0,
y: isAboveTrigger ? ANIMATION_START_POINT : -ANIMATION_START_POINT,
}}
exitStyle={{
opacity: 0,
y: isAboveTrigger ? ANIMATION_START_POINT : -ANIMATION_START_POINT,
}}
>
{actions?.map((action, index) => (
<MenuOption
key={action.title}
title={action.title}
icon={action.icon}
iconColor={action.iconColor}
destructive={action.destructive}
onPress={createPressHandler(index)}
/>
))}
</Flex>
</TouchableArea>
)}
<Flex
backgroundColor="$surface1"
p={MENU_PADDING}
borderRadius="$rounded20"
borderColor="$surface3"
borderWidth="$spacing1"
gap={MENU_OPTION_GAP}
alignItems="flex-start"
width={CONTEXT_MENU_WIDTH}
shadowRadius="$spacing4"
shadowColor="$shadowColor"
>
{actions?.map((action, index) => (
<MenuOption
key={action.title}
title={action.title}
icon={action.icon}
iconColor={action.iconColor}
destructive={action.destructive}
onPress={createPressHandler(index)}
/>
))}
</Flex>
</TouchableArea>
)}
</AnimatePresence>
</Flex>
</Portal>
......
......@@ -29,7 +29,7 @@ import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks'
import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks/useCanAddressClaimUnitag'
import { useAccounts } from 'wallet/src/features/wallet/hooks'
export function ManageWalletsModal(): JSX.Element {
......
......@@ -80,7 +80,12 @@ export function BuyNativeTokenModal({
onPress={onClose}
/>
)}
<BuyNativeTokenButton nativeCurrencyInfo={nativeCurrencyInfo} canBridge={true} onPress={onClose} />
<BuyNativeTokenButton
usesStaticText
usesStaticTheme={false}
nativeCurrencyInfo={nativeCurrencyInfo}
onPress={onClose}
/>
{!bridgingTokenWithHighestBalance && <ReceiveButton onPress={onClose} />}
</Flex>
</Flex>
......
import { useTranslation } from 'react-i18next'
import { useOpenReceiveModal } from 'src/features/modals/hooks/useOpenReceiveModal'
import { DeprecatedButton } from 'ui/src'
import { Button, Flex } from 'ui/src'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
......@@ -9,18 +9,19 @@ export function ReceiveButton({ onPress }: { onPress: () => void }): JSX.Element
const openReceiveModal = useOpenReceiveModal()
return (
<Trace logPress element={ElementName.Receive}>
<DeprecatedButton
size="medium"
theme="secondary"
width="100%"
onPress={() => {
openReceiveModal()
onPress?.()
}}
>
{t('common.receive')}
</DeprecatedButton>
</Trace>
<Flex row>
<Trace logPress element={ElementName.Receive}>
<Button
size="medium"
emphasis="secondary"
onPress={() => {
openReceiveModal()
onPress?.()
}}
>
{t('common.receive')}
</Button>
</Trace>
</Flex>
)
}
......@@ -107,6 +107,7 @@ export function TokenDetailsActionButtons({
{userHasBalance && !disabled && (
<StyledContextMenu
isAboveTrigger
isLeftOfTrigger
actions={actionsWithIcons}
isOpen={actionMenuOpen}
closeMenu={closeActionMenu}
......
......@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
import { ListRenderItemInfo } from 'react-native'
import { Flex, Inset, Loader } from 'ui/src'
import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { TokenOptionItem } from 'uniswap/src/components/TokenSelector/items/TokenOptionItem'
import { TokenOptionItem } from 'uniswap/src/components/lists/items/tokens/TokenOptionItem'
import { PortfolioBalance } from 'uniswap/src/features/dataApi/types'
import { FORCurrencyOrBalance, FiatOnRampCurrency } from 'uniswap/src/features/fiatOnRamp/types'
import { getUnsupportedFORTokensWithBalance, isSupportedFORCurrency } from 'uniswap/src/features/fiatOnRamp/utils'
......
import { useEffect, useMemo } from 'react'
import { NativeModules } from 'react-native'
import OneSignal from 'react-native-onesignal'
import { OneSignal } from 'react-native-onesignal'
import { useSelector } from 'react-redux'
import { useBiometricAppSettings } from 'src/features/biometrics/useBiometricAppSettings'
import { useDeviceSupportsBiometricAuth } from 'src/features/biometrics/useDeviceSupportsBiometricAuth'
......@@ -125,7 +125,7 @@ export function TraceUserProperties(): null {
}, [allowAnalytics, isTestnetModeEnabled])
useEffect(() => {
OneSignal.sendTag(OneSignalUserTagField.AccountIsUnfunded, signerAccountsTotalBalance === 0 ? 'true' : 'false')
OneSignal.User.addTag(OneSignalUserTagField.AccountIsUnfunded, signerAccountsTotalBalance === 0 ? 'true' : 'false')
}, [signerAccountsTotalBalance])
return null
......
......@@ -36,6 +36,23 @@ exports[`BackButton renders without error 1`] = `
}
>
<View
collapsable={false}
forwardedRef={[Function]}
jestAnimatedStyle={
{
"value": {
"borderBottomLeftRadius": 999999,
"borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999,
"borderTopRightRadius": 999999,
"transform": [
{
"rotate": "0deg",
},
],
},
}
}
style={
{
"alignItems": "center",
......
......@@ -10,6 +10,7 @@ exports[`CopyTextButton renders without error 1`] = `
>
<View
collapsable={false}
dd-action-name="Copy"
focusVisibleStyle={
{
"backgroundColor": "rgba(34,34,34,0.09)",
......
......@@ -4,9 +4,9 @@ import { useDispatch, useSelector } from 'react-redux'
import { selectCustomEndpoint } from 'src/features/tweaks/selectors'
import { setCustomEndpoint } from 'src/features/tweaks/slice'
import { Accordion, Flex, Text } from 'ui/src'
import { GatingButton } from 'uniswap/src/components/gating/GatingButton'
import { AccordionHeader } from 'uniswap/src/components/gating/GatingOverrides'
import { TextInput } from 'uniswap/src/components/input/TextInput'
import { GatingButton } from 'wallet/src/components/gating/GatingButton'
import { AccordionHeader } from 'wallet/src/components/gating/GatingOverrides'
export function ServerOverrides(): JSX.Element {
const dispatch = useDispatch()
......
......@@ -95,6 +95,111 @@ exports[`SortButton renders without error 1`] = `
Volume
</Text>
<View
collapsable={false}
forwardedRef={[Function]}
jestAnimatedStyle={
{
"value": {
"borderBottomLeftRadius": {
"callStart": null,
"callback": [Function],
"current": 999999,
"finished": true,
"lastTimestamp": 0,
"omega0": 31.622776601683793,
"omega1": NaN,
"onFrame": [Function],
"onStart": [Function],
"reduceMotion": false,
"startTimestamp": 0,
"startValue": 999999,
"timestamp": 0,
"toValue": 999999,
"velocity": 0,
"zeta": 1.1858541225631423,
},
"borderBottomRightRadius": {
"callStart": null,
"callback": [Function],
"current": 999999,
"finished": true,
"lastTimestamp": 0,
"omega0": 31.622776601683793,
"omega1": NaN,
"onFrame": [Function],
"onStart": [Function],
"reduceMotion": false,
"startTimestamp": 0,
"startValue": 999999,
"timestamp": 0,
"toValue": 999999,
"velocity": 0,
"zeta": 1.1858541225631423,
},
"borderTopLeftRadius": {
"callStart": null,
"callback": [Function],
"current": 999999,
"finished": true,
"lastTimestamp": 0,
"omega0": 31.622776601683793,
"omega1": NaN,
"onFrame": [Function],
"onStart": [Function],
"reduceMotion": false,
"startTimestamp": 0,
"startValue": 999999,
"timestamp": 0,
"toValue": 999999,
"velocity": 0,
"zeta": 1.1858541225631423,
},
"borderTopRightRadius": {
"callStart": null,
"callback": [Function],
"current": 999999,
"finished": true,
"lastTimestamp": 0,
"omega0": 31.622776601683793,
"omega1": NaN,
"onFrame": [Function],
"onStart": [Function],
"reduceMotion": false,
"startTimestamp": 0,
"startValue": 999999,
"timestamp": 0,
"toValue": 999999,
"velocity": 0,
"zeta": 1.1858541225631423,
},
"transform": [
{
"rotate": {
"__prefix": "",
"__suffix": "deg",
"callStart": null,
"callback": [Function],
"current": "270deg",
"finished": true,
"lastTimestamp": 0,
"omega0": 31.622776601683793,
"omega1": NaN,
"onFrame": [Function],
"onStart": [Function],
"reduceMotion": false,
"startTimestamp": 0,
"startValue": 270,
"strippedCurrent": 270,
"timestamp": 0,
"toValue": 270,
"velocity": 0,
"zeta": 1.1858541225631423,
},
},
],
},
}
}
style={
{
"alignItems": "center",
......
import { useEffect } from 'react'
import { WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logger } from 'utilities/src/logger/logger'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
import { useSignerAccounts } from 'wallet/src/features/wallet/hooks'
// WALL-6234
export function useLogMissingMnemonic(): void {
const signerMnemonicAccounts = useSignerAccounts()
const mnemonicId = signerMnemonicAccounts[0]?.mnemonicId
useEffect(() => {
const logMissingMnemonic = async (): Promise<void> => {
if (!mnemonicId) {
return
}
const keyringMnemonicIds = await Keyring.getMnemonicIds()
if (keyringMnemonicIds.find((id) => id === mnemonicId)) {
// Ignore if mnemonic is in the keyring.
return
}
const keyringPrivateKeyAddresses = await Keyring.getAddressesForStoredPrivateKeys()
const accountsSortedByTime = signerMnemonicAccounts.sort((a, b) => a.timeImportedMs - b.timeImportedMs)
sendAnalyticsEvent(WalletEventName.KeyringMissingMnemonic, {
mnemonicId,
timeImportedMsFirst: accountsSortedByTime[0]?.timeImportedMs,
timeImportedMsLast: accountsSortedByTime[accountsSortedByTime.length - 1]?.timeImportedMs,
keyringMnemonicIds,
keyringPrivateKeyAddresses,
signerMnemonicAccounts: accountsSortedByTime.map((account) => ({
mnemonicId: account.mnemonicId,
address: account.address,
timeImportedMs: account.timeImportedMs,
})),
})
}
logMissingMnemonic().catch((error) => {
logger.error(error, {
tags: { file: 'useLogMissingMnemonic.ts', function: 'logMissingMnemonic' },
})
})
}, [mnemonicId, signerMnemonicAccounts])
}
......@@ -12,6 +12,7 @@ import { FORQuote, FiatCurrencyInfo, FiatOnRampCurrency } from 'uniswap/src/feat
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo'
import { buildCurrencyId } from 'uniswap/src/utils/currencyId'
import { v4 as uuidv4 } from 'uuid'
interface FiatOnRampContextType {
quotesSections?: SectionListData<FORQuote>[] | undefined
......@@ -35,6 +36,7 @@ interface FiatOnRampContextType {
setIsOffRamp: (isOffRamp: boolean) => void
isTokenInputMode: boolean
setIsTokenInputMode: React.Dispatch<React.SetStateAction<boolean>>
externalTransactionIdSuffix: string
}
const initialState: FiatOnRampContextType = {
......@@ -56,6 +58,7 @@ const initialState: FiatOnRampContextType = {
setIsOffRamp: () => undefined,
isTokenInputMode: false,
setIsTokenInputMode: () => undefined,
externalTransactionIdSuffix: '',
}
const FiatOnRampContext = createContext<FiatOnRampContextType>(initialState)
......@@ -73,6 +76,10 @@ export function FiatOnRampProvider({ children }: { children: React.ReactNode }):
const [isTokenInputMode, setIsTokenInputMode] = useState<boolean>(false)
const [fiatAmount, setFiatAmount] = useState<number | undefined>()
const [tokenAmount, setTokenAmount] = useState<number | undefined>()
const [externalTransactionIdSuffix] = useState<string>(() => {
// Generate a UUID and extract the last 4 groups as the suffix
return uuidv4().split('-').slice(1).join('-')
})
const { initialState: initialModalState } = useSelector(selectModalState(ModalName.FiatOnRampAggregator))
const prefilledCurrency = initialModalState?.prefilledCurrency
......@@ -125,6 +132,7 @@ export function FiatOnRampProvider({ children }: { children: React.ReactNode }):
setIsOffRamp,
isTokenInputMode,
setIsTokenInputMode,
externalTransactionIdSuffix,
}}
>
{children}
......
......@@ -183,6 +183,23 @@ exports[`renders collection preview card 1`] = `
</View>
</View>
<View
collapsable={false}
forwardedRef={[Function]}
jestAnimatedStyle={
{
"value": {
"borderBottomLeftRadius": 999999,
"borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999,
"borderTopRightRadius": 999999,
"transform": [
{
"rotate": "180deg",
},
],
},
}
}
style={
{
"alignItems": "center",
......
import { Linking } from 'react-native'
import OneSignal, { NotificationReceivedEvent, OpenedEvent } from 'react-native-onesignal'
import { OneSignal } from 'react-native-onesignal'
import { NotificationType } from 'src/features/notifications/constants'
import { config } from 'uniswap/src/config'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
......@@ -12,9 +12,12 @@ import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient'
export const initOneSignal = (): void => {
OneSignal.setAppId(config.onesignalAppId)
// Uncomment for local debugging
// OneSignal.Debug.setLogLevel(LogLevel.Verbose)
OneSignal.setNotificationWillShowInForegroundHandler((event: NotificationReceivedEvent) => {
OneSignal.initialize(config.onesignalAppId)
OneSignal.Notifications.addEventListener('foregroundWillDisplay', (event) => {
const notification = event.getNotification()
const additionalData = notification.additionalData as { notification_type?: string }
const notificationType = additionalData?.notification_type
......@@ -41,11 +44,13 @@ export const initOneSignal = (): void => {
}
}
// Complete with undefined means don't show OS notifications while app is in foreground
event.complete(enabled ? notification : undefined)
if (!enabled) {
// Prevent default will avoid showing OS notifications while app is in foreground
event.preventDefault()
}
})
OneSignal.setNotificationOpenedHandler((event: OpenedEvent) => {
OneSignal.Notifications.addEventListener('click', (event) => {
logger.debug('Onesignal', 'setNotificationOpenedHandler', `Notification opened: ${event.notification}`)
setTimeout(
......@@ -67,7 +72,7 @@ export const initOneSignal = (): void => {
getUniqueId()
.then((deviceId) => {
if (deviceId) {
OneSignal.setExternalUserId(deviceId)
OneSignal.login(deviceId)
}
})
.catch(() =>
......@@ -81,23 +86,20 @@ export const initOneSignal = (): void => {
}
export const promptPushPermission = async (): Promise<boolean> => {
return new Promise((resolve) => {
OneSignal.promptForPushNotificationsWithUserResponse((response) => {
logger.debug('Onesignal', 'promptForPushNotificationsWithUserResponse', `Prompt response: ${response}`)
resolve(response)
})
})
const response = await OneSignal.Notifications.requestPermission(true)
logger.debug('Onesignal', 'promptForPushNotificationsWithUserResponse', `Prompt response: ${response}`)
return response
}
export const getOneSignalUserIdOrError = async (): Promise<string> => {
const onesignalUserId = (await OneSignal.getDeviceState())?.userId
const onesignalUserId = await OneSignal.User.getOnesignalId()
if (!onesignalUserId) {
throw new Error('Onesignal user ID is not defined')
}
return onesignalUserId
}
export const getOneSignalPushToken = async (): Promise<string | undefined> => {
const onesignalPushToken = (await OneSignal.getDeviceState())?.pushToken
export const getOneSignalPushToken = async (): Promise<string | null> => {
const onesignalPushToken = await OneSignal.User.pushSubscription.getTokenAsync()
return onesignalPushToken
}
import OneSignal from 'react-native-onesignal'
import { OneSignal } from 'react-native-onesignal'
import { NotifSettingType, OneSignalUserTagField } from 'src/features/notifications/constants'
import { selectAllPushNotificationSettings } from 'src/features/notifications/selectors'
import { initNotifsForNewUser, updateNotifSettings } from 'src/features/notifications/slice'
......@@ -28,7 +28,7 @@ function* syncWithOneSignal() {
if (finishedOnboarding) {
const { generalUpdatesEnabled, priceAlertsEnabled } = yield* select(selectAllPushNotificationSettings)
yield* call(OneSignal.sendTags, {
yield* call(OneSignal.User.addTags, {
[NotifSettingType.GeneralUpdates]: generalUpdatesEnabled.toString(),
[NotifSettingType.PriceAlerts]: priceAlertsEnabled.toString(),
})
......@@ -36,7 +36,7 @@ function* syncWithOneSignal() {
}
function* initNewUser() {
yield* call(OneSignal.sendTags, {
yield* call(OneSignal.User.addTags, {
[NotifSettingType.GeneralUpdates]: 'true',
[NotifSettingType.PriceAlerts]: 'true',
})
......@@ -47,7 +47,7 @@ function* processFinalizedTx(action: ReturnType<typeof finalizeTransaction>) {
action.payload.typeInfo.type === TransactionType.Swap && action.payload.status === TransactionStatus.Success
if (isSuccessfulSwap) {
yield* call(
OneSignal.sendTag,
OneSignal.User.addTag,
OneSignalUserTagField.SwapLastCompletedAt,
Math.floor(Date.now() / ONE_SECOND_MS).toString(),
)
......
import { SharedEventName } from '@uniswap/analytics-events'
import OneSignal from 'react-native-onesignal'
import { OneSignal } from 'react-native-onesignal'
import { useDispatch } from 'react-redux'
import { OnboardingStackBaseParams, useOnboardingStackNavigation } from 'src/app/navigation/types'
import { OneSignalUserTagField } from 'src/features/notifications/constants'
......@@ -35,7 +35,7 @@ export function useCompleteOnboardingCallback({
// Initializes notification settings
dispatch(initNotifsForNewUser())
OneSignal.sendTags({
OneSignal.User.addTags({
[OneSignalUserTagField.OnboardingWalletAddress]: onboardingAddresses[0] ?? '',
[OneSignalUserTagField.OnboardingCompletedAt]: Math.floor(Date.now() / ONE_SECOND_MS).toString(),
[OneSignalUserTagField.OnboardingImportType]: importType,
......
......@@ -56,6 +56,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element |
quoteCurrency,
fiatAmount,
tokenAmount,
externalTransactionIdSuffix,
} = useFiatOnRampContext()
const serviceProvider = selectedQuote?.serviceProviderDetails
......@@ -63,6 +64,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element |
activeAccountAddress,
quoteCurrency.currencyInfo?.currency.chainId ?? UniverseChainId.Mainnet,
serviceProvider?.serviceProvider,
externalTransactionIdSuffix,
)
const onError = useCallback((): void => {
......
......@@ -140,6 +140,7 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
isOffRamp,
isTokenInputMode,
setIsTokenInputMode,
externalTransactionIdSuffix,
} = useFiatOnRampContext()
const { appFiatCurrencySupportedInMeld, meldSupportedFiatCurrency, supportedFiatCurrencies } =
......@@ -288,6 +289,7 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
fiatCurrency: meldSupportedFiatCurrency.code,
chainId: quoteCurrency.currencyInfo?.currency.chainId,
isTokenInputMode,
externalTransactionIdSuffix,
},
)
......@@ -392,6 +394,7 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
token: currency.currencyInfo.currency.symbol.toLowerCase(),
isUnsupported: !isSupportedFORCurrency(currency),
chainId: currency.currencyInfo?.currency.chainId,
externalTransactionIdSuffix,
},
)
}
......
......@@ -39,6 +39,7 @@ import { useWalletRestore } from 'src/features/wallet/hooks'
import { HomeScreenQuickActions } from 'src/screens/HomeScreen/HomeScreenQuickActions'
import { HomeScreenTabIndex } from 'src/screens/HomeScreen/HomeScreenTabIndex'
import { useHomeScreenState } from 'src/screens/HomeScreen/useHomeScreenState'
import { useHomeScreenTracking } from 'src/screens/HomeScreen/useHomeScreenTracking'
import { useHomeScrollRefs } from 'src/screens/HomeScreen/useHomeScrollRefs'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { useOpenBackupReminderModal } from 'src/utils/useOpenBackupReminderModal'
......@@ -88,6 +89,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
const { showEmptyWalletState, isTabsDataLoaded } = useHomeScreenState()
useHomeScreenTracking()
// opens the wallet restore modal if recovery phrase is missing after the app is opened
useWalletRestore({ openModalImmediately: true })
// Record a heartbeat for anonymous user DAU
......
import { DdSdkReactNative } from '@datadog/mobile-react-native'
import { useEffect } from 'react'
import { useSelector } from 'react-redux'
import { selectFavoriteTokens } from 'uniswap/src/features/favorites/selectors'
/**
* Helper hook for the home screen to track any user specific attributes.
*/
export function useHomeScreenTracking(): void {
const favoriteCurrencyIds = useSelector(selectFavoriteTokens)
useEffect(() => {
DdSdkReactNative.setAttributes({
favoriteTokensCount: favoriteCurrencyIds.length,
}).catch(() => undefined)
}, [favoriteCurrencyIds.length])
}
......@@ -459,6 +459,7 @@ exports[`RestoreCloudBackupPasswordScreen renders correctly 1`] = `
>
<View
collapsable={false}
dd-action-name="Continue"
focusVisibleStyle={
{
"scaleX": 0.98,
......
......@@ -24,7 +24,7 @@ import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { LANDING_ANIMATION_DURATION, LandingBackground } from 'wallet/src/components/landing/LandingBackground'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks'
import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks/useCanAddressClaimUnitag'
type Props = NativeStackScreenProps<OnboardingStackParamList, OnboardingScreens.Landing>
......
......@@ -114,8 +114,9 @@ export const HeaderRightElement = memo(function HeaderRightElement(): JSX.Elemen
}, [menuActions, isVisible, colors.neutral2])
return (
<AnimatedFlex row alignItems="center" entering={FadeIn} gap="$spacing20">
<AnimatedFlex row alignItems="center" entering={FadeIn} gap="$spacing12">
<StyledContextMenu
isLeftOfTrigger
actions={actionsWithIcons}
isOpen={isMenuOpen}
closeMenu={closeMenu}
......@@ -128,7 +129,7 @@ export const HeaderRightElement = memo(function HeaderRightElement(): JSX.Elemen
>
<Flex
hitSlop={{ right: 5, left: 20, top: 20, bottom: 20 }}
style={{ padding: spacing.spacing8, marginRight: -spacing.spacing8 }}
style={{ padding: spacing.spacing8 }}
testID={TestID.TokenDetailsMoreButton}
>
<EllipsisIcon color={ellipsisColor} height={iconSizes.icon16} width={iconSizes.icon16} />
......
......@@ -39,7 +39,6 @@ ignores: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-onboarding',
'@storybook/testing-library',
'@storybook/blocks',
'@storybook/preset-create-react-app',
'storybook-addon-pseudo-states',
......
......@@ -90,6 +90,7 @@ module.exports = {
plugins: [
new DefinePlugin({
__DEV__: isDev,
'process.env.EXPO_OS': '"web"',
}),
// Webpack 5 does not polyfill node globals, so we do so for those necessary:
new ProvidePlugin({
......
......@@ -12,7 +12,7 @@
"sitemap:generate": "node scripts/generate-sitemap.js",
"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": "NODE_OPTIONS=--max-old-space-size=8192 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",
......@@ -95,7 +95,6 @@
"@storybook/react-webpack5": "8.5.2",
"@storybook/test": "8.5.2",
"@storybook/test-runner": "0.21.0",
"@storybook/testing-library": "0.2.2",
"@swc/core": "1.3.72",
"@swc/jest": "0.2.29",
"@swc/plugin-styled-components": "1.5.97",
......@@ -279,6 +278,7 @@
"react-is": "18.3.1",
"react-markdown": "4.3.1",
"react-native-gesture-handler": "2.21.2",
"react-native-reanimated": "3.16.7",
"react-popper": "2.3.0",
"react-redux": "8.0.5",
"react-router-dom": "6.10.0",
......
......@@ -126,4 +126,16 @@
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://app.uniswap.org/positions/create</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/positions</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
</urlset>
......@@ -42,6 +42,7 @@
"https://*.twnodes.com",
"https://*.uniswap.org",
"https://*.walletconnect.com",
"https://*.walletconnect.org",
"https://*.zerion.io",
"https://alfajores-forno.celo-testnet.org",
"https://api.avax.network/ext/bc/C/rpc",
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import styled from 'lib/styled-components'
import { CopyHelper, EllipsisStyle } from 'theme/components'
import { CopyHelper } from 'theme/components/CopyHelper'
import { EllipsisStyle } from 'theme/components/styles'
import { Flex } from 'ui/src'
import { Unitag } from 'ui/src/components/icons/Unitag'
import { useENSName } from 'uniswap/src/features/ens/api'
......
......@@ -6,7 +6,7 @@ import styled from 'lib/styled-components'
import { ReactNode, useReducer } from 'react'
import { Info } from 'react-feather'
import { Text } from 'rebass'
import { ExternalLink } from 'theme/components'
import { ExternalLink } from 'theme/components/Links'
import { ThemedText } from 'theme/components/text'
import { uniswapUrls } from 'uniswap/src/constants/urls'
......
......@@ -11,7 +11,8 @@ import Row from 'components/deprecated/Row'
import styled from 'lib/styled-components'
import { useCallback } from 'react'
import { SignatureType } from 'state/signatures/types'
import { EllipsisStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { EllipsisStyle } from 'theme/components/styles'
import { BridgeIcon } from 'uniswap/src/components/CurrencyLogo/SplitLogo'
import {
TransactionStatus,
......
......@@ -10,7 +10,8 @@ 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 { ThemedText } from 'theme/components'
import { ExternalLink } from 'theme/components/Links'
import { Flex, Text } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
......
......@@ -4,7 +4,7 @@ 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 { ExternalLink } from 'theme/components/Links'
import { ellipseMiddle } from 'utilities/src/addresses'
import { NumberType, useFormatter } from 'utils/formatNumbers'
......
......@@ -43,22 +43,6 @@ exports[`CancelOrdersDialog should render limit order text 1`] = `
gap: 12px;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c4 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -155,6 +139,22 @@ exports[`CancelOrdersDialog should render limit order text 1`] = `
align-items: center;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
background-color: #FFFFFF;
border-radius: 16px;
......@@ -498,22 +498,6 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = `
gap: 12px;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c4 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -610,6 +594,22 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = `
align-items: center;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
background-color: #FFFFFF;
border-radius: 16px;
......
......@@ -43,22 +43,6 @@ exports[`OrderContent should render without error, filled order 1`] = `
gap: 12px;
}
.c4 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c15 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -78,14 +62,6 @@ exports[`OrderContent should render without error, filled order 1`] = `
opacity: 0.4;
}
.c6 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #22222212;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -145,6 +121,30 @@ exports[`OrderContent should render without error, filled order 1`] = `
gap: 8px;
}
.c4 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c6 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #22222212;
}
.c12 {
cursor: auto;
color: #7D7D7D;
......@@ -537,22 +537,6 @@ exports[`OrderContent should render without error, limit order 1`] = `
gap: 12px;
}
.c4 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c17 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -572,14 +556,6 @@ exports[`OrderContent should render without error, limit order 1`] = `
opacity: 0.4;
}
.c6 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #22222212;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -639,6 +615,30 @@ exports[`OrderContent should render without error, limit order 1`] = `
gap: 8px;
}
.c4 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c6 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #22222212;
}
.c12 {
cursor: auto;
color: #7D7D7D;
......@@ -1075,30 +1075,6 @@ exports[`OrderContent should render without error, open order 1`] = `
gap: 12px;
}
.c4 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c6 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #22222212;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -1158,6 +1134,30 @@ exports[`OrderContent should render without error, open order 1`] = `
gap: 8px;
}
.c4 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c6 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #22222212;
}
.c12 {
cursor: auto;
color: #7D7D7D;
......
......@@ -162,6 +162,16 @@ exports[`OffchainOrderLineItem should render type TRANSACTION_ID 1`] = `
letter-spacing: -0.01em;
}
.c1 {
cursor: auto;
color: #7D7D7D;
}
.c2 {
text-align: right;
overflow-wrap: break-word;
}
.c3 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -181,16 +191,6 @@ exports[`OffchainOrderLineItem should render type TRANSACTION_ID 1`] = `
opacity: 0.4;
}
.c1 {
cursor: auto;
color: #7D7D7D;
}
.c2 {
text-align: right;
overflow-wrap: break-word;
}
<span
class=""
style="display: contents;"
......
......@@ -14,7 +14,8 @@ 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 { ThemedText } from 'theme/components'
import { EllipsisStyle } from 'theme/components/styles'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { Checkbox, useMedia } from 'ui/src'
import { useFormatter } from 'utils/formatNumbers'
......
......@@ -42,29 +42,6 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = `
gap: 4px;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c6 {
display: -webkit-box;
display: -webkit-flex;
......@@ -88,6 +65,29 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = `
flex-grow: 1;
}
.c5 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
gap: 12px;
height: 68px;
......
......@@ -15,7 +15,8 @@ import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletConten
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { EllipsisStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { EllipsisStyle } from 'theme/components/styles'
import { Text, Tooltip } from 'ui/src'
import { ContextMenu } from 'uniswap/src/components/menus/ContextMenuV2'
import { NATIVE_TOKEN_PLACEHOLDER } from 'uniswap/src/constants/addresses'
......
......@@ -3,7 +3,8 @@ import Row from 'components/deprecated/Row'
import styled, { useTheme } from 'lib/styled-components'
import { ReactNode } from 'react'
import { ArrowRight } from 'react-feather'
import { ClickableStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { ClickableStyle } from 'theme/components/styles'
import { Text } from 'ui/src'
const Container = styled.button`
......
......@@ -12,8 +12,9 @@ import { ReactNode } from 'react'
import { ChevronRight } from 'react-feather'
import { Trans } from 'react-i18next'
import { useOpenModal } from 'state/application/hooks'
import { ClickableStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import ThemeToggle from 'theme/components/ThemeToggle'
import { ClickableStyle } from 'theme/components/styles'
import { Flex } from 'ui/src'
import { LockedDocument } from 'ui/src/components/icons/LockedDocument'
import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3'
......
......@@ -2,7 +2,8 @@ import { ScrollBarStyles } from 'components/Common/styles'
import Column from 'components/deprecated/Column'
import styled from 'lib/styled-components'
import { ArrowLeft } from 'react-feather'
import { ClickableStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { ClickableStyle } from 'theme/components/styles'
const Menu = styled(Column)`
width: 100%;
......
import { AddressDisplay } from 'components/AccountDetails/AddressDisplay'
import StatusIcon from 'components/Identicon/StatusIcon'
import styled from 'lib/styled-components'
import { CopyHelper, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { CopyHelper } from 'theme/components/CopyHelper'
import { Text } from 'ui/src'
import { shortenAddress } from 'utilities/src/addresses'
......
......@@ -5,7 +5,8 @@ import { ReactNode } from 'react'
import { Check } from 'react-feather'
import type { To } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { ClickableStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { ClickableStyle } from 'theme/components/styles'
import { breakpoints } from 'ui/src/theme'
const InternalLinkMenuItem = styled(Link)`
......
......@@ -4,7 +4,7 @@ import { useAccount } from 'hooks/useAccount'
import styled, { useTheme } from 'lib/styled-components'
import { ChangeEvent, ReactNode, useCallback } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { ExternalLink } from 'theme/components'
import { ExternalLink } from 'theme/components/Links'
import { flexColumnNoWrap } from 'theme/styles'
import { Text } from 'ui/src'
import { useENS } from 'uniswap/src/features/ens/useENS'
......
......@@ -5,7 +5,8 @@ import { useCallback } from 'react'
import { Trans } from 'react-i18next'
import { useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { CopyHelper, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { CopyHelper } from 'theme/components/CopyHelper'
import { Flex, QRCodeDisplay, Text, useSporeColors } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos'
......
import styled, { DefaultTheme } from 'lib/styled-components'
import { readableColor } from 'polished'
import { PropsWithChildren } from 'react'
export enum BadgeVariant {
DEFAULT = 'DEFAULT',
NEGATIVE = 'NEGATIVE',
POSITIVE = 'POSITIVE',
PRIMARY = 'PRIMARY',
WARNING = 'WARNING',
PROMOTIONAL = 'PROMOTIONAL',
BRANDED = 'BRANDED',
SOFT = 'SOFT',
WARNING_OUTLINE = 'WARNING_OUTLINE',
}
interface BadgeProps {
variant?: BadgeVariant
}
function pickBackgroundColor(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
switch (variant) {
case BadgeVariant.BRANDED:
return theme.brandedGradient
case BadgeVariant.PROMOTIONAL:
return theme.promotionalGradient
case BadgeVariant.NEGATIVE:
return theme.critical
case BadgeVariant.POSITIVE:
return theme.success
case BadgeVariant.SOFT:
return theme.accent2
case BadgeVariant.PRIMARY:
return theme.accent1
case BadgeVariant.WARNING:
return theme.deprecated_accentWarning
case BadgeVariant.WARNING_OUTLINE:
return 'transparent'
default:
return theme.surface2
}
}
function pickBorder(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
switch (variant) {
case BadgeVariant.WARNING_OUTLINE:
return `1px solid ${theme.deprecated_accentWarning}`
default:
return 'unset'
}
}
function pickFontColor(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
switch (variant) {
case BadgeVariant.BRANDED:
return theme.darkMode ? theme.neutral1 : theme.white
case BadgeVariant.NEGATIVE:
return readableColor(theme.critical)
case BadgeVariant.POSITIVE:
return readableColor(theme.success)
case BadgeVariant.SOFT:
return theme.accent1
case BadgeVariant.WARNING:
return readableColor(theme.deprecated_accentWarning)
case BadgeVariant.WARNING_OUTLINE:
return theme.deprecated_accentWarning
default:
return theme.neutral2
}
}
const Badge = styled.div<PropsWithChildren<BadgeProps>>`
align-items: center;
background: ${({ theme, variant }) => pickBackgroundColor(variant, theme)};
border: ${({ theme, variant }) => pickBorder(variant, theme)};
border-radius: 0.5rem;
color: ${({ theme, variant }) => pickFontColor(variant, theme)};
display: inline-flex;
padding: 4px 6px;
justify-content: center;
font-weight: 535;
`
export default Badge
......@@ -3,7 +3,8 @@ import { useTheme } from 'lib/styled-components'
import { useState } from 'react'
import { Globe, X } from 'react-feather'
import { Trans } from 'react-i18next'
import { ClickableTamaguiStyle, ExternalLink } from 'theme/components'
import { ExternalLink } from 'theme/components/Links'
import { ClickableTamaguiStyle } from 'theme/components/styles'
import { capitalize } from 'tsafe'
import { Flex, Text, styled as tamaguiStyled } from 'ui/src'
import { iconSizes, zIndexes } from 'ui/src/theme'
......
......@@ -7,7 +7,7 @@ import { useCallback, useState } from 'react'
import { Copy } from 'react-feather'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { ClickableStyle } from 'theme/components'
import { ClickableStyle } from 'theme/components/styles'
import { useMedia } from 'ui/src'
import { shortenAddress } from 'utilities/src/addresses'
......
......@@ -6,7 +6,8 @@ import ms from 'ms'
import { useMemo, useState } from 'react'
import { AlertTriangle, X } from 'react-feather'
import { Trans } from 'react-i18next'
import { ClickableTamaguiStyle, ExternalLink } from 'theme/components'
import { ExternalLink } from 'theme/components/Links'
import { ClickableTamaguiStyle } from 'theme/components/styles'
import { Flex, styled as tamaguiStyled } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { useUniswapContext } from 'uniswap/src/contexts/UniswapContext'
......
......@@ -4,12 +4,12 @@ import { getProtocolColor, getProtocolName } from 'graphql/data/util'
import { useTheme } from 'lib/styled-components'
import { UTCTimestamp } from 'lightweight-charts'
import { ReactElement, ReactNode } from 'react'
import { EllipsisTamaguiStyle } from 'theme/components'
import { EllipsisTamaguiStyle } from 'theme/components/styles'
import { Flex, Text, styled } from 'ui/src'
import { PriceSource } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NumberType, useFormatter } from 'utils/formatNumbers'
export type ChartHeaderProtocolInfo = { protocol: PriceSource; value?: number }
type ChartHeaderProtocolInfo = { protocol: PriceSource; value?: number }
const ProtocolLegendWrapper = styled(Flex, {
position: 'absolute',
......
......@@ -19,7 +19,7 @@ import {
} from 'pages/Pool/Positions/create/utils'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ClickableTamaguiStyle } from 'theme/components'
import { ClickableTamaguiStyle } from 'theme/components/styles'
import {
Button,
Flex,
......
......@@ -25,7 +25,7 @@ interface TVLChartParams extends ChartModelParams<StackedLineData> {
gradients?: { start: string; end: string }[]
}
export class TVLChartModel extends ChartModel<StackedLineData> {
class TVLChartModel extends ChartModel<StackedLineData> {
protected series: ISeriesApi<'Custom'>
private hoveredLogicalIndex: Logical | null | undefined
......
......@@ -49,7 +49,7 @@ class VolumeChartModel extends CustomVolumeChartModel<SingleHistogramData> {
}
// eslint-disable-next-line consistent-return
export function formatHistoryDuration(t: TFunction, duration: HistoryDuration): string {
function formatHistoryDuration(t: TFunction, duration: HistoryDuration): string {
switch (duration) {
case HistoryDuration.FiveMinute:
return t('common.pastFiveMinutes')
......
import { ChartHeaderProtocolInfo } from 'components/Charts/ChartHeader'
import { CustomHistogramData, StackedHistogramData } from 'components/Charts/VolumeChart/renderer'
import { PriceSource } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
export function isStackedHistogramData(data: CustomHistogramData): data is StackedHistogramData {
return (data as StackedHistogramData).values !== undefined
......@@ -20,17 +18,6 @@ export function getCumulativeVolume(data: CustomHistogramData[]) {
return data.reduce((sum, curr) => (sum += getCumulativeSum(curr)), 0)
}
export function getVolumeProtocolInfo(
data: StackedHistogramData | undefined,
sources: PriceSource[],
): ChartHeaderProtocolInfo[] {
const info = new Array<ChartHeaderProtocolInfo>()
for (const source of sources) {
info.push({ protocol: source, value: data?.values[source] })
}
return info
}
/* Copied from https://github.com/tradingview/lightweight-charts/blob/master/plugin-examples/src/helpers/ */
interface BitmapPositionLength {
......
......@@ -6,7 +6,7 @@ import { SwapResult } from 'hooks/useSwapCallback'
import { Trans } from 'react-i18next'
import { InterfaceTrade, TradeFillType } from 'state/routing/types'
import { isLimitTrade, isUniswapXTrade } from 'state/routing/utils'
import { ExternalLink } from 'theme/components'
import { ExternalLink } from 'theme/components/Links'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
......
......@@ -20,8 +20,8 @@ import { InterfaceTrade, TradeFillType } from 'state/routing/types'
import { isLimitTrade, isUniswapXTradeType } from 'state/routing/utils'
import { useOrder } from 'state/signatures/hooks'
import { useIsTransactionConfirmed } from 'state/transactions/hooks'
import { ExternalLink } from 'theme/components'
import { AnimationType } from 'theme/components/FadePresence'
import { ExternalLink } from 'theme/components/Links'
import { ThemedText } from 'theme/components/text'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { uniswapUrls } from 'uniswap/src/constants/urls'
......
......@@ -4,7 +4,8 @@ import Column from 'components/deprecated/Column'
import Row, { RowBetween } from 'components/deprecated/Row'
import styled, { Keyframes, keyframes } from 'lib/styled-components'
import { ReactElement, useEffect, useState } from 'react'
import { ExternalLink, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { ExternalLink } from 'theme/components/Links'
import { Flex } from 'ui/src'
import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types'
......
......@@ -85,22 +85,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
justify-content: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c16 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -158,6 +142,22 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
align-items: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
margin: 48px 0 8px;
}
......@@ -410,22 +410,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
justify-content: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c16 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -483,6 +467,22 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
align-items: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
margin: 48px 0 8px;
}
......@@ -755,22 +755,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
justify-content: center;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -809,6 +793,22 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
align-items: center;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
margin: 48px 0 8px;
}
......@@ -1030,22 +1030,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
gap: 8px;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -1084,6 +1068,22 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
align-items: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
margin: 48px 0 8px;
}
......@@ -1357,22 +1357,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
justify-content: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c16 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -1430,6 +1414,22 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
align-items: center;
}
.c10 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c11 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
margin: 48px 0 8px;
}
......@@ -1740,22 +1740,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
justify-content: center;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
......@@ -1794,6 +1778,22 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
align-items: center;
}
.c12 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
margin: 48px 0 8px;
}
......
......@@ -2,7 +2,9 @@ import Column from 'components/deprecated/Column'
import styled, { useTheme } from 'lib/styled-components'
import { Slash } from 'react-feather'
import { Trans } from 'react-i18next'
import { CopyHelper, ExternalLink, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { CopyHelper } from 'theme/components/CopyHelper'
import { ExternalLink } from 'theme/components/Links'
import { Flex, Text } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
......
......@@ -3,7 +3,8 @@ import Row from 'components/deprecated/Row'
import styled, { css } from 'lib/styled-components'
import { X } from 'react-feather'
import { Trans } from 'react-i18next'
import { ClickableStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { ClickableStyle } from 'theme/components/styles'
import { useFormatter } from 'utils/formatNumbers'
interface LimitPriceButtonProps {
......
......@@ -4,7 +4,8 @@ import Row from 'components/deprecated/Row'
import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider'
import styled from 'lib/styled-components'
import { Trans } from 'react-i18next'
import { ClickableStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { ClickableStyle } from 'theme/components/styles'
import { Text } from 'ui/src'
const CurrencySymbolContainer = styled.span`
......
......@@ -20,7 +20,8 @@ import { useCallback, useMemo, useState } from 'react'
import { useLimitContext } from 'state/limit/LimitContext'
import { CurrencyState } from 'state/swap/types'
import { useSwapAndLimitContext } from 'state/swap/useSwapContext'
import { ClickableStyle, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { ClickableStyle } from 'theme/components/styles'
import { Locale } from 'uniswap/src/features/language/constants'
import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
......
......@@ -43,22 +43,6 @@ exports[`<Dialog /> renders different button types 1`] = `
gap: 12px;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c4 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -155,6 +139,22 @@ exports[`<Dialog /> renders different button types 1`] = `
align-items: center;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
background-color: #FFFFFF;
border-radius: 16px;
......@@ -437,22 +437,6 @@ exports[`<Dialog /> renders the Dialog component correctly 1`] = `
gap: 12px;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c4 {
-webkit-text-decoration: none;
text-decoration: none;
......@@ -549,6 +533,22 @@ exports[`<Dialog /> renders the Dialog component correctly 1`] = `
align-items: center;
}
.c11 {
color: #222222;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c13 {
color: #7D7D7D;
-webkit-letter-spacing: -0.01em;
-moz-letter-spacing: -0.01em;
-ms-letter-spacing: -0.01em;
letter-spacing: -0.01em;
}
.c2 {
background-color: #FFFFFF;
border-radius: 16px;
......
......@@ -3,7 +3,9 @@ import { useIsMobile } from 'hooks/screenSize/useIsMobile'
import styled from 'lib/styled-components'
import { PropsWithChildren, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { CopyToClipboard, ExternalLink, ThemedText } from 'theme/components'
import { ThemedText } from 'theme/components'
import { CopyToClipboard } from 'theme/components/CopyHelper'
import { ExternalLink } from 'theme/components/Links'
import { Button, Flex, TouchableArea } from 'ui/src'
import { CopyAlt } from 'ui/src/components/icons/CopyAlt'
import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron'
......@@ -13,12 +15,13 @@ const Code = styled.code`
font-weight: 485;
font-size: 12px;
line-height: 16px;
word-sentry.wrap: break-word;
word-wrap: break-word;
width: 100%;
color: ${({ theme }) => theme.neutral1};
font-family: ${({ theme }) => theme.fonts.code};
overflow: scroll;
max-height: calc(100vh - 450px);
-webkit-overflow-scrolling: touch;
`
const Separator = styled.div`
......@@ -27,37 +30,38 @@ const Separator = styled.div`
const Fallback = ({ error, eventId }: { error: Error; eventId: string | null }) => {
const { t } = useTranslation()
const isMobile = useIsMobile()
const errorDetails = error.stack || error.message
return (
<Flex width="100vw" height="100vh">
<Flex width="100%" p="$spacing1" maxWidth={500} centered m="auto">
<Flex gap="$gap24">
<ErrorDetailsSection errorDetails={errorDetails} eventId={eventId} />
<Flex row gap="$gap12">
<Flex height="100%" width="100%" position="absolute" centered top={0} left={0} right={0} bottom={0}>
<Flex
gap="$gap24"
width="100%"
p={isMobile ? '$spacing16' : '$spacing1'}
maxWidth={isMobile ? '100%' : 500}
centered
>
<ErrorDetailsSection errorDetails={errorDetails} eventId={eventId} />
<Flex width="100%" row gap="$gap12">
<Flex row flexBasis={0} flexGrow={1}>
<Button emphasis="primary" size="small" variant="branded" onPress={() => window.location.reload()}>
{t('common.reload.label')}
</Button>
<ExternalLink
style={{ flexGrow: 1, flexBasis: 0 }}
id="get-support-on-discord"
href={uniswapUrls.helpRequestUrl}
target="_blank"
>
<Flex row>
<Button
alignSelf="stretch"
emphasis="secondary"
size="small"
variant="branded"
onPress={() => window.location.reload()}
>
{t('common.getSupport.button')}
</Button>
</Flex>
</ExternalLink>
</Flex>
<ExternalLink
style={{ flexGrow: 1, flexBasis: 0 }}
id="get-support-on-discord"
href={uniswapUrls.helpRequestUrl}
target="_blank"
>
<Flex row>
<Button emphasis="secondary" size="small" variant="branded">
{t('common.getSupport.button')}
</Button>
</Flex>
</ExternalLink>
</Flex>
</Flex>
</Flex>
......@@ -82,7 +86,13 @@ function ErrorDetailsSection({ errorDetails, eventId }: { errorDetails: string;
{eventId ? t('error.request.provideId') : t('common.error.request')}
</Description>
</Flex>
<Flex backgroundColor="$surface2" gap="$spacing8" p="$spacing24" borderRadius="$rounded24">
<Flex
alignSelf="stretch"
backgroundColor="$surface2"
gap="$spacing8"
p={isMobile ? '$spacing16' : '$spacing24'}
borderRadius="$rounded24"
>
<Flex row gap="$gap16" alignItems="center" justifyContent="space-between">
<ThemedText.SubHeader>
{eventId ? t('error.id', { eventId }) : t('common.error.details')}
......
import { useTranslation } from 'react-i18next'
import { ClickableTamaguiStyle } from 'theme/components'
import { ClickableTamaguiStyle } from 'theme/components/styles'
import { Flex, Text, TouchableArea } from 'ui/src'
import { AlertTriangleFilled } from 'ui/src/components/icons/AlertTriangleFilled'
import { RotateLeft } from 'ui/src/components/icons/RotateLeft'
......
import Column from 'components/deprecated/Column'
import Row from 'components/deprecated/Row'
import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains'
import styled from 'lib/styled-components'
import styledDep from 'lib/styled-components'
import { PropsWithChildren } from 'react'
import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { Button, ModalCloseIcon } from 'ui/src'
import { breakpoints } from 'ui/src/theme'
import { Button, Flex, ModalCloseIcon, Text, styled } from 'ui/src'
import { ExperimentRow } from 'uniswap/src/components/gating/GatingOverrides'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/features/chains/types'
import {
......@@ -15,68 +13,25 @@ import {
NetworkRequestsConfigKey,
QuickRouteChainsConfigKey,
} from 'uniswap/src/features/gating/configs'
import { Experiments } from 'uniswap/src/features/gating/experiments'
import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/gating/flags'
import { useFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/gating/hooks'
import { Statsig } from 'uniswap/src/features/gating/sdk/statsig'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
const Wrapper = styled(Column)`
padding: 20px 16px;
width: 100%;
gap: 8px;
`
const FlagsColumn = styled(Column)`
max-height: 600px;
padding-bottom: 8px;
overflow-y: auto;
@media screen and (max-width: ${breakpoints.md}px) {
max-height: unset;
}
`
const CenteredRow = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0px;
max-width: 100%;
gap: 4px;
`
const Header = styled(CenteredRow)`
font-weight: 535;
font-size: 16px;
border-bottom: 1px solid ${({ theme }) => theme.surface3};
justify-content: space-between;
`
const FlagName = styled.span`
font-size: 16px;
line-height: 20px;
color: ${({ theme }) => theme.neutral1};
`
const FlagGroupName = styled.span`
font-size: 20px;
line-height: 24px;
color: ${({ theme }) => theme.neutral1};
font-weight: 535;
`
const FlagDescription = styled.span`
font-size: 12px;
line-height: 16px;
color: ${({ theme }) => theme.neutral2};
display: flex;
align-items: center;
`
const CenteredRow = styled(Flex, {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
py: '$gap8',
maxWidth: '100%',
gap: '$gap4',
})
const FlagInfo = styled.div`
display: flex;
flex-direction: column;
padding-left: 8px;
flex-shrink: 1;
overflow: hidden;
`
const FlagInfo = styled(Flex, {
pl: '$padding8',
flexShrink: 1,
})
interface FeatureFlagProps {
label: string
......@@ -87,14 +42,14 @@ function FeatureFlagGroup({ name, children }: PropsWithChildren<{ name: string }
return (
<>
<CenteredRow key={name}>
<FlagGroupName>{name}</FlagGroupName>
<Text variant="body1">{name}</Text>
</CenteredRow>
{children}
</>
)
}
const FlagVariantSelection = styled.select`
const FlagVariantSelection = styledDep.select`
border-radius: 12px;
padding: 8px;
background: ${({ theme }) => theme.surface3};
......@@ -118,8 +73,10 @@ function FeatureFlagOption({ flag, label }: FeatureFlagProps) {
return (
<CenteredRow key={flag}>
<FlagInfo>
<FlagName>{name}</FlagName>
<FlagDescription>{label}</FlagDescription>
<Text variant="body2">{name}</Text>
<Text variant="body4" color="$neutral2">
{label}
</Text>
</FlagInfo>
<FlagVariantSelection
id={name}
......@@ -161,8 +118,10 @@ function DynamicConfigDropdown<
return (
<CenteredRow key={config}>
<FlagInfo>
<FlagName>{config}</FlagName>
<FlagDescription>{label}</FlagDescription>
<Text variant="body2">{config}</Text>
<Text variant="body4" color="$neutral2">
{label}
</Text>
</FlagInfo>
<select multiple onChange={handleSelectChange}>
{options.map((opt) => (
......@@ -181,10 +140,10 @@ export default function FeatureFlagModal() {
return (
<Modal name={ModalName.FeatureFlags} isModalOpen={open} onClose={closeModal} padding={0}>
<Wrapper>
<Header>
<Row width="100%" justify="space-between">
<span>Feature Flag Settings</span>
<Flex py="$gap20" px="$gap16" gap="$gap8">
<CenteredRow borderBottomColor="$surface3" borderBottomWidth={1}>
<Flex row grow alignItems="center" justifyContent="space-between">
<Text variant="subheading2">Feature Flag Settings</Text>
<Button
onPress={() => {
Statsig.removeGateOverride()
......@@ -196,10 +155,10 @@ export default function FeatureFlagModal() {
>
Clear Overrides
</Button>
</Row>
</Flex>
<ModalCloseIcon onClose={closeModal} />
</Header>
<FlagsColumn>
</CenteredRow>
<Flex maxHeight="600px" pb="$gap8" overflow="scroll" $md={{ maxHeight: 'unset' }}>
<FeatureFlagOption flag={FeatureFlags.EmbeddedWallet} label="Add internal embedded wallet functionality" />
<FeatureFlagOption flag={FeatureFlags.V4Swap} label="Enable v4 in the shared swap flow" />
<FeatureFlagOption flag={FeatureFlags.UniswapX} label="[Universal Swap Flow Only] Enable UniswapX" />
......@@ -224,7 +183,6 @@ export default function FeatureFlagModal() {
flag={FeatureFlags.Eip6936Enabled}
label="Enable EIP-6963: Multi Injected Provider Discovery"
/>
<FeatureFlagOption flag={FeatureFlags.SwapPresets} label="Enable swap presets" />
<FeatureFlagOption flag={FeatureFlags.LimitsFees} label="Enable Limits fees" />
<FeatureFlagOption flag={FeatureFlags.V4Data} label="Enable v4 data" />
<FeatureFlagOption flag={FeatureFlags.MigrateV3ToV4} label="Enable migrate flow from v3 -> v4" />
......@@ -271,11 +229,22 @@ export default function FeatureFlagModal() {
<FeatureFlagOption flag={FeatureFlags.TraceJsonRpc} label="Enables JSON-RPC tracing" />
<FeatureFlagOption flag={FeatureFlags.AATestWeb} label="A/A Test for Web" />
</FeatureFlagGroup>
</FlagsColumn>
<Button onPress={() => window.location.reload()} variant="default" emphasis="secondary" size="small">
<FeatureFlagGroup name="Experiments">
<Flex ml="$padding8">
<ExperimentRow experiment={Experiments.SwapPresets} />
</Flex>
</FeatureFlagGroup>
</Flex>
<Button
onPress={() => window.location.reload()}
variant="default"
emphasis="secondary"
size="small"
fill={false}
>
Reload
</Button>
</Wrapper>
</Flex>
</Modal>
)
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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