Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onboarding): Keyless Backup Setup Fail in Onboarding Flow #5537

Merged
merged 11 commits into from
Jun 14, 2024
3 changes: 2 additions & 1 deletion locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@
"title": "Backup unavailable",
"body": "You can set up wallet backup later or protect your account now by saving your recovery phrase.",
"later": "I'll set up backup later",
"manual": "Save recovery phrase"
"manual": "Save recovery phrase",
"skip": "Skip (not recommended)"
}
},
"restore": {
Expand Down
1 change: 1 addition & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export enum KeylessBackupEvents {
cab_progress_completed_continue = 'cab_progress_completed_continue',
cab_progress_failed_later = 'cab_progress_failed_later',
cab_progress_failed_manual = 'cab_progress_failed_manual',
cab_progress_failed_skip_onboarding = 'cab_progress_failed_skip_onboarding',
cab_post_encrypted_mnemonic_failed = 'cab_post_encrypted_mnemonic_failed',
cab_torus_keyshare_timeout = 'cab_torus_keyshare_timeout',
cab_handle_keyless_backup_failed = 'cab_handle_keyless_backup_failed',
Expand Down
4 changes: 3 additions & 1 deletion src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { SerializableRewardsInfo } from 'src/earn/types'
import { ProviderSelectionAnalyticsData } from 'src/fiatExchanges/types'
import { CICOFlow, FiatExchangeFlow, PaymentMethod } from 'src/fiatExchanges/utils'
import { HomeActionName, NotificationBannerCTATypes, NotificationType } from 'src/home/types'
import { KeylessBackupOrigin } from 'src/keylessBackup/KeylessBackupProgress'
import { KeylessBackupFlow, KeylessBackupStatus } from 'src/keylessBackup/types'
import { LocalCurrencyCode } from 'src/localCurrency/consts'
import { NftOrigin } from 'src/nfts/types'
Expand Down Expand Up @@ -264,7 +265,8 @@ interface KeylessBackupEventsProperties {
[KeylessBackupEvents.cab_issue_valora_keyshare_error]: CommonKeylessBackupProps
[KeylessBackupEvents.cab_progress_completed_continue]: undefined
[KeylessBackupEvents.cab_progress_failed_later]: undefined
[KeylessBackupEvents.cab_progress_failed_manual]: undefined
[KeylessBackupEvents.cab_progress_failed_manual]: { origin: KeylessBackupOrigin }
[KeylessBackupEvents.cab_progress_failed_skip_onboarding]: undefined
[KeylessBackupEvents.cab_post_encrypted_mnemonic_failed]: {
backupAlreadyExists: boolean
}
Expand Down
1 change: 1 addition & 0 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[KeylessBackupEvents.cab_progress_completed_continue]: ``,
[KeylessBackupEvents.cab_progress_failed_later]: ``,
[KeylessBackupEvents.cab_progress_failed_manual]: ``,
[KeylessBackupEvents.cab_progress_failed_skip_onboarding]: ``,
[KeylessBackupEvents.cab_post_encrypted_mnemonic_failed]: ``,
[KeylessBackupEvents.cab_torus_keyshare_timeout]: ``,
[KeylessBackupEvents.cab_handle_keyless_backup_failed]: `When keyless backup fails to generate store encrypted mnemonic for setup or fails to retrieve and decrypt mnemonic for restore`,
Expand Down
3 changes: 3 additions & 0 deletions src/keylessBackup/KeylessBackupIntro.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react'
import { KeylessBackupEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import KeylessBackupIntro from 'src/keylessBackup/KeylessBackupIntro'
import { KeylessBackupOrigin } from 'src/keylessBackup/KeylessBackupProgress'
import { KeylessBackupFlow } from 'src/keylessBackup/types'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
Expand Down Expand Up @@ -30,6 +31,7 @@ describe('KeylessBackupIntro', () => {
})
expect(navigate).toHaveBeenCalledWith(Screens.SignInWithEmail, {
keylessBackupFlow: KeylessBackupFlow.Setup,
origin: KeylessBackupOrigin.Settings,
})
})

Expand Down Expand Up @@ -68,6 +70,7 @@ describe('KeylessBackupIntro', () => {
})
expect(navigate).toHaveBeenCalledWith(Screens.SignInWithEmail, {
keylessBackupFlow: KeylessBackupFlow.Restore,
origin: KeylessBackupOrigin.Settings,
})
})
})
Expand Down
6 changes: 5 additions & 1 deletion src/keylessBackup/KeylessBackupIntro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CancelButton from 'src/components/CancelButton'
import Card from 'src/components/Card'
import TextButton from 'src/components/TextButton'
import EnvelopeIcon from 'src/keylessBackup/EnvelopeIcon'
import { KeylessBackupOrigin } from 'src/keylessBackup/KeylessBackupProgress'
import SmartphoneIcon from 'src/keylessBackup/SmartphoneIcon'
import { KeylessBackupFlow } from 'src/keylessBackup/types'
import { emptyHeader } from 'src/navigator/Headers'
Expand Down Expand Up @@ -84,7 +85,10 @@ function KeylessBackupIntro({ route }: Props) {
testID="keylessBackupIntro/Continue"
onPress={() => {
ValoraAnalytics.track(KeylessBackupEvents.cab_intro_continue, { keylessBackupFlow })
navigate(Screens.SignInWithEmail, { keylessBackupFlow })
navigate(Screens.SignInWithEmail, {
keylessBackupFlow,
origin: KeylessBackupOrigin.Settings,
})
}}
text={isSetup ? t('continue') : t('next')}
size={BtnSizes.FULL}
Expand Down
1 change: 1 addition & 0 deletions src/keylessBackup/KeylessBackupPhoneCodeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function KeylessBackupPhoneCodeInput({
onSuccess={() => {
navigate(Screens.KeylessBackupProgress, {
keylessBackupFlow: route.params.keylessBackupFlow,
origin: route.params.origin,
})
}}
title={<Text style={styles.title}>{t('phoneVerificationInput.title')}</Text>}
Expand Down
4 changes: 3 additions & 1 deletion src/keylessBackup/KeylessBackupPhoneInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Props = NativeStackScreenProps<StackParamList, Screens.KeylessBackupPhoneIn

function KeylessBackupPhoneInput({ route }: Props) {
const { t } = useTranslation()
const { selectedCountryCodeAlpha2, keylessBackupFlow } = route.params
const { selectedCountryCodeAlpha2, keylessBackupFlow, origin } = route.params
const cachedNumber = useSelector(e164NumberSelector)
const cachedCountryCallingCode = useSelector(defaultCountryCodeSelector)
const countries = useMemo(() => new Countries(i18n.language), [i18n.language])
Expand Down Expand Up @@ -78,6 +78,7 @@ function KeylessBackupPhoneInput({ route }: Props) {
navigate(Screens.KeylessBackupPhoneInput, {
keylessBackupFlow,
selectedCountryCodeAlpha2: countryCodeAlpha2,
origin,
})
},
})
Expand All @@ -90,6 +91,7 @@ function KeylessBackupPhoneInput({ route }: Props) {
navigate(Screens.KeylessBackupPhoneCodeInput, {
keylessBackupFlow,
e164Number: phoneNumberInfo.e164Number,
origin,
})
}

Expand Down
52 changes: 49 additions & 3 deletions src/keylessBackup/KeylessBackupProgress.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react'
import { Provider } from 'react-redux'
import { KeylessBackupEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import KeylessBackupProgress from 'src/keylessBackup/KeylessBackupProgress'
import KeylessBackupProgress, { KeylessBackupOrigin } from 'src/keylessBackup/KeylessBackupProgress'
import { keylessBackupAcceptZeroBalance, keylessBackupBail } from 'src/keylessBackup/slice'
import { KeylessBackupFlow, KeylessBackupStatus } from 'src/keylessBackup/types'
import { ensurePincode, navigate, navigateHome } from 'src/navigator/NavigationService'
Expand Down Expand Up @@ -40,9 +40,13 @@ function createStore(keylessBackupStatus: KeylessBackupStatus, zeroBalance = fal
})
}

function getProps(flow: KeylessBackupFlow = KeylessBackupFlow.Setup) {
function getProps(
flow: KeylessBackupFlow = KeylessBackupFlow.Setup,
origin: KeylessBackupOrigin = KeylessBackupOrigin.Settings
) {
return getMockStackScreenProps(Screens.KeylessBackupProgress, {
keylessBackupFlow: flow,
origin,
})
}

Expand Down Expand Up @@ -115,7 +119,49 @@ describe('KeylessBackupProgress', () => {

expect(ValoraAnalytics.track).toHaveBeenCalledTimes(1)
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
KeylessBackupEvents.cab_progress_failed_manual
KeylessBackupEvents.cab_progress_failed_manual,
{ origin: KeylessBackupOrigin.Settings }
)
})
it('navigates to recovery phrase on failure when coming from onboarding', async () => {
jest.mocked(ensurePincode).mockResolvedValueOnce(true)
const { getByTestId } = render(
<Provider store={createStore(KeylessBackupStatus.Failed)}>
<KeylessBackupProgress
{...getProps(KeylessBackupFlow.Setup, KeylessBackupOrigin.Onboarding)}
/>
</Provider>
)
expect(getByTestId('KeylessBackupProgress/ManualOnboarding')).toBeTruthy()
fireEvent.press(getByTestId('KeylessBackupProgress/ManualOnboarding'))

await waitFor(() => expect(navigate).toHaveBeenCalledTimes(1))
expect(navigate).toHaveBeenCalledWith(Screens.AccountKeyEducation)

expect(ValoraAnalytics.track).toHaveBeenCalledTimes(1)
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
KeylessBackupEvents.cab_progress_failed_manual,
{ origin: KeylessBackupOrigin.Onboarding }
)
})
it('navigates to CYA on failure when coming from onboarding', async () => {
jest.mocked(ensurePincode).mockResolvedValueOnce(true)
const { getByTestId } = render(
<Provider store={createStore(KeylessBackupStatus.Failed)}>
<KeylessBackupProgress
{...getProps(KeylessBackupFlow.Setup, KeylessBackupOrigin.Onboarding)}
/>
</Provider>
)
expect(getByTestId('KeylessBackupProgress/Skip')).toBeTruthy()
fireEvent.press(getByTestId('KeylessBackupProgress/Skip'))

await waitFor(() => expect(navigate).toHaveBeenCalledTimes(1))
expect(navigate).toHaveBeenCalledWith(Screens.ChooseYourAdventure)

expect(ValoraAnalytics.track).toHaveBeenCalledTimes(1)
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
KeylessBackupEvents.cab_progress_failed_skip_onboarding
)
})
})
Expand Down
54 changes: 43 additions & 11 deletions src/keylessBackup/KeylessBackupProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ import Logger from 'src/utils/Logger'

const TAG = 'keylessBackup/KeylessBackupProgress'

export enum KeylessBackupOrigin {
Onboarding = 'Onboarding',
Settings = 'Settings',
}
Comment on lines +35 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: move this to keylessBackup/types since this is used in multiple places. This can potentially cause circular imports


function KeylessBackupProgress({
route,
navigation,
}: NativeStackScreenProps<StackParamList, Screens.KeylessBackupProgress>) {
const keylessBackupStatus = useSelector(keylessBackupStatusSelector)
const { t } = useTranslation()
const { keylessBackupFlow, origin } = route.params

const onPressHelp = () => {
ValoraAnalytics.track(KeylessBackupEvents.cab_restore_failed_help)
Expand All @@ -55,7 +61,7 @@ function KeylessBackupProgress({
navigation.setOptions({
headerRight: () =>
keylessBackupStatus === KeylessBackupStatus.Failed &&
route.params.keylessBackupFlow === KeylessBackupFlow.Restore && (
keylessBackupFlow === KeylessBackupFlow.Restore && (
<TopBarTextButton
title={t('keylessBackupStatus.restore.failed.help')}
testID="KeylessBackupRestoreHelp"
Expand All @@ -66,10 +72,10 @@ function KeylessBackupProgress({
})
})

if (route.params.keylessBackupFlow === KeylessBackupFlow.Restore) {
if (keylessBackupFlow === KeylessBackupFlow.Restore) {
return <Restore />
} else {
return <Setup />
return <Setup origin={origin} />
}
}

Expand Down Expand Up @@ -250,17 +256,19 @@ function Restore() {
}
}

function Setup() {
function Setup({ origin }: { origin: KeylessBackupOrigin }) {
const keylessBackupStatus = useSelector(keylessBackupStatusSelector)
const { t } = useTranslation()

const navigatedFromSettings = origin === KeylessBackupOrigin.Settings

const onPressContinue = () => {
ValoraAnalytics.track(KeylessBackupEvents.cab_progress_completed_continue)
navigateHome()
}

const onPressManual = async () => {
ValoraAnalytics.track(KeylessBackupEvents.cab_progress_failed_manual)
ValoraAnalytics.track(KeylessBackupEvents.cab_progress_failed_manual, { origin })
try {
const pinIsCorrect = await ensurePincode()
if (pinIsCorrect) {
Expand All @@ -276,6 +284,16 @@ function Setup() {
navigate(Screens.Settings)
}

const onPressManualOnboarding = () => {
ValoraAnalytics.track(KeylessBackupEvents.cab_progress_failed_manual, { origin })
navigate(Screens.AccountKeyEducation)
}

const onPressSkip = () => {
ValoraAnalytics.track(KeylessBackupEvents.cab_progress_failed_skip_onboarding)
navigate(Screens.ChooseYourAdventure)
}

switch (keylessBackupStatus) {
case KeylessBackupStatus.InProgress: {
return renderInProgressState(t('keylessBackupStatus.setup.inProgress.title'))
Expand Down Expand Up @@ -311,18 +329,32 @@ function Setup() {
<Text style={styles.body}>{t('keylessBackupStatus.setup.failed.body')}</Text>
</View>
<Button
testID="KeylessBackupProgress/Later"
onPress={onPressLater}
text={t('keylessBackupStatus.setup.failed.later')}
testID={
navigatedFromSettings
? 'KeylessBackupProgress/Later'
: 'KeylessBackupProgress/ManualOnboarding'
}
onPress={navigatedFromSettings ? onPressLater : onPressManualOnboarding}
text={t(
navigatedFromSettings
? 'keylessBackupStatus.setup.failed.later'
: 'keylessBackupStatus.setup.failed.manual'
)}
size={BtnSizes.FULL}
type={BtnTypes.PRIMARY}
style={styles.button}
touchableStyle={styles.buttonTouchable}
/>
<Button
testID="KeylessBackupProgress/Manual"
onPress={onPressManual}
text={t('keylessBackupStatus.setup.failed.manual')}
testID={
navigatedFromSettings ? 'KeylessBackupProgress/Manual' : 'KeylessBackupProgress/Skip'
}
onPress={navigatedFromSettings ? onPressManual : onPressSkip}
text={t(
navigatedFromSettings
? 'keylessBackupStatus.setup.failed.manual'
: 'keylessBackupStatus.setup.failed.skip'
)}
size={BtnSizes.FULL}
type={BtnTypes.SECONDARY}
style={styles.button}
Expand Down
4 changes: 2 additions & 2 deletions src/keylessBackup/SignInWithEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function SignInWithEmail({ route }: Props) {
const { t } = useTranslation()
const dispatch = useDispatch()
const { authorize, getCredentials, clearCredentials } = useAuth0()
const { keylessBackupFlow } = route.params
const { keylessBackupFlow, origin } = route.params
const [loading, setLoading] = useState(false)

const onPressGoogle = async () => {
Expand Down Expand Up @@ -57,7 +57,7 @@ function SignInWithEmail({ route }: Props) {
if (!credentials.idToken) {
throw new Error('got an empty token from auth0')
}
navigate(Screens.KeylessBackupPhoneInput, { keylessBackupFlow })
navigate(Screens.KeylessBackupPhoneInput, { keylessBackupFlow, origin })
dispatch(googleSignInCompleted({ idToken: credentials.idToken }))
ValoraAnalytics.track(KeylessBackupEvents.cab_sign_in_with_google_success, {
keylessBackupFlow,
Expand Down
5 changes: 5 additions & 0 deletions src/navigator/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote'
import { CICOFlow, FiatExchangeFlow, SimplexQuote } from 'src/fiatExchanges/utils'
import { Props as KycLandingProps } from 'src/fiatconnect/KycLanding'
import { FiatAccount } from 'src/fiatconnect/slice'
import { KeylessBackupOrigin } from 'src/keylessBackup/KeylessBackupProgress'
import { KeylessBackupFlow } from 'src/keylessBackup/types'
import { Screens } from 'src/navigator/Screens'
import { Nft } from 'src/nfts/types'
Expand Down Expand Up @@ -136,13 +137,16 @@ export type StackParamList = {
[Screens.KeylessBackupPhoneCodeInput]: {
keylessBackupFlow: KeylessBackupFlow
e164Number: string
origin: KeylessBackupOrigin
}
[Screens.KeylessBackupPhoneInput]: {
keylessBackupFlow: KeylessBackupFlow
selectedCountryCodeAlpha2?: string
origin: KeylessBackupOrigin
}
[Screens.KeylessBackupProgress]: {
keylessBackupFlow: KeylessBackupFlow
origin: KeylessBackupOrigin
}
[Screens.KeylessBackupIntro]: {
keylessBackupFlow: KeylessBackupFlow
Expand Down Expand Up @@ -260,6 +264,7 @@ export type StackParamList = {
[Screens.Settings]: { promptConfirmRemovalModal?: boolean } | undefined
[Screens.SignInWithEmail]: {
keylessBackupFlow: KeylessBackupFlow
origin: KeylessBackupOrigin
}
[Screens.Spend]: undefined
[Screens.StoreWipeRecoveryScreen]: undefined
Expand Down
2 changes: 2 additions & 0 deletions src/onboarding/registration/ImportSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { fireEvent, render } from '@testing-library/react-native'
import * as React from 'react'
import 'react-native'
import { Provider } from 'react-redux'
import { KeylessBackupOrigin } from 'src/keylessBackup/KeylessBackupProgress'
import { KeylessBackupFlow } from 'src/keylessBackup/types'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
Expand Down Expand Up @@ -37,6 +38,7 @@ describe('ImportSelect', () => {
fireEvent.press(getByTestId('ImportSelect/CloudBackup'))
expect(navigate).toHaveBeenCalledWith(Screens.SignInWithEmail, {
keylessBackupFlow: KeylessBackupFlow.Restore,
origin: KeylessBackupOrigin.Onboarding,
})
})

Expand Down
Loading
Loading