diff --git a/CHANGELOG.md b/CHANGELOG.md index c5f49df169a..f7c93bd9122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,16 @@ ## Unreleased (develop) -## 4.43.0 (staging) +## 4.43.2 (2025-02-15) + +- fixed: (Zano) Reject wrapped ETH addresses in address validation. + +## 4.43.1 (2025-02-13) + +- fixed: Missing 2-factor approve / deny scene on login +- fixed: Security check notification not reappearing after dismissal + +## 4.43.0 (2025-02-09) - added: `chooseCaip19Asset` EdgeProvider API for precise wallet selection using CAIP-19 identifiers - added: EdgeSpend feature for gift card purchase via Phaze @@ -16,7 +25,14 @@ - changed: ramps: Infinite buy support enabled - fixed: iOS simulator builds for XCode 26 -## 4.42.0 (2025-01-19) +## 4.42.1 (2026-01-28) + +- added: `chooseCaip19Asset` EdgeProvider API for precise wallet selection using CAIP-19 identifiers +- added: Pass OS and app version details to core context for v2/coreRollup endpoint +- added: EdgeSpend feature for gift card purchase via Phaze +- changed: Append chain names to token codes in RampCreateScene + +## 4.42.0 (2026-01-19) - added: Zcash buy/sell support with Banxa - changed: ramps: Infinite buy support according to new API diff --git a/eslint.config.mjs b/eslint.config.mjs index fa2df2718a3..d237ea0a170 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -239,10 +239,6 @@ export default [ 'src/components/rows/TxCryptoAmountRow.tsx', 'src/components/scenes/AssetSettingsScene.tsx', 'src/components/scenes/ChangeMiningFeeScene.tsx', - 'src/components/scenes/ChangePasswordScene.tsx', - 'src/components/scenes/ChangePinScene.tsx', - 'src/components/scenes/ChangeUsernameScene.tsx', - 'src/components/scenes/CoinRankingDetailsScene.tsx', 'src/components/scenes/ConfirmScene.tsx', 'src/components/scenes/CreateWalletAccountSelectScene.tsx', @@ -285,19 +281,17 @@ export default [ 'src/components/scenes/Loans/LoanDetailsScene.tsx', 'src/components/scenes/Loans/LoanManageScene.tsx', 'src/components/scenes/Loans/LoanStatusScene.tsx', - 'src/components/scenes/LoginScene.tsx', + 'src/components/scenes/ManageTokensScene.tsx', 'src/components/scenes/MigrateWalletCalculateFeeScene.tsx', 'src/components/scenes/MigrateWalletCompletionScene.tsx', 'src/components/scenes/NotificationCenterScene.tsx', 'src/components/scenes/NotificationScene.tsx', - 'src/components/scenes/OtpRepairScene.tsx', + 'src/components/scenes/OtpSettingsScene.tsx', - 'src/components/scenes/PasswordRecoveryScene.tsx', - 'src/components/scenes/PromotionSettingsScene.tsx', - 'src/components/scenes/SecurityAlertsScene.tsx', + 'src/components/scenes/PromotionSettingsScene.tsx', 'src/components/scenes/SpendingLimitsScene.tsx', 'src/components/scenes/Staking/EarnScene.tsx', @@ -312,7 +306,6 @@ export default [ 'src/components/scenes/TransactionDetailsScene.tsx', 'src/components/scenes/TransactionsExportScene.tsx', - 'src/components/scenes/UpgradeUsernameScreen.tsx', 'src/components/scenes/WalletRestoreScene.tsx', 'src/components/scenes/WcConnectionsScene.tsx', @@ -330,7 +323,7 @@ export default [ 'src/components/services/FioService.ts', 'src/components/services/LoanManagerService.ts', 'src/components/services/NetworkActivity.ts', - 'src/components/services/PasswordReminderService.ts', + 'src/components/services/PermissionsManager.tsx', 'src/components/services/Providers.tsx', diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 224265ebf73..5d419182a4d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -15,13 +15,13 @@ PODS: - disklet (0.5.2): - React - DoubleConversion (1.1.6) - - edge-core-js (2.41.0): + - edge-core-js (2.41.3): - React-Core - - edge-currency-accountbased (4.71.1): + - edge-currency-accountbased (4.71.4): - React-Core - - edge-currency-plugins (3.8.10): + - edge-currency-plugins (3.8.11): - React-Core - - edge-exchange-plugins (2.40.4): + - edge-exchange-plugins (2.40.5): - React-Core - edge-login-ui-rn (3.35.0): - React-Core @@ -1773,7 +1773,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-zano (0.2.5): + - react-native-zano (0.2.6): - OpenSSL-Universal - React-Core - react-native-zcash (0.10.3): @@ -3338,10 +3338,10 @@ SPEC CHECKSUMS: CNIOWindows: 3047f2d8165848a3936a0a755fee27c6b5ee479b disklet: 8a20bf8a568635b6e6bb8f93297dac13ee5cef98 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb - edge-core-js: c4d5806062e266cc90054901887784a29b60be31 - edge-currency-accountbased: a547885f96f0b0d96efe1bb7b885c7b0985b83dd - edge-currency-plugins: 6b3341707a6a5c74f837a012768dd2f6c55a691b - edge-exchange-plugins: f31912c54a50852bd02077b795aace0e0bee8365 + edge-core-js: 60ad7f9a59418f6bf90eab9bfc66b18d7877c7f1 + edge-currency-accountbased: 9ca740b8330e909dfcb0cda3e81870a8a3980895 + edge-currency-plugins: 002abe3c0d4fb6040046ec779b208c6aa83bee83 + edge-exchange-plugins: db3cfb3686abf0e31c5f2d0f9c0ce8c40b7cf9c9 edge-login-ui-rn: 74294715a31efa2f79a916a3f89bf47a99cec102 EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8 Expo: 43d9e0c3108cc3a1c2739743e9b51086144ee4b0 @@ -3419,7 +3419,7 @@ SPEC CHECKSUMS: react-native-safari-view: 07dc856a2663fef31eaca6beb79b111b8f6cf1f2 react-native-safe-area-context: 83e0ac3d023997de1c2e035af907cc4dc05f718c react-native-webview: 69c118d283fccfbc4fca0cd680e036ff3bf188fa - react-native-zano: 4af16a60e81819be92c425a373db3fa7908b10fb + react-native-zano: dd0967aaa0bbfe347c0ea5af15bc94f2d059fbf7 react-native-zcash: 34b665ed972547f6d09ad47aad706c5daa80bf09 React-NativeModulesApple: df8e5bc59e78ca3040ffbf41336889f3bd0fad68 React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c diff --git a/package.json b/package.json index f87a3cd1b98..e4fbce0a31e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "edge-react-gui", - "version": "4.43.0", + "version": "4.43.2", "private": true, "description": "Edge Wallet React GUI", "homepage": "https://edge.app", @@ -105,11 +105,11 @@ "deprecated-react-native-prop-types": "^5.0.0", "detect-bundler": "^1.1.0", "disklet": "^0.5.2", - "edge-core-js": "^2.41.0", - "edge-currency-accountbased": "^4.71.1", + "edge-core-js": "^2.41.3", + "edge-currency-accountbased": "^4.71.4", "edge-currency-monero": "^2.2.0", - "edge-currency-plugins": "^3.8.10", - "edge-exchange-plugins": "^2.40.4", + "edge-currency-plugins": "^3.8.11", + "edge-exchange-plugins": "^2.40.5", "edge-info-server": "^3.10.0", "edge-login-ui-rn": "^3.35.0", "ethers": "^5.7.2", @@ -171,7 +171,7 @@ "react-native-webview": "^13.15.0", "react-native-wheel-picker-android": "^2.0.6", "react-native-worklets": "^0.6.1", - "react-native-zano": "^0.2.5", + "react-native-zano": "^0.2.6", "react-native-zcash": "^0.10.3", "react-redux": "^8.1.1", "redux": "^4.2.1", diff --git a/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap index c9f8020455f..77835d74bc4 100644 --- a/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap @@ -5265,206 +5265,6 @@ exports[`SettingsScene should render SettingsScene 1`] = ` - - - - Light Mode - - - - - - - - - - - - { - return await writeDeviceSettings({ ...deviceSettings, isLightTheme }) +export const writeThemeMode = async (themeMode: ThemeMode) => { + return await writeDeviceSettings({ ...deviceSettings, themeMode }) } /** diff --git a/src/actions/LoginActions.tsx b/src/actions/LoginActions.tsx index a7baad4fac3..613743aeb01 100644 --- a/src/actions/LoginActions.tsx +++ b/src/actions/LoginActions.tsx @@ -25,7 +25,11 @@ import { lstrings } from '../locales/strings' import type { WalletCreateItem } from '../selectors/getCreateWalletList' import { config } from '../theme/appConfig' import type { Dispatch, GetState, ThunkAction } from '../types/reduxTypes' -import type { EdgeAppSceneProps, NavigationBase } from '../types/routerTypes' +import type { + EdgeAppSceneProps, + NavigationBase, + RootSceneProps +} from '../types/routerTypes' import { currencyCodesToEdgeAssets } from '../util/CurrencyInfoHelpers' import { logActivity } from '../util/logger' import { logEvent, trackError } from '../util/tracking' @@ -52,12 +56,11 @@ const PER_WALLET_TIMEOUT = 5000 const MIN_CREATE_WALLET_TIMEOUT = 20000 export function initializeAccount( - navigation: NavigationBase, + navigation: RootSceneProps<'login'>['navigation'], account: EdgeAccount ): ThunkAction> { return async (dispatch, getState) => { const { newAccount } = account - const rootNavigation = getRootNavigation(navigation) // Load all settings upfront so we can navigate immediately after LOGIN const [syncedSettings, localSettings] = await Promise.all([ @@ -80,7 +83,7 @@ export function initializeAccount( // Navigate immediately - all settings are now in Redux if (newAccount) { await navigateToNewAccountFlow( - rootNavigation, + navigation, account, syncedSettings, referralPromise, @@ -88,7 +91,7 @@ export function initializeAccount( getState ) } else { - navigateToExistingAccountHome(rootNavigation, referralPromise) + navigateToExistingAccountHome(navigation, referralPromise) } performance.mark('loginEnd', { detail: { isNewAccount: newAccount } }) @@ -143,6 +146,9 @@ export function initializeAccount( // Check for security alerts: if (hasSecurityAlerts(account)) { + // This is not the normal security alerts scene! + // Since we only have access to the root navigator, + // this scene exists as a peer of the main app: navigation.push('securityAlerts') hideSurvey = true } @@ -208,7 +214,7 @@ export function initializeAccount( * Navigate to wallet creation flow for new accounts. */ async function navigateToNewAccountFlow( - rootNavigation: NavigationBase, + navigation: RootSceneProps<'login'>['navigation'], account: EdgeAccount, syncedSettings: SyncedAccountSettings, referralPromise: Promise, @@ -279,7 +285,7 @@ async function navigateToNewAccountFlow( ) } - rootNavigation.replace('edgeApp', { + navigation.replace('edgeApp', { screen: 'edgeAppStack', params: { screen: 'createWalletSelectCryptoNewAccount', @@ -296,11 +302,11 @@ async function navigateToNewAccountFlow( * Navigate to home screen for existing accounts. */ function navigateToExistingAccountHome( - rootNavigation: NavigationBase, + navigation: RootSceneProps<'login'>['navigation'], referralPromise: Promise ): void { const { defaultScreen } = getDeviceSettings() - rootNavigation.replace('edgeApp', { + navigation.replace('edgeApp', { screen: 'edgeAppStack', params: { screen: 'edgeTabs', diff --git a/src/app.ts b/src/app.ts index b1e3950dde1..005681c2ce0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,7 +9,7 @@ import NetInfo from '@react-native-community/netinfo' import * as Sentry from '@sentry/react-native' import { Buffer } from 'buffer' import { asObject, asString } from 'cleaners' -import { InteractionManager, LogBox } from 'react-native' +import { Appearance, InteractionManager, LogBox } from 'react-native' import { getVersion } from 'react-native-device-info' import RNFS from 'react-native-fs' @@ -285,16 +285,37 @@ if (ENV.DEBUG_THEME) { }) } +// Theme initialization and system theme listener initDeviceSettings() .then(() => { - const { isLightTheme } = getDeviceSettings() - // Only change theme if light mode is enabled (dark is already the default) - if (isLightTheme) { + const { themeMode } = getDeviceSettings() + + // Apply theme based on mode setting at startup + let shouldUseLightTheme = false + if (themeMode === 'light') { + shouldUseLightTheme = true + } else if (themeMode === 'system') { + shouldUseLightTheme = Appearance.getColorScheme() !== 'dark' + } + // Only change theme if light mode is needed (dark is already the default) + if (shouldUseLightTheme) { // Defer until after React render cycle completes InteractionManager.runAfterInteractions(() => { changeTheme(config.lightTheme) }) } + + // Global listener for OS theme changes (active when themeMode is 'system') + Appearance.addChangeListener(({ colorScheme }) => { + const { themeMode: currentMode } = getDeviceSettings() + if (currentMode === 'system') { + InteractionManager.runAfterInteractions(() => { + const newTheme = + colorScheme === 'dark' ? config.darkTheme : config.lightTheme + changeTheme(newTheme) + }) + } + }) }) .catch(err => { console.log(err) diff --git a/src/components/Main.tsx b/src/components/Main.tsx index f2424a4d16d..82e1977a731 100644 --- a/src/components/Main.tsx +++ b/src/components/Main.tsx @@ -1280,6 +1280,11 @@ export const Main: React.FC = () => { return }} + + {navigation == null ? null : ( diff --git a/src/components/cards/ErrorCard.tsx b/src/components/cards/ErrorCard.tsx index bc716fa2a4c..c288d587450 100644 --- a/src/components/cards/ErrorCard.tsx +++ b/src/components/cards/ErrorCard.tsx @@ -1,3 +1,4 @@ +import Clipboard from '@react-native-clipboard/clipboard' import * as React from 'react' import { useHandler } from '../../hooks/useHandler' @@ -6,7 +7,7 @@ import { useSelector } from '../../types/reactRedux' import { normalizeError } from '../../util/normalizeError' import { trackError } from '../../util/tracking' import { RawTextModal } from '../modals/RawTextModal' -import { Airship } from '../services/AirshipInstance' +import { Airship, showToast } from '../services/AirshipInstance' import { AlertCardUi4 } from './AlertCard' /** @@ -36,17 +37,41 @@ export const ErrorCard: React.FC = props => { const { error } = props const isDevMode = useSelector(state => state.ui.settings.developerModeOn) - const [reportSent, setReportSent] = React.useState(false) + const [errorIdentifier, setErrorIdentifier] = React.useState< + { eventId: string } | { aggregateId: string } + >() + + // Reset error identifier when error changes + React.useEffect(() => { + setErrorIdentifier(undefined) + }, [error]) const handleReportError = useHandler((): void => { if (error != null) { - trackError(error, 'AlertDropdown_Report', { + const errorIdentifier = trackError(error, 'AlertDropdown_Report', { userReportedError: true }) - setReportSent(true) + setErrorIdentifier(errorIdentifier) + } + }) + + const handleCopyEventId = useHandler((): void => { + if (errorIdentifier != null) { + const id = + 'eventId' in errorIdentifier + ? errorIdentifier.eventId + : errorIdentifier.aggregateId + Clipboard.setString(id) + showToast(lstrings.fragment_error_report_id_copied) } }) + const handleShowError = useHandler(async (): Promise => { + await Airship.show(bridge => ( + + )) + }) + // Happy path if (error instanceof I18nError) { return ( @@ -54,33 +79,50 @@ export const ErrorCard: React.FC = props => { ) } + const reportSent = errorIdentifier != null + + const isAggregateError = + errorIdentifier != null && 'aggregateId' in errorIdentifier + const copyLabel = isAggregateError + ? lstrings.fragment_copy_aggregate_id + : lstrings.fragment_copy_event_id + const buttonProps = isDevMode || __DEV__ ? { - label: 'Show Error', - onPress: async () => { - await Airship.show(bridge => ( - - )) - } + label: lstrings.string_show_error, + onPress: handleShowError + } + : reportSent + ? { + label: copyLabel, + onPress: handleCopyEventId } : { - label: reportSent - ? lstrings.string_report_sent - : lstrings.string_report_error, - disabled: reportSent, + label: lstrings.string_report_error, onPress: handleReportError } + const idLabel = + errorIdentifier != null && 'eventId' in errorIdentifier + ? lstrings.fragment_event_id + : lstrings.fragment_aggregate_id + const idValue = + errorIdentifier != null + ? 'eventId' in errorIdentifier + ? errorIdentifier.eventId + : errorIdentifier.aggregateId + : '' + const bodyText = reportSent + ? `${lstrings.string_report_sent}\n\n${idLabel}: ${idValue}` + : lstrings.error_generic_message + // Unhappy path return ( ) diff --git a/src/components/cards/GiftCardDisplayCard.tsx b/src/components/cards/GiftCardDisplayCard.tsx index b69e9ae44d9..9ce751064ba 100644 --- a/src/components/cards/GiftCardDisplayCard.tsx +++ b/src/components/cards/GiftCardDisplayCard.tsx @@ -249,6 +249,7 @@ const getStyles = cacheStyles((theme: Theme) => ({ pendingText: { fontSize: theme.rem(0.875), fontFamily: theme.fontFaceMedium, - ...theme.embossedTextShadow + ...theme.embossedTextShadow, + color: theme.giftCardText } })) diff --git a/src/components/icons/IconBadge.tsx b/src/components/icons/IconBadge.tsx index 905a6a544a7..b95b79c1733 100644 --- a/src/components/icons/IconBadge.tsx +++ b/src/components/icons/IconBadge.tsx @@ -83,12 +83,14 @@ const getStyles = cacheStyles((theme: Theme) => { justifyContent: 'center' }, textIos: { + color: theme.badgeText, fontSize: theme.rem(0.5) - 1, fontFamily: theme.fontFaceBold, marginLeft: 2, marginRight: 1 }, textAndroid: { + color: theme.badgeText, fontSize: theme.rem(0.5) - 1, fontFamily: theme.fontFaceBold, marginTop: 1.5, @@ -98,7 +100,7 @@ const getStyles = cacheStyles((theme: Theme) => { width: theme.rem(0.15), height: theme.rem(0.15), borderRadius: theme.rem(0.15 / 2), - backgroundColor: theme.primaryText + backgroundColor: theme.badgeText } } }) diff --git a/src/components/scenes/ChangePasswordScene.tsx b/src/components/scenes/ChangePasswordScene.tsx index f21dc9100ab..208f77c3ebd 100644 --- a/src/components/scenes/ChangePasswordScene.tsx +++ b/src/components/scenes/ChangePasswordScene.tsx @@ -10,7 +10,7 @@ import { SceneWrapper } from '../common/SceneWrapper' interface Props extends EdgeAppSceneProps<'changePassword'> {} -export const ChangePasswordScene = (props: Props) => { +export const ChangePasswordScene: React.FC = props => { const { navigation } = props const account = useSelector(state => state.core.account) const context = useSelector(state => state.core.context) diff --git a/src/components/scenes/ChangePinScene.tsx b/src/components/scenes/ChangePinScene.tsx index fd342afc225..f89f476b579 100644 --- a/src/components/scenes/ChangePinScene.tsx +++ b/src/components/scenes/ChangePinScene.tsx @@ -10,16 +10,16 @@ import { SceneWrapper } from '../common/SceneWrapper' interface Props extends EdgeAppSceneProps<'changePin'> {} -export const ChangePinScene = (props: Props) => { +export const ChangePinScene: React.FC = props => { const { navigation } = props const account = useSelector(state => state.core.account) const context = useSelector(state => state.core.context) const dispatch = useDispatch() - const handleComplete = () => { + const handleComplete = useHandler(() => { logActivity(`PIN Changed: ${account.username}`) navigation.goBack() - } + }) const handleLogEvent = useHandler((event, values) => { dispatch(logEvent(event, values)) diff --git a/src/components/scenes/ChangeUsernameScene.tsx b/src/components/scenes/ChangeUsernameScene.tsx index 34f8fe0fdcd..8ace835384c 100644 --- a/src/components/scenes/ChangeUsernameScene.tsx +++ b/src/components/scenes/ChangeUsernameScene.tsx @@ -9,7 +9,7 @@ import { SceneWrapper } from '../common/SceneWrapper' interface Props extends EdgeAppSceneProps<'changeUsername'> {} -export const ChangeUsernameScene = (props: Props) => { +export const ChangeUsernameScene: React.FC = props => { const { navigation, route } = props const { password } = route.params const dispatch = useDispatch() diff --git a/src/components/scenes/GiftCardPurchaseScene.tsx b/src/components/scenes/GiftCardPurchaseScene.tsx index 1ab15e8b975..640bf3b9967 100644 --- a/src/components/scenes/GiftCardPurchaseScene.tsx +++ b/src/components/scenes/GiftCardPurchaseScene.tsx @@ -231,6 +231,9 @@ export const GiftCardPurchaseScene: React.FC = props => { const [amountText, setAmountText] = React.useState( hasFixedDenominations ? String(sortedDenominations[0]) : '' ) + const [amountInputError, setAmountInputError] = React.useState< + string | undefined + >() // Update selection when denominations become available (e.g., after brand fetch) React.useEffect(() => { @@ -247,6 +250,7 @@ export const GiftCardPurchaseScene: React.FC = props => { setMinimumWarning(null) setProductUnavailable(false) setError(null) + setAmountInputError(undefined) // Only allow numbers and decimal point const cleaned = text.replace(/[^0-9.]/g, '') @@ -262,12 +266,38 @@ export const GiftCardPurchaseScene: React.FC = props => { } }) + // Validate amount on blur for variable range cards + const handleAmountBlur = useHandler(() => { + if (!hasVariableRange || amountText === '') return + + const parsed = parseFloat(amountText) + if (isNaN(parsed)) return + + const fiatSymbol = getFiatSymbol(brand.currency) + if (parsed < minVal) { + setAmountInputError( + sprintf( + lstrings.card_amount_min_error_message_1s, + `${fiatSymbol}${minVal}` + ) + ) + } else if (parsed > maxVal) { + setAmountInputError( + sprintf( + lstrings.card_amount_max_error_message_1s, + `${fiatSymbol}${maxVal}` + ) + ) + } + }) + // Handle MAX button press const handleMaxPress = useHandler(() => { if (hasVariableRange) { setMinimumWarning(null) setProductUnavailable(false) setError(null) + setAmountInputError(undefined) setAmountText(String(maxVal)) setSelectedAmount(maxVal) } @@ -366,22 +396,23 @@ export const GiftCardPurchaseScene: React.FC = props => { const caip19 = tokenInfo.caip19 + // Get currency code for display (used in warnings, success, and error messages) + const currencyCode = + tokenId != null + ? account.currencyConfig[wallet.currencyInfo.pluginId]?.allTokens[ + tokenId + ]?.currencyCode ?? wallet.currencyInfo.currencyCode + : wallet.currencyInfo.currencyCode + // Check minimum amount for selected token if (selectedAmount < tokenInfo.minimumAmountInUSD) { - const currencyCode = - tokenId != null - ? account.currencyConfig[wallet.currencyInfo.pluginId]?.allTokens[ - tokenId - ]?.currencyCode ?? wallet.currencyInfo.currencyCode - : wallet.currencyInfo.currencyCode - setMinimumWarning({ header: sprintf( - lstrings.gift_card_minimum_warning_header, + lstrings.gift_card_minimum_warning_header_1s, currencyCode ), footer: sprintf( - lstrings.gift_card_minimum_warning_footer, + lstrings.gift_card_minimum_warning_footer_1s, formatMinimumInBrandCurrency(tokenInfo.minimumAmountInUSD) ) }) @@ -424,13 +455,6 @@ export const GiftCardPurchaseScene: React.FC = props => { // Convert quantity to native amount (crypto amount to pay) // The quantity is in the token's standard units (e.g., BTC, ETH) - const currencyCode = - tokenId != null - ? account.currencyConfig[wallet.currencyInfo.pluginId]?.allTokens[ - tokenId - ]?.currencyCode ?? wallet.currencyInfo.currencyCode - : wallet.currencyInfo.currencyCode - const multiplier = tokenId != null ? account.currencyConfig[wallet.currencyInfo.pluginId]?.allTokens[ @@ -568,23 +592,36 @@ export const GiftCardPurchaseScene: React.FC = props => { return } - // Check for minimum amount error from API + // Check for minimum amount error from API (with specific minimum) const minimumMatch = /Minimum cart cost should be above: ([\d.]+)/.exec( errorMessage ) + // Check for generic "order too small" error (no specific minimum) + const isGenericMinimumError = errorMessage.includes( + 'Order amount is too small' + ) + if (minimumMatch != null) { const minimumUSD = parseFloat(minimumMatch[1]) setMinimumWarning({ header: sprintf( - lstrings.gift_card_minimum_warning_header, - 'this cryptocurrency' + lstrings.gift_card_minimum_warning_header_1s, + currencyCode ), footer: sprintf( - lstrings.gift_card_minimum_warning_footer, + lstrings.gift_card_minimum_warning_footer_1s, formatMinimumInBrandCurrency(minimumUSD) ) }) + } else if (isGenericMinimumError) { + setMinimumWarning({ + header: sprintf( + lstrings.gift_card_minimum_warning_header_1s, + currencyCode + ), + footer: lstrings.gift_card_minimum_warning_generic + }) } else { // Show ErrorCard for other errors setError(err) @@ -725,10 +762,13 @@ export const GiftCardPurchaseScene: React.FC = props => { {} let firstRun = true -export function LoginScene(props: Props) { +export const LoginScene: React.FC = props => { const { navigation, route } = props const { experimentConfig, @@ -78,11 +77,9 @@ export function LoginScene(props: Props) { context .loginWithPIN(YOLO_USERNAME, YOLO_PIN) .then(async account => { - await dispatch( - initializeAccount(navigation as NavigationBase, account) - ) + await dispatch(initializeAccount(navigation, account)) }) - .catch(error => { + .catch((error: unknown) => { showError(error) }) } @@ -90,11 +87,9 @@ export function LoginScene(props: Props) { context .loginWithPassword(YOLO_USERNAME, YOLO_PASSWORD) .then(async account => { - await dispatch( - initializeAccount(navigation as NavigationBase, account) - ) + await dispatch(initializeAccount(navigation, account)) }) - .catch(error => { + .catch((error: unknown) => { showError(error) }) } @@ -111,11 +106,9 @@ export function LoginScene(props: Props) { useLoginId: true }) .then(async account => { - await dispatch( - initializeAccount(navigation as NavigationBase, account) - ) + await dispatch(initializeAccount(navigation, account)) }) - .catch(error => { + .catch((error: unknown) => { showError(error) }) } @@ -129,8 +122,8 @@ export function LoginScene(props: Props) { () => ({ callback() { Keyboard.dismiss() - showHelpModal(navigation as NavigationBase).catch(err => { - showDevError(err) + showHelpModal(navigation as NavigationBase).catch((error: unknown) => { + showDevError(error) }) }, text: lstrings.string_help @@ -145,16 +138,14 @@ export function LoginScene(props: Props) { : undefined const handleLogin = useHandler((account: EdgeAccount) => { - dispatch(initializeAccount(navigation as NavigationBase, account)).catch( - (error: unknown) => { - showError(error) - } - ) + dispatch(initializeAccount(navigation, account)).catch((error: unknown) => { + showError(error) + }) }) const handleSendLogs = useHandler(() => { - dispatch(showSendLogsModal()).catch(err => { - showError(err) + dispatch(showSendLogsModal()).catch((error: unknown) => { + showError(error) }) }) diff --git a/src/components/scenes/OtpRepairScene.tsx b/src/components/scenes/OtpRepairScene.tsx index 26adc01aa6b..5437b02a281 100644 --- a/src/components/scenes/OtpRepairScene.tsx +++ b/src/components/scenes/OtpRepairScene.tsx @@ -15,7 +15,7 @@ export interface OtpRepairParams { interface Props extends EdgeAppSceneProps<'otpRepair'> {} -export const OtpRepairScene = (props: Props) => { +export const OtpRepairScene: React.FC = props => { const { navigation, route } = props const { otpError } = route.params const account = useSelector(state => state.core.account) diff --git a/src/components/scenes/PasswordRecoveryScene.tsx b/src/components/scenes/PasswordRecoveryScene.tsx index e74761c939b..b250581249b 100644 --- a/src/components/scenes/PasswordRecoveryScene.tsx +++ b/src/components/scenes/PasswordRecoveryScene.tsx @@ -10,7 +10,7 @@ import { SceneWrapper } from '../common/SceneWrapper' interface Props extends EdgeAppSceneProps<'passwordRecovery'> {} -export const ChangeRecoveryScene = (props: Props) => { +export const ChangeRecoveryScene: React.FC = props => { const { navigation } = props const account = useSelector(state => state.core.account) const context = useSelector(state => state.core.context) diff --git a/src/components/scenes/SecurityAlertsScene.tsx b/src/components/scenes/SecurityAlertsScene.tsx index 8c28804ff23..c2ebbec6b48 100644 --- a/src/components/scenes/SecurityAlertsScene.tsx +++ b/src/components/scenes/SecurityAlertsScene.tsx @@ -3,13 +3,15 @@ import * as React from 'react' import { useHandler } from '../../hooks/useHandler' import { useDispatch, useSelector } from '../../types/reactRedux' -import type { EdgeAppSceneProps } from '../../types/routerTypes' +import type { RootSceneProps } from '../../types/routerTypes' import { logEvent } from '../../util/tracking' import { SceneWrapper } from '../common/SceneWrapper' -interface Props extends EdgeAppSceneProps<'securityAlerts'> {} +// Danger: This scene is mounted in two places, +// so this could also be `EdgeAppSceneProps<'securityAlerts'>`: +interface Props extends RootSceneProps<'securityAlerts'> {} -export const SecurityAlertsScene = (props: Props) => { +export const SecurityAlertsScene: React.FC = props => { const { navigation } = props const account = useSelector(state => state.core.account) const context = useSelector(state => state.core.context) diff --git a/src/components/scenes/SettingsScene.tsx b/src/components/scenes/SettingsScene.tsx index 0a5f073f494..b707dbffbe2 100644 --- a/src/components/scenes/SettingsScene.tsx +++ b/src/components/scenes/SettingsScene.tsx @@ -7,7 +7,7 @@ import { isTouchEnabled } from 'edge-login-ui-rn' import * as React from 'react' -import { Platform } from 'react-native' +import { Appearance, InteractionManager, Platform } from 'react-native' import { check } from 'react-native-permissions' import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome' import IonIcon from 'react-native-vector-icons/Ionicons' @@ -18,7 +18,7 @@ import { getDeviceSettings, writeDisableAnimations, writeForceLightAccountCreate, - writeIsLightTheme + writeThemeMode } from '../../actions/DeviceSettingsActions' import { setDeveloperModeOn, @@ -42,6 +42,7 @@ import { config } from '../../theme/appConfig' import { useState } from '../../types/reactHooks' import { useDispatch, useSelector } from '../../types/reactRedux' import type { EdgeAppSceneProps, NavigationBase } from '../../types/routerTypes' +import type { ThemeMode } from '../../types/types' import { secondsToDisplay } from '../../util/displayTime' import { getDisplayUsername, removeIsoPrefix } from '../../util/utils' import { ButtonsView } from '../buttons/ButtonsView' @@ -51,6 +52,7 @@ import { TextDropdown } from '../common/TextDropdown' import { SectionView } from '../layout/SectionView' import { AutoLogoutModal } from '../modals/AutoLogoutModal' import { ConfirmContinueModal } from '../modals/ConfirmContinueModal' +import { RadioListModal } from '../modals/RadioListModal' import { TextInputModal } from '../modals/TextInputModal' import { Airship, showDevError, showError } from '../services/AirshipInstance' import { requestContactsPermission } from '../services/PermissionsManager' @@ -129,8 +131,8 @@ export const SettingsScene: React.FC = props => { const [localContactPermissionOn, setLocalContactsPermissionOn] = React.useState(false) - const [isLightTheme, setIsLightTheme] = useState( - getDeviceSettings().isLightTheme + const [themeMode, setThemeMode] = useState( + getDeviceSettings().themeMode ) const [defaultLogLevel, setDefaultLogLevel] = React.useState< EdgeLogType | 'silent' @@ -239,11 +241,55 @@ export const SettingsScene: React.FC = props => { } }) - const handleToggleLightTheme = useHandler(async () => { - const newIsLightTheme = !isLightTheme - setIsLightTheme(newIsLightTheme) - changeTheme(newIsLightTheme ? config.lightTheme : config.darkTheme) - await writeIsLightTheme(newIsLightTheme) + // Apply the correct theme based on mode and system preference + // Deferred to avoid "Cannot update a component while rendering" errors + + // Note: System theme change listener is registered globally in app.ts + const applyTheme = React.useCallback((mode: ThemeMode) => { + InteractionManager.runAfterInteractions(() => { + if (mode === 'system') { + const systemIsDark = Appearance.getColorScheme() === 'dark' + changeTheme(systemIsDark ? config.darkTheme : config.lightTheme) + } else { + changeTheme(mode === 'light' ? config.lightTheme : config.darkTheme) + } + }) + }, []) + + const handleSelectTheme = useHandler(async () => { + const themeModeLabels: Record = { + light: lstrings.settings_theme_light, + dark: lstrings.settings_theme_dark, + system: lstrings.settings_theme_system + } + + const items = (['light', 'dark', 'system'] as ThemeMode[]).map(mode => ({ + icon: null, + name: themeModeLabels[mode], + value: mode + })) + + const result = await Airship.show(bridge => ( + + )) + + if (result != null) { + // Find the mode by label + const newMode = Object.entries(themeModeLabels).find( + ([, label]) => label === result + )?.[0] as ThemeMode | undefined + + if (newMode != null && newMode !== themeMode) { + setThemeMode(newMode) + applyTheme(newMode) + await writeThemeMode(newMode) + } + } }) const handleSetAutoLogoutTime = useHandler(async () => { @@ -646,12 +692,6 @@ export const SettingsScene: React.FC = props => { value={defaultLogLevel === 'info'} onPress={handleToggleVerboseLogging} /> - {ENV.ALLOW_DEVELOPER_MODE && ( @@ -671,6 +711,19 @@ export const SettingsScene: React.FC = props => { onPress={handleToggleForceLightAccountCreate} /> )} + {developerModeOn && ( + + )} )} diff --git a/src/components/scenes/UpgradeUsernameScreen.tsx b/src/components/scenes/UpgradeUsernameScreen.tsx index d37e321eb6d..0612d18149d 100644 --- a/src/components/scenes/UpgradeUsernameScreen.tsx +++ b/src/components/scenes/UpgradeUsernameScreen.tsx @@ -11,7 +11,7 @@ import { SceneWrapper } from '../common/SceneWrapper' interface Props extends EdgeAppSceneProps<'upgradeUsername'> {} -export const UpgradeUsernameScene = (props: Props) => { +export const UpgradeUsernameScene: React.FC = props => { const { navigation } = props const account = useSelector(state => state.core.account) const context = useSelector(state => state.core.context) diff --git a/src/components/services/PasswordReminderService.ts b/src/components/services/PasswordReminderService.ts index c6d501fe0de..db913df1b2e 100644 --- a/src/components/services/PasswordReminderService.ts +++ b/src/components/services/PasswordReminderService.ts @@ -1,51 +1,36 @@ import * as React from 'react' import { setPasswordReminder } from '../../actions/LocalSettingsActions' -import { connect } from '../../types/reactRedux' -import type { PasswordReminder } from '../../types/types' +import { useAsyncEffect } from '../../hooks/useAsyncEffect' +import { + initialState, + type PasswordReminderState +} from '../../reducers/PasswordReminderReducer' +import { useDispatch, useSelector } from '../../types/reactRedux' import { matchJson } from '../../util/matchJson' -import { showError } from './AirshipInstance' -interface StateProps { - settingsLoaded: boolean | null - passwordReminder: PasswordReminder -} -interface DispatchProps { - setPasswordReminder: (passwordReminder: PasswordReminder) => Promise -} -type Props = StateProps & DispatchProps +interface Props {} -class PasswordReminderComponent extends React.PureComponent { - componentDidUpdate(prevProps: Props) { - if ( - this.props.settingsLoaded && - !matchJson(prevProps.passwordReminder, this.props.passwordReminder) - ) { - this.props - .setPasswordReminder(this.props.passwordReminder) - .catch((error: unknown) => { - showError(error) - }) - } - } +export const PasswordReminderService: React.FC = props => { + const settingsLoaded = + useSelector(state => state.ui.settings.settingsLoaded) ?? false + const passwordReminder = useSelector(state => state.ui.passwordReminder) + const lastPasswordReminder = React.useRef(initialState) + const dispatch = useDispatch() - render() { - return null - } -} + useAsyncEffect( + async () => { + if ( + settingsLoaded && + !matchJson(passwordReminder, lastPasswordReminder.current) + ) { + lastPasswordReminder.current = passwordReminder + await dispatch(setPasswordReminder(passwordReminder)) + } + }, + [settingsLoaded, passwordReminder], + 'PasswordReminderService' + ) -export const PasswordReminderService = connect< - StateProps, - DispatchProps, - unknown ->( - state => ({ - settingsLoaded: state.ui.settings.settingsLoaded, - passwordReminder: state.ui.passwordReminder - }), - dispatch => ({ - async setPasswordReminder(passwordReminder: PasswordReminder) { - await dispatch(setPasswordReminder(passwordReminder)) - } - }) -)(PasswordReminderComponent) + return null +} diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index b207db9a902..d74d02563a8 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -220,6 +220,11 @@ const strings = { fragment_request_address_uri_copied: 'Request address URI copied to clipboard', fragment_copied: 'Successfully copied to clipboard', + fragment_aggregate_id: 'Aggregate ID', + fragment_copy_aggregate_id: 'Copy Aggregate ID', + fragment_copy_event_id: 'Copy Event ID', + fragment_event_id: 'Event ID', + fragment_error_report_id_copied: 'Error report ID copied', request_minimum_notification_title: 'Minimum Balance Required', request_xrp_minimum_notification_body_1xrp: 'Ripple (XRP) wallets require a 1 XRP minimum balance. You must deposit at least 1 XRP to this address before this wallet will show a balance or transactions. 1 XRP will be unspendable for the lifetime of this wallet address.', @@ -507,8 +512,10 @@ const strings = { settings_button_change_username: 'Change Username', settings_developer_mode: 'Developer Mode', settings_verbose_logging: 'Verbose Logging', - settings_dark_theme: 'Dark Theme', - settings_light_mode: 'Light Mode', + settings_theme: 'Theme', + settings_theme_light: 'Light', + settings_theme_dark: 'Dark', + settings_theme_system: 'System', button_disable_animations: 'Disable Animations', settings_button_contacts_access_permission: 'Contacts Access', settings_button_lock_settings: 'Tap to Lock Account Settings', @@ -1405,7 +1412,8 @@ const strings = { string_max_cap: 'MAX', string_warning: 'Warning', // Generic string. Same with wc_smartcontract_warning_title string_report_error: 'Report Error', - string_report_sent: 'Report sent.', + string_report_sent: 'The report has been sent successfully.', + string_show_error: 'Show Error', string_best_rate_badge_text: 'Best\nRate', step_prefix_s: 'Step %s:', @@ -1918,10 +1926,12 @@ const strings = { gift_card_network_error: 'Unable to load gift cards. Please check your network connection.', gift_card_minimum_warning_title: 'Below Minimum', - gift_card_minimum_warning_header: - 'The selected amount is below the minimum for %s.', - gift_card_minimum_warning_footer: - 'Please select a different payment method or increase your purchase amount to at least %s.', + gift_card_minimum_warning_header_1s: + 'The selected amount is below the minimum for %1$s.', + gift_card_minimum_warning_footer_1s: + 'Please select a different payment method or increase your purchase amount to at least %1$s.', + gift_card_minimum_warning_generic: + 'The selected amount is too small for this cryptocurrency. Please select a different payment method or increase your purchase amount.', gift_card_redeemed_cards: 'Redeemed Cards', gift_card_unmark_as_redeemed: 'Unmark as Redeemed', gift_card_active_cards: 'Active Cards', @@ -2376,6 +2386,8 @@ const strings = { buy_new_card_button: `Buy New Card`, card_amount_max_error_message_s: `Maximum card purchase amount is $%s`, card_amount_min_error_message_s: `Minimum card purchase amount is $%s`, + card_amount_max_error_message_1s: 'Maximum card purchase amount is %1$s', + card_amount_min_error_message_1s: 'Minimum card purchase amount is %1$s', delete_card_confirmation_title: 'Delete Card?', getting_payment_invoice_message: 'Getting payment invoice', learn_more_button: `Learn More`, diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index c21266ea0ce..9f1630df5b8 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -134,6 +134,11 @@ "fragment_request_subtitle": "Receive", "fragment_request_address_uri_copied": "Request address URI copied to clipboard", "fragment_copied": "Successfully copied to clipboard", + "fragment_aggregate_id": "Aggregate ID", + "fragment_copy_aggregate_id": "Copy Aggregate ID", + "fragment_copy_event_id": "Copy Event ID", + "fragment_event_id": "Event ID", + "fragment_error_report_id_copied": "Error report ID copied", "request_minimum_notification_title": "Minimum Balance Required", "request_xrp_minimum_notification_body_1xrp": "Ripple (XRP) wallets require a 1 XRP minimum balance. You must deposit at least 1 XRP to this address before this wallet will show a balance or transactions. 1 XRP will be unspendable for the lifetime of this wallet address.", "request_xrp_minimum_notification_alert_body_1xrp": "This wallet will always require a 1 XRP minimum", @@ -361,8 +366,10 @@ "settings_button_change_username": "Change Username", "settings_developer_mode": "Developer Mode", "settings_verbose_logging": "Verbose Logging", - "settings_dark_theme": "Dark Theme", - "settings_light_mode": "Light Mode", + "settings_theme": "Theme", + "settings_theme_light": "Light", + "settings_theme_dark": "Dark", + "settings_theme_system": "System", "button_disable_animations": "Disable Animations", "settings_button_contacts_access_permission": "Contacts Access", "settings_button_lock_settings": "Tap to Lock Account Settings", @@ -1109,7 +1116,8 @@ "string_max_cap": "MAX", "string_warning": "Warning", "string_report_error": "Report Error", - "string_report_sent": "Report sent.", + "string_report_sent": "The report has been sent successfully.", + "string_show_error": "Show Error", "string_best_rate_badge_text": "Best\nRate", "step_prefix_s": "Step %s:", "scan_as_in_scan_barcode": "Scan", @@ -1491,8 +1499,9 @@ "gift_card_more_options": "Browse more gift cards", "gift_card_network_error": "Unable to load gift cards. Please check your network connection.", "gift_card_minimum_warning_title": "Below Minimum", - "gift_card_minimum_warning_header": "The selected amount is below the minimum for %s.", - "gift_card_minimum_warning_footer": "Please select a different payment method or increase your purchase amount to at least %s.", + "gift_card_minimum_warning_header_1s": "The selected amount is below the minimum for %1$s.", + "gift_card_minimum_warning_footer_1s": "Please select a different payment method or increase your purchase amount to at least %1$s.", + "gift_card_minimum_warning_generic": "The selected amount is too small for this cryptocurrency. Please select a different payment method or increase your purchase amount.", "gift_card_redeemed_cards": "Redeemed Cards", "gift_card_unmark_as_redeemed": "Unmark as Redeemed", "gift_card_active_cards": "Active Cards", @@ -1856,6 +1865,8 @@ "buy_new_card_button": "Buy New Card", "card_amount_max_error_message_s": "Maximum card purchase amount is $%s", "card_amount_min_error_message_s": "Minimum card purchase amount is $%s", + "card_amount_max_error_message_1s": "Maximum card purchase amount is %1$s", + "card_amount_min_error_message_1s": "Minimum card purchase amount is %1$s", "delete_card_confirmation_title": "Delete Card?", "getting_payment_invoice_message": "Getting payment invoice", "learn_more_button": "Learn More", diff --git a/src/plugins/gift-cards/phazeApi.ts b/src/plugins/gift-cards/phazeApi.ts index 7871ade69e2..0ef8fa56145 100644 --- a/src/plugins/gift-cards/phazeApi.ts +++ b/src/plugins/gift-cards/phazeApi.ts @@ -21,7 +21,7 @@ import { // --------------------------------------------------------------------------- /** Minimum card value in USD. Cards below this are filtered out. */ -export const MINIMUM_CARD_VALUE_USD = 5 +export const MINIMUM_CARD_VALUE_USD = 20 // --------------------------------------------------------------------------- // Field definitions for different use cases diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts index 37c76da714a..b7a32e0026c 100644 --- a/src/plugins/ramps/rampConstraints.ts +++ b/src/plugins/ramps/rampConstraints.ts @@ -107,4 +107,13 @@ export function* constraintGenerator( if (params.rampPluginId === 'banxa') { yield params.paymentType !== 'ach' } + + // + // Infinite + // + + if (params.rampPluginId === 'infinite') { + // Disable Infinite completely + yield false + } } diff --git a/src/theme/variables/edgeDark.ts b/src/theme/variables/edgeDark.ts index d1818009e41..5b06f9dd8c3 100644 --- a/src/theme/variables/edgeDark.ts +++ b/src/theme/variables/edgeDark.ts @@ -497,6 +497,7 @@ export const edgeDark: Theme = { // UI 4.0: badgeDot: palette.accentRed, + badgeText: palette.white, // Shadows iconShadow: { diff --git a/src/theme/variables/edgeLight.ts b/src/theme/variables/edgeLight.ts index b7d7750dae7..e732cd2afb5 100644 --- a/src/theme/variables/edgeLight.ts +++ b/src/theme/variables/edgeLight.ts @@ -140,7 +140,7 @@ export const edgeLight: Theme = { // Icons icon: palette.darkestNavy, iconTappable: palette.darkMint, - iconDeactivated: palette.whiteOp75, + iconDeactivated: palette.darkestNavy, dangerIcon: palette.accentRed, warningIcon: palette.accentOrange, iconLoadingOverlay: palette.whiteOp75, @@ -362,13 +362,13 @@ export const edgeLight: Theme = { toggleButtonOff: palette.darkGray, // Confirmation slider - confirmationSlider: palette.darkGray, + confirmationSlider: palette.lightGray, confirmationSliderCompleted: palette.darkGreen, confirmationSliderText: palette.darkestNavy, - confirmationSliderArrow: palette.backgroundWhite, + confirmationSliderArrow: palette.darkestNavy, confirmationSliderThumb: palette.darkMint, - confirmationSliderTextDeactivated: palette.darkGray, - confirmationThumbDeactivated: palette.darkGray, + confirmationSliderTextDeactivated: palette.gray, + confirmationThumbDeactivated: palette.gray, confirmationSliderWidth: deviceWidth >= 340 ? 295 : deviceWidth - 45, confirmationSliderThumbWidth: 55, @@ -500,6 +500,7 @@ export const edgeLight: Theme = { // UI 4.0: badgeDot: palette.accentRed, + badgeText: palette.white, // Shadows iconShadow: { diff --git a/src/theme/variables/testDark.ts b/src/theme/variables/testDark.ts index 1190819d885..f8734f1ea07 100644 --- a/src/theme/variables/testDark.ts +++ b/src/theme/variables/testDark.ts @@ -38,32 +38,36 @@ const palette = { darkMint: '#089e73', edgeMint: '#00f1a2', - darkAqua: '#1b2f3b', - navyAqua: '#121d25', - navyAquaMiddle: '#11191f', // For vertical gradient - navyAquaDarker: '#0E141A', // For vertical gradient - blueGray: '#A4C7DF', - gray: '#87939E', - lightGray: '#D9E3ED', - mutedBlue: '#2F5E89', + + gray: '#888888', + darkGray: '#494949', + darkGrayOp30: 'hsla(0, 0%, 53%, 0.3)', + lightGray: '#e2e2e2', + + blueGray: '#D9E3ED', + blueGrayOp75: 'rgba(217, 227, 237, .75)', + blueGrayOp80: 'rgba(135, 147, 158, .8)', + accentGreen: '#77C513', accentRed: '#E85466', accentBlue: '#0073D9', accentOrange: '#F1AA19', darkBlueLightened: '#2B333A', - blackOp25: 'rgba(0, 0, 0, .25)', + blackOp10: 'rgba(0, 0, 0, .1)', + blackOp35: 'rgba(0, 0, 0, .25)', blackOp50: 'rgba(0, 0, 0, .5)', + blackOp70: 'rgba(0, 0, 0, .7)', blackOp80: 'rgba(0, 0, 0, .8)', whiteOp05: 'rgba(255, 255, 255, .05)', whiteOp10: 'rgba(255, 255, 255, .1)', whiteOp25: 'rgba(255, 255, 255, .25)', + whiteOp37: 'rgba(255, 255, 255, .37)', + whiteOp50: 'rgba(255, 255, 255, .5)', whiteOp75: 'rgba(255, 255, 255, .75)', - grayOp80: 'rgba(135, 147, 158, .8)', accentOrangeOp30: 'rgba(241, 170, 25, .3)', - lightGrayOp75: 'rgba(217, 227, 237, .75)', transparent: 'rgba(255, 255, 255, 0)', // Fonts @@ -78,7 +82,7 @@ const palette = { skyBlue: '#3dd9f4', blackOp65: 'rgba(0, 0, 0, .65)', redOp60: 'rgba(232, 84, 102, .6)', - grayOp70: 'rgba(135, 147, 158, .7)', + blueGrayOp70: 'rgba(135, 147, 158, .7)', greenOp60: 'rgba(119, 197, 19, .6)', lightGreen: '#75C649', greenOp50: 'rgba(51, 183, 36, 0.5)', @@ -141,12 +145,12 @@ export const testDark: Theme = { loadingIcon: palette.edgeMint, // Background - backgroundGradientColors: [palette.black, palette.black], + backgroundGradientColors: [palette.backgroundBlack, palette.backgroundBlack], backgroundGradientStart: { x: 0, y: 0 }, - backgroundGradientEnd: { x: 1, y: 0 }, + backgroundGradientEnd: { x: 1, y: 1 }, backgroundDots: { blurRadius: scale(80), - dotOpacity: 0.25, + dotOpacity: 0.1, dots: [ { // Top-left: @@ -172,10 +176,13 @@ export const testDark: Theme = { ], assetOverrideDots: [undefined, { accentColor: 'iconAccentColor' }, null] }, - assetBackgroundGradientColors: [palette.darkAqua, palette.black], + assetBackgroundGradientColors: [ + palette.backgroundBlack, + palette.backgroundBlack + ], assetBackgroundGradientStart: { x: 0, y: 0 }, assetBackgroundGradientEnd: { x: 0, y: 1 }, - assetBackgroundColorScale: 0.3, + assetBackgroundColorScale: 0.1, // Camera Overlay cameraOverlayColor: palette.black, @@ -183,18 +190,18 @@ export const testDark: Theme = { cameraOverlayOpEnd: 0.3, // Modal - modal: palette.navyAqua, + modal: palette.backgroundBlack, modalCloseIcon: palette.edgeMint, modalBorderColor: palette.transparent, modalBorderWidth: 0, modalBorderRadiusRem: 1, - modalBackground: palette.whiteOp10, + modalBackground: palette.whiteOp37, modalSceneOverlayColor: palette.black, - modalDragbarColor: palette.gray, + modalDragbarColor: palette.darkGrayOp30, modalLikeBackground: '#333232', - sideMenuBorderColor: palette.navyAqua, + sideMenuBorderColor: palette.backgroundBlack, sideMenuBorderWidth: 0, sideMenuFont: palette.QuicksandMedium, @@ -204,18 +211,17 @@ export const testDark: Theme = { tileBackgroundMuted: palette.transparent, // Section Lists - // listSectionHeaderBackgroundGradientColors: [palette.navyAquaMiddle], // For vertical gradient listSectionHeaderBackgroundGradientColors: [`#000000aa`, `#00000000`], // Commenting out will remove background gradient: - listSectionHeaderBackgroundGradientStart: { x: 0, y: 0 }, - listSectionHeaderBackgroundGradientEnd: { x: 1, y: 0 }, + listSectionHeaderBackgroundGradientStart: null, + listSectionHeaderBackgroundGradientEnd: null, // Text primaryText: palette.white, secondaryText: palette.skyBlue, warningText: palette.accentOrange, positiveText: palette.accentGreen, - negativeText: palette.gray, + negativeText: palette.blueGray, negativeDeltaText: palette.accentRed, dangerText: palette.accentRed, textLink: palette.edgeMint, @@ -335,7 +341,7 @@ export const testDark: Theme = { textShadowRadius: 3 }, - tabBarBackground: [palette.blackOp25, palette.blackOp50], + tabBarBackground: [palette.transparent, palette.transparent], tabBarBackgroundStart: { x: 0, y: 0.5 }, tabBarBackgroundEnd: { x: 0, y: 1 }, tabBarTopOutlineColors: [`${palette.white}22`, `${palette.white}22`], @@ -353,10 +359,10 @@ export const testDark: Theme = { toggleButtonOff: palette.gray, // Confirmation slider - confirmationSlider: palette.darkBlueLightened, + confirmationSlider: palette.darkGray, confirmationSliderCompleted: palette.darkGreen, confirmationSliderText: palette.white, - confirmationSliderArrow: palette.darkAqua, + confirmationSliderArrow: palette.backgroundBlack, confirmationSliderThumb: palette.edgeMint, confirmationSliderTextDeactivated: palette.gray, confirmationThumbDeactivated: palette.gray, @@ -365,7 +371,7 @@ export const testDark: Theme = { // Lines lineDivider: palette.whiteOp10, - titleLineDivider: palette.blueGray, + titleLineDivider: palette.whiteOp10, thinLineWidth: 1, mediumLineWidth: 2, thickLineWidth: 3, @@ -384,7 +390,7 @@ export const testDark: Theme = { dateModalTextLight: palette.accentBlue, dateModalTextDark: palette.white, dateModalBackgroundLight: palette.white, - dateModalBackgroundDark: palette.darkAqua, + dateModalBackgroundDark: palette.backgroundBlack, // Wallet Icon Progress walletProgressIconFill: palette.edgeMint, @@ -438,20 +444,20 @@ export const testDark: Theme = { textInputTextColor: palette.white, textInputTextColorDisabled: palette.gray, textInputTextColorFocused: palette.white, - textInputBackgroundColor: palette.darkAqua, - textInputBackgroundColorDisabled: palette.darkAqua, - textInputBackgroundColorFocused: palette.darkAqua, + textInputBackgroundColor: palette.graySecondary, + textInputBackgroundColorDisabled: palette.transparent, + textInputBackgroundColorFocused: palette.graySecondary, textInputBorderColor: `${palette.edgeMint}00`, - textInputBorderColorDisabled: palette.gray, + textInputBorderColorDisabled: palette.graySecondary, textInputBorderColorFocused: palette.edgeMint, textInputBorderRadius: 100, textInputBorderWidth: 1, - textInputIconColor: palette.gray, - textInputIconColorDisabled: palette.gray, + textInputIconColor: palette.whiteOp50, + textInputIconColorDisabled: palette.whiteOp50, textInputIconColorFocused: palette.edgeMint, - textInputPlaceholderColor: palette.gray, - textInputPlaceholderColorDisabled: palette.gray, - textInputPlaceholderColorFocused: palette.edgeMint, + textInputPlaceholderColor: palette.whiteOp50, + textInputPlaceholderColorDisabled: palette.whiteOp50, + textInputPlaceholderColorFocused: palette.whiteOp50, textInputSelectionColor: palette.whiteOp25, // Animation @@ -491,6 +497,7 @@ export const testDark: Theme = { // UI 4.0: badgeDot: palette.accentRed, + badgeText: palette.white, // Shadows iconShadow: { @@ -501,6 +508,7 @@ export const testDark: Theme = { }, shadowOpacity: 0.6, shadowRadius: 4, + // Disable Android shadow elevation: 0 }, @@ -586,10 +594,10 @@ export const testDark: Theme = { txDirBgReceive: palette.greenOp60, txDirBgSend: palette.redOp60, - txDirBgSwap: palette.grayOp70, + txDirBgSwap: palette.blueGrayOp70, txDirFgReceive: palette.lightGreen, txDirFgSend: palette.lightRed, - txDirFgSwap: palette.lightGray, + txDirFgSwap: palette.blueGray, giftCardOverlayGradient: { colors: [ diff --git a/src/theme/variables/testLight.ts b/src/theme/variables/testLight.ts index 8f554c2de5a..1569ed6e91a 100644 --- a/src/theme/variables/testLight.ts +++ b/src/theme/variables/testLight.ts @@ -140,7 +140,7 @@ export const testLight: Theme = { // Icons icon: palette.darkestNavy, iconTappable: palette.darkMint, - iconDeactivated: palette.whiteOp75, + iconDeactivated: palette.darkestNavy, dangerIcon: palette.accentRed, warningIcon: palette.accentOrange, iconLoadingOverlay: palette.whiteOp75, @@ -362,13 +362,13 @@ export const testLight: Theme = { toggleButtonOff: palette.darkGray, // Confirmation slider - confirmationSlider: palette.darkGray, + confirmationSlider: palette.lightGray, confirmationSliderCompleted: palette.darkGreen, confirmationSliderText: palette.darkestNavy, - confirmationSliderArrow: palette.backgroundWhite, + confirmationSliderArrow: palette.darkestNavy, confirmationSliderThumb: palette.darkMint, - confirmationSliderTextDeactivated: palette.darkGray, - confirmationThumbDeactivated: palette.darkGray, + confirmationSliderTextDeactivated: palette.gray, + confirmationThumbDeactivated: palette.gray, confirmationSliderWidth: deviceWidth >= 340 ? 295 : deviceWidth - 45, confirmationSliderThumbWidth: 55, @@ -500,6 +500,7 @@ export const testLight: Theme = { // UI 4.0: badgeDot: palette.accentRed, + badgeText: palette.white, // Shadows iconShadow: { diff --git a/src/types/Theme.ts b/src/types/Theme.ts index 2d46f466638..310663e0a24 100644 --- a/src/types/Theme.ts +++ b/src/types/Theme.ts @@ -453,6 +453,7 @@ export interface Theme { // UI 4.0: badgeDot: string + badgeText: string // Shadows iconShadow: ThemeShadowParams diff --git a/src/types/routerTypes.tsx b/src/types/routerTypes.tsx index 463f23d5754..e3ec4b65425 100644 --- a/src/types/routerTypes.tsx +++ b/src/types/routerTypes.tsx @@ -270,6 +270,7 @@ export type RootParamList = {} & { edgeApp: NavigationCore.NavigatorScreenParams | undefined gettingStarted: GettingStartedParams login: LoginParams + securityAlerts: undefined } // Upgraded types to comply with the navigation upgrade requirements @@ -322,6 +323,7 @@ export type WalletsTabSceneProps = // defined above. // ------------------------------------------------------------------------- +/** @deprecated Use one of the XyzParamList types instead */ export type AppParamList = RootParamList & DrawerParamList & EdgeAppStackParamList & @@ -330,31 +332,32 @@ export type AppParamList = RootParamList & BuySellTabParamList & WalletsTabParamList -export type RouteSceneKey = keyof AppParamList - /** * The of the `navigation` prop passed to each scene, * but without any scene-specific stuff. + * @deprecated Use one of the `XyzSceneProps<"routeName">['navigation']` types. */ export type NavigationBase = NavigationCore.NavigationProp & StackActionHelpers /** * The `navigation` prop passed to each scene. + * @deprecated Use one of the `XyzSceneProps<"routeName">['navigation']` types. */ - export type NavigationProp = NavigationCore.NavigationProp & StackActionHelpers /** * The `route` prop passed to each scene. + * @deprecated Use one of the `XyzSceneProps<"routeName">['route']` types. */ export type RouteProp = NavigationCore.RouteProp /** * All the props passed to each scene. + * @deprecated Use one of the `XyzSceneProps<"routeName">` types. */ export interface EdgeSceneProps { navigation: NavigationProp diff --git a/src/types/types.ts b/src/types/types.ts index 90d5ee93349..2f5cf24f931 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -221,13 +221,14 @@ const asLocalAccountSettingsInner = asObject({ }) export const asDefaultScreen = asValue('home', 'assets') +export const asThemeMode = asValue('light', 'dark', 'system') const asDeviceSettingsInner = asObject({ defaultScreen: asMaybe(asDefaultScreen, 'home'), developerPluginUri: asMaybe(asString), disableAnimations: asMaybe(asBoolean, false), forceLightAccountCreate: asMaybe(asBoolean, false), - isLightTheme: asMaybe(asBoolean, false), + themeMode: asMaybe(asThemeMode, 'dark'), isSurveyDiscoverShown: asMaybe(asBoolean, false) }) @@ -239,6 +240,7 @@ export const asDeviceSettings = asMaybe(asDeviceSettingsInner, () => ) export type DefaultScreen = ReturnType +export type ThemeMode = ReturnType export type PasswordReminder = ReturnType export type LocalAccountSettings = ReturnType export type DeviceSettings = ReturnType diff --git a/src/util/tracking.ts b/src/util/tracking.ts index df6272d60e0..b0aa4a64453 100644 --- a/src/util/tracking.ts +++ b/src/util/tracking.ts @@ -190,7 +190,7 @@ export function trackError( error: unknown, nameTag?: string, metadata?: Record -): void { +): { eventId: string } | { aggregateId: string } { const err = normalizeError(error) if (err instanceof AggregateErrorFix) { @@ -202,10 +202,10 @@ export function trackError( trackError(e, nameTag, metadata) }) }) - return + return { aggregateId } } - captureException(err, scope => { + const eventId = captureException(err, scope => { scope.setTag('event.name', nameTag) if (metadata != null) { const context: Record = {} @@ -214,6 +214,7 @@ export function trackError( } return scope }) + return { eventId } } /** diff --git a/yarn.lock b/yarn.lock index bb13e3ca75b..8a68ec96ff2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1063,7 +1063,20 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0", "@babel/traverse@^7.7.0": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" + integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.0" + debug "^4.3.1" + +"@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0", "@babel/traverse@^7.7.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== @@ -2128,9 +2141,9 @@ randombytes "^2.1.0" text-encoding "0.7.0" -"@fioprotocol/fiosdk@https://github.com/jon-edge/fiosdk_typescript.git#92a0fb895b2ce57e5955cd30cb4b7fa2bcc66bf2": +"@fioprotocol/fiosdk@https://github.com/EdgeApp/fiosdk_typescript.git#47df5818442edec69b735d6a723747aad33b8d71": version "1.9.0" - resolved "https://github.com/jon-edge/fiosdk_typescript.git#92a0fb895b2ce57e5955cd30cb4b7fa2bcc66bf2" + resolved "https://github.com/EdgeApp/fiosdk_typescript.git#47df5818442edec69b735d6a723747aad33b8d71" dependencies: "@fioprotocol/fiojs" "1.0.1" "@types/text-encoding" "0.0.35" @@ -9526,10 +9539,10 @@ ed25519@0.0.4: bindings "^1.2.1" nan "^2.0.9" -edge-core-js@^2.41.0: - version "2.41.0" - resolved "https://registry.yarnpkg.com/edge-core-js/-/edge-core-js-2.41.0.tgz#4262701cc0719a263b4132e72e7e1299ab363817" - integrity sha512-ntTT0yEtr6xF6+u4KbfLK7LX2kmXBvpwD8LU26B9+k3L3o1UsVZ36OjY8B6w1Wvo+jSNldxNyhTqyQYgX0ysoQ== +edge-core-js@^2.41.3: + version "2.41.3" + resolved "https://registry.yarnpkg.com/edge-core-js/-/edge-core-js-2.41.3.tgz#c228d3c1f545af0c13f07e08308f81c98edc66fa" + integrity sha512-UxKFxFK+3Ypd/bY1ZM3XjFbqeQAqzkMQA3YGiYifQtdT8E1Vw3UKrhvtlaOkT44lRyamJzv64bYs5aRx8CetZQ== dependencies: "@nymproject/mix-fetch" "^1.4.1" aes-js "^3.1.0" @@ -9552,10 +9565,10 @@ edge-core-js@^2.41.0: yaob "^0.3.12" yavent "^0.1.5" -edge-currency-accountbased@^4.71.1: - version "4.71.1" - resolved "https://registry.yarnpkg.com/edge-currency-accountbased/-/edge-currency-accountbased-4.71.1.tgz#802dd5e2410737a04c09c4bd13683f7b3b620791" - integrity sha512-BT4LjE79f5wDFjwK5t0QoQ7E/RXqLlBVvBn7RRzdgr8Haf/ZTmylFT02+fHAqzzjyB8k11ck/bMJRIAnp4Rf3A== +edge-currency-accountbased@^4.71.4: + version "4.71.4" + resolved "https://registry.yarnpkg.com/edge-currency-accountbased/-/edge-currency-accountbased-4.71.4.tgz#5f819c9ec6998d4a9f474732809415609f9d6ad0" + integrity sha512-nJCH52SsnZm9W3Pnv49/iRt61Xn3Jo6ovFu+EZezFdyl9UVsq+pbbAN/R3fONZBRbcpH448pAeHjL83EkQuYlw== dependencies: "@chain-registry/client" "^2.0.28" "@chain-registry/types" "^2.0.28" @@ -9563,7 +9576,7 @@ edge-currency-accountbased@^4.71.1: "@emurgo/cardano-serialization-lib-nodejs" "^14.1.1" "@ethereumjs/common" "^4.0.0" "@ethereumjs/tx" "^5.0.0" - "@fioprotocol/fiosdk" "https://github.com/jon-edge/fiosdk_typescript.git#92a0fb895b2ce57e5955cd30cb4b7fa2bcc66bf2" + "@fioprotocol/fiosdk" "https://github.com/EdgeApp/fiosdk_typescript.git#47df5818442edec69b735d6a723747aad33b8d71" "@greymass/eosio" "^0.6.8" "@greymass/eosio-resources" "^0.7.0" "@hashgraph/sdk" "^2.44.0" @@ -9624,10 +9637,10 @@ edge-currency-monero@^2.2.0: buffer "^5.0.6" uri-js "^3.0.2" -edge-currency-plugins@^3.8.10: - version "3.8.10" - resolved "https://registry.yarnpkg.com/edge-currency-plugins/-/edge-currency-plugins-3.8.10.tgz#b154ddf945287645bbb5ca0678187443a5f4ad2e" - integrity sha512-N7MaPL2YuIS8ADH9oKvcyvl772PePf5SPoCyu5IYmpXUDPe1204wNEbGgyGAyeAVnzzflIrjfnzuohjPaWgpaA== +edge-currency-plugins@^3.8.11: + version "3.8.11" + resolved "https://registry.yarnpkg.com/edge-currency-plugins/-/edge-currency-plugins-3.8.11.tgz#1e2c16e1599425d3b212c225cde10d665ca5548e" + integrity sha512-cMzwSt4PFjkSzSmtINtxiy+xgvxoxjYs62dItP2wbsWkmT+4sqqI3Zr23w6XnDS14B18NbMfUw28C6uZyd2Oqg== dependencies: "@bitcoinerlab/secp256k1" "^1.2.0" altcoin-js "^1.0.0" @@ -9654,10 +9667,10 @@ edge-currency-plugins@^3.8.10: wifgrs "^2.0.6" ws "^7.4.6" -edge-exchange-plugins@^2.40.4: - version "2.40.4" - resolved "https://registry.yarnpkg.com/edge-exchange-plugins/-/edge-exchange-plugins-2.40.4.tgz#eafcea40175e5413fc0a6b5927017cfbed24c649" - integrity sha512-zuzpl46GkDeZqr1Mn59dplmtel3oce3OHaUmsI6Sem1alUb1zlgSDi+m5RVlqR3d+QikrhKG7IsBaxOSLM9fcg== +edge-exchange-plugins@^2.40.5: + version "2.40.5" + resolved "https://registry.yarnpkg.com/edge-exchange-plugins/-/edge-exchange-plugins-2.40.5.tgz#fe92fe78ff74cf09509697e085cce0bcfcf77826" + integrity sha512-RaeJ1q+Y3uLfNmD/vCtCfnBGzX+hWu0HaDT36iRobw7gpKN5kjqW6KREw9ali6riVZ/J/eFfLEBZzp8MPbKmPg== dependencies: "@cosmjs/encoding" "^0.32.2" "@scure/base" "^1.2.6" @@ -16317,10 +16330,10 @@ react-native-worklets@^0.6.1: convert-source-map "^2.0.0" semver "7.7.2" -react-native-zano@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/react-native-zano/-/react-native-zano-0.2.5.tgz#ccf604656220436a2a2652a6556381496ab87aa0" - integrity sha512-aI/vk9tBW3pRBTQWj8jJiqqx1RKK1P78vefi/fYi4jGCzwLn82OQ5bsm5Ne5RA3XrzceKHE13spmES8EmgDUTQ== +react-native-zano@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/react-native-zano/-/react-native-zano-0.2.6.tgz#2fc61b2124faa5438d5558c55ceb8b70d36c384e" + integrity sha512-uNEBVCTqFiDWk+GGikcyaf0N8HjdWOE8B6cq6DF5sfKyNXdRHFPy6g6Ha9+d7q3qZvoaQklvqs4al1J0Es5RCw== dependencies: cleaners "^0.3.17" @@ -17756,7 +17769,16 @@ string-length@^4.0.2: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -17865,7 +17887,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17879,6 +17901,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.0, strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -19592,7 +19621,7 @@ wordwrapjs@^4.0.0: reduce-flatten "^2.0.0" typical "^5.2.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -19610,6 +19639,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"