Skip to content

Commit

Permalink
feat(onboarding): Keyless Backup Setup Fail in Onboarding Flow (#5537)
Browse files Browse the repository at this point in the history
### Description

Added origin param to decide whether CAB flow was entered from
Onboarding or Settings. The first screen that this param is passed to is
SignInWithEmail, since that is the first CAB screen that is hit during
onboarding.

### Test plan

Unit tests added.

**Manual:**
Screen:
![Simulator Screenshot - iPhone 14 Pro - 2024-06-12 at 12 31
28](https://github.com/valora-inc/wallet/assets/140328381/c3b35555-cd21-4e0b-a28e-b529856bd428)

Recovery phrase:


https://github.com/valora-inc/wallet/assets/140328381/7e17e02b-620d-4045-9e23-1e9065e52d4a


Skip:


https://github.com/valora-inc/wallet/assets/140328381/313c348c-9b48-4ad8-9be1-f0adc7c09981


### Related issues

- Fixes #ACT-1222

### Backwards compatibility

Yes

### Network scalability

If a new NetworkId and/or Network are added in the future, the changes
in this PR will:

- [X] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
  • Loading branch information
finnian0826 authored Jun 14, 2024
1 parent 14e4c61 commit 9fb1a3a
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 21 deletions.
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',
}

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

0 comments on commit 9fb1a3a

Please sign in to comment.