diff --git a/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap index 6b9221e5259..9a4c38e671c 100644 --- a/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap @@ -26,7 +26,7 @@ exports[`Request should render a blank scene with loaded props 1`] = ` } } > - To buy, sell, and receive funds, please back up your account. Edge encrypted backups use a familiar username and password method that will safeguard your assets and help prevent loss of funds. + To buy, sell, and receive funds, please create a full account. Edge full accounts require no personal information and use a familiar username and password method that will safeguard your assets and help prevent loss of funds. Never share your username and password, and store your credentials securely! diff --git a/src/actions/ScanActions.tsx b/src/actions/ScanActions.tsx index 2f3d99e75be..e6426c32a7d 100644 --- a/src/actions/ScanActions.tsx +++ b/src/actions/ScanActions.tsx @@ -1,3 +1,4 @@ +import { gt } from 'biggystring' import { asMaybeInsufficientFundsError, EdgeAccount, EdgeCurrencyWallet, EdgeParsedUri, EdgeSpendInfo, EdgeTokenId } from 'edge-core-js' import * as React from 'react' import { sprintf } from 'sprintf-js' @@ -9,9 +10,10 @@ import { WalletListModal, WalletListResult } from '../components/modals/WalletLi import { Airship, showError, showWarning } from '../components/services/AirshipInstance' import { getSpecialCurrencyInfo } from '../constants/WalletAndCurrencyConstants' import { lstrings } from '../locales/strings' +import { convertCurrency } from '../selectors/WalletSelectors' import { config } from '../theme/appConfig' import { RequestAddressLink } from '../types/DeepLinkTypes' -import { Dispatch, ThunkAction } from '../types/reduxTypes' +import { Dispatch, RootState, ThunkAction } from '../types/reduxTypes' import { NavigationBase } from '../types/routerTypes' import { getCurrencyCode, getWalletTokenId } from '../util/CurrencyInfoHelpers' import { parseDeepLink } from '../util/DeepLinkParser' @@ -203,7 +205,7 @@ export function handleWalletUris( if (parsedUri.privateKeys != null && parsedUri.privateKeys.length > 0) { // PRIVATE KEY URI - return await privateKeyModalActivated(account, navigation, wallet, parsedUri.privateKeys) + return await privateKeyModalActivated(state, account, navigation, wallet, parsedUri.privateKeys) } // PUBLIC ADDRESS URI @@ -239,7 +241,13 @@ export function handleWalletUris( } } -async function privateKeyModalActivated(account: EdgeAccount, navigation: NavigationBase, wallet: EdgeCurrencyWallet, privateKeys: string[]): Promise { +async function privateKeyModalActivated( + state: RootState, + account: EdgeAccount, + navigation: NavigationBase, + wallet: EdgeCurrencyWallet, + privateKeys: string[] +): Promise { const message = sprintf(lstrings.private_key_modal_sweep_from_private_key_message, config.appName) await Airship.show<'confirm' | 'cancel' | undefined>(bridge => ( @@ -257,7 +265,7 @@ async function privateKeyModalActivated(account: EdgeAccount, navigation: Naviga const memoryWalletPromise = account.makeMemoryWallet(wallet.type, { keys }) navigation.navigate('sweepPrivateKeyProcessing', { memoryWalletPromise, receivingWallet: wallet }) } catch (e) { - await sweepPrivateKeys(account, navigation, wallet, privateKeys) + await sweepPrivateKeys(state, account, navigation, wallet, privateKeys) } return true } @@ -269,7 +277,7 @@ async function privateKeyModalActivated(account: EdgeAccount, navigation: Naviga )) } -async function sweepPrivateKeys(account: EdgeAccount, navigation: NavigationBase, wallet: EdgeCurrencyWallet, privateKeys: string[]) { +async function sweepPrivateKeys(state: RootState, account: EdgeAccount, navigation: NavigationBase, wallet: EdgeCurrencyWallet, privateKeys: string[]) { if (checkAndShowLightBackupModal(account, navigation)) return try { @@ -278,6 +286,11 @@ async function sweepPrivateKeys(account: EdgeAccount, navigation: NavigationBase privateKeys, spendTargets: [] }) + // Check for a $50 maximum sweep for light accounts + const sweepAmountFiat = convertCurrency(state, wallet.currencyInfo.currencyCode, 'USD', unsignedTx.nativeAmount) + if (gt(sweepAmountFiat, 50) && checkAndShowLightBackupModal(account, navigation)) return + + // Continue with sweep if above requirements met const signedTx = await wallet.signTx(unsignedTx) await wallet.broadcastTx(signedTx) diff --git a/src/components/scenes/GettingStartedScene.tsx b/src/components/scenes/GettingStartedScene.tsx index 8d3075b3729..9329c35e619 100644 --- a/src/components/scenes/GettingStartedScene.tsx +++ b/src/components/scenes/GettingStartedScene.tsx @@ -20,7 +20,7 @@ import uspImage1 from '../../assets/images/gettingStarted/usp1.png' import uspImage2 from '../../assets/images/gettingStarted/usp2.png' import uspImage3 from '../../assets/images/gettingStarted/usp3.png' import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings' -import { ExperimentConfig } from '../../experimentConfig' +import { ExperimentConfig, UspSigninCtaType } from '../../experimentConfig' import { useHandler } from '../../hooks/useHandler' import { lstrings } from '../../locales/strings' import { useDispatch, useSelector } from '../../types/reactRedux' @@ -87,7 +87,6 @@ export const GettingStartedScene = (props: Props) => { const { experimentConfig } = route.params const context = useSelector(state => state.core.context) const hasLocalUsers = context.localUsers.length > 0 - const lightAccounts = experimentConfig.createAccountType === 'light' && !hasLocalUsers // An extra index is added to account for the extra initial usp slide OR to // allow the SwipeOffsetDetector extra room for the user to swipe beyond to @@ -98,7 +97,7 @@ export const GettingStartedScene = (props: Props) => { // Route helpers const visitPasswordScene = (): void => navigation.replace('login', { - loginUiInitialRoute: lightAccounts ? 'login-password-light' : 'login-password', + loginUiInitialRoute: 'login-password', experimentConfig }) @@ -106,7 +105,8 @@ export const GettingStartedScene = (props: Props) => { // Android needs replace instead of navigate or the loginUiInitialRoute // doesn't work... navigation.replace('login', { - loginUiInitialRoute: lightAccounts ? 'new-light-account' : 'new-account', + // Only create light accounts if no other accounts exist + loginUiInitialRoute: hasLocalUsers ? 'new-account' : 'new-light-account', experimentConfig }) @@ -156,6 +156,37 @@ export const GettingStartedScene = (props: Props) => { } ) + const footerButtons = + experimentConfig.uspSigninCta === 'alreadyHaveAccount' ? ( + <> + + + + {/* eslint-disable-next-line react-native/no-raw-text */} + {`${lstrings.getting_started_already_have_an_account} `} + {lstrings.getting_started_sign_in} + + + + ) : ( + + ) + return ( @@ -204,7 +235,7 @@ export const GettingStartedScene = (props: Props) => { ))} - + {sections.map((section, index) => { return (
@@ -217,20 +248,7 @@ export const GettingStartedScene = (props: Props) => { ) })} - - - - {/* eslint-disable-next-line react-native/no-raw-text */} - {`${lstrings.getting_started_already_have_an_account} `} - {lstrings.getting_started_sign_in} - - + {footerButtons} @@ -422,11 +440,11 @@ const SectionCoverAnimated = styled(Animated.View)<{ swipeOffset: SharedValue }>(theme => props => { - const { swipeOffset } = props +const Sections = styled(Animated.View)<{ swipeOffset: SharedValue; uspSigninCta: UspSigninCtaType }>(theme => props => { + const { swipeOffset, uspSigninCta } = props return [ { - paddingBottom: theme.rem(1) + paddingBottom: uspSigninCta === 'alreadyHaveAccount' ? theme.rem(1) : theme.rem(-0.5) }, useAnimatedStyle(() => { const flexGrow = interpolate(swipeOffset.value, [0, 1], [0, 1.5]) diff --git a/src/experimentConfig.ts b/src/experimentConfig.ts index b99b51fb012..5ee6b73fe8e 100644 --- a/src/experimentConfig.ts +++ b/src/experimentConfig.ts @@ -1,33 +1,31 @@ import { asMaybe, asObject, asValue, Cleaner } from 'cleaners' import { makeReactNativeDisklet } from 'disklet' -import { CreateAccountType } from 'edge-login-ui-rn' import { LOCAL_EXPERIMENT_CONFIG } from './constants/constantSettings' import { ENV } from './env' import { isMaestro } from './util/maestro' -export type BackupTextType = 'original' | 'backup' | 'secure' | 'create' +export type UspSigninCtaType = 'alreadyHaveAccount' | 'signIn' // Persistent experiment config for A/B testing. Values initialized in this // config persist throughout the liftetime of the app install. export interface ExperimentConfig { - createAccountType: CreateAccountType signupCaptcha: 'withCaptcha' | 'withoutCaptcha' + uspSigninCta: UspSigninCtaType } // Defined default "unchanged" values before experimentation. export const DEFAULT_EXPERIMENT_CONFIG: ExperimentConfig = { - createAccountType: 'full', - signupCaptcha: 'withoutCaptcha' + signupCaptcha: 'withoutCaptcha', + uspSigninCta: 'alreadyHaveAccount' } const experimentConfigDisklet = makeReactNativeDisklet() // The probability of an experiment config feature being set for a given key const experimentDistribution = { - createAccountType: [50, 50], signupCaptcha: [50, 50], - backupText: [25, 25, 25, 25] + uspSigninCta: [50, 50] } /** @@ -67,8 +65,8 @@ const generateExperimentConfigVal = (key: keyof typeof experimentDistribution } const asExperimentConfig: Cleaner = asObject({ - createAccountType: asMaybe(asValue('full', 'light'), generateExperimentConfigVal('createAccountType', ['full', 'light'])), - signupCaptcha: asMaybe(asValue('withoutCaptcha', 'withCaptcha'), generateExperimentConfigVal('signupCaptcha', ['withoutCaptcha', 'withCaptcha'])) + signupCaptcha: asMaybe(asValue('withoutCaptcha', 'withCaptcha'), generateExperimentConfigVal('signupCaptcha', ['withoutCaptcha', 'withCaptcha'])), + uspSigninCta: asMaybe(asValue('alreadyHaveAccount', 'signIn'), generateExperimentConfigVal('uspSigninCta', ['alreadyHaveAccount', 'signIn'])) }) /** diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 2687a5e487c..fcbfcdd8668 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -1447,7 +1447,7 @@ const strings = { fiat_plugin_sell_failed_try_again: 'Sell order failed. Please try again.', fiat_plugin_sell_failed_to_send_try_again: 'Failed to send funds for sell transaction. Please try again.', fiat_plugin_cannot_continue_camera_permission: 'Cannot continue. Camera permission needed for ID verifications', - fiat_plugin_purchase_limit_error: 'Please back up your account to increase the purchase limit', + fiat_plugin_purchase_limit_error: 'Please create a full account to increase the purchase limit', fiat_plugin_max_buy_quote_error: 'Provider cannot create max buy quote', fiat_plugin_max_sell_quote_error: 'Provider cannot create max sell quote', fiat_plugin_max_sell_quote_error_1s: 'Cannot create max sell quote for %1$s', @@ -1525,21 +1525,21 @@ const strings = { backup_account: 'Back Up Account', backup_delete_confirm_message: 'Are you sure you want to delete this account without backing up first? You will NOT be able to recover wallets and transactions for this account!', - backup_info_message: 'Create a username and password to securely encrypt and back up your account', - backup_dismiss_button: 'Continue Without a Backup', + backup_info_message: 'Create a username and password to create a full account and secure your funds. No personal information is required', + backup_dismiss_button: 'Continue with Guest Account', backup_warning_message: 'Without a backup, you risk losing your funds!', backup_web3_handle_warning_message: 'Without a backup, you risk losing your web3 handle!', tap_to_learn_more: 'Tap to learn more.', backup_for_transfer_message: - 'To buy, sell, and receive funds, please back up your account. Edge encrypted backups use a familiar username and password method that will safeguard your assets and help prevent loss of funds.\n\nNever share your username and password, and store your credentials securely!', + 'To buy, sell, and receive funds, please create a full account. Edge full accounts require no personal information and use a familiar username and password method that will safeguard your assets and help prevent loss of funds.\n\nNever share your username and password, and store your credentials securely!', guest_account: 'Guest Account', tap_to_create_username_password: 'Tap to create a username and password', // Backup Message Variants - backup_title: 'Back Up Your Account', + backup_title: 'Create Full Account', backup_message: 'Create a username and password to continue.', - backup_message_subtext: 'Backing up your account ensures you can safely recover your funds in the event that you lose access to your device.', + backup_message_subtext: 'Creating a full account ensures you can safely recover your funds in the event that you lose access to your device.', no_access_disclaimer_1s: '%1$s has zero access to user funds and does not see or store any private keys or personal information.', diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index df504e2574f..d077df0917d 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -1272,7 +1272,7 @@ "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", - "fiat_plugin_purchase_limit_error": "Please back up your account to increase the purchase limit", + "fiat_plugin_purchase_limit_error": "Please create a full account to increase the purchase limit", "fiat_plugin_max_buy_quote_error": "Provider cannot create max buy quote", "fiat_plugin_max_sell_quote_error": "Provider cannot create max sell quote", "fiat_plugin_max_sell_quote_error_1s": "Cannot create max sell quote for %1$s", @@ -1330,17 +1330,17 @@ "sepa_transfer_prompt_s": "Your order %1$s has been submitted!\n\nPlease save the order details below for your records and instruct your bank to make the payment with the information in the Payment Details section.", "backup_account": "Back Up Account", "backup_delete_confirm_message": "Are you sure you want to delete this account without backing up first? You will NOT be able to recover wallets and transactions for this account!", - "backup_info_message": "Create a username and password to securely encrypt and back up your account", - "backup_dismiss_button": "Continue Without a Backup", + "backup_info_message": "Create a username and password to create a full account and secure your funds. No personal information is required", + "backup_dismiss_button": "Continue with Guest Account", "backup_warning_message": "Without a backup, you risk losing your funds!", "backup_web3_handle_warning_message": "Without a backup, you risk losing your web3 handle!", "tap_to_learn_more": "Tap to learn more.", - "backup_for_transfer_message": "To buy, sell, and receive funds, please back up your account. Edge encrypted backups use a familiar username and password method that will safeguard your assets and help prevent loss of funds.\n\nNever share your username and password, and store your credentials securely!", + "backup_for_transfer_message": "To buy, sell, and receive funds, please create a full account. Edge full accounts require no personal information and use a familiar username and password method that will safeguard your assets and help prevent loss of funds.\n\nNever share your username and password, and store your credentials securely!", "guest_account": "Guest Account", "tap_to_create_username_password": "Tap to create a username and password", - "backup_title": "Back Up Your Account", + "backup_title": "Create Full Account", "backup_message": "Create a username and password to continue.", - "backup_message_subtext": "Backing up your account ensures you can safely recover your funds in the event that you lose access to your device.", + "backup_message_subtext": "Creating a full account ensures you can safely recover your funds in the event that you lose access to your device.", "no_access_disclaimer_1s": "%1$s has zero access to user funds and does not see or store any private keys or personal information.", "learn_more": "Learn More", "dismiss": "Dismiss",