diff --git a/packages/core-mobile/app/assets/avatars/avatar-1.jpeg b/packages/core-mobile/app/assets/avatars/avatar-1.jpeg new file mode 100644 index 0000000000..4a0d38d0fb Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-1.jpeg differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-2.jpeg b/packages/core-mobile/app/assets/avatars/avatar-2.jpeg new file mode 100644 index 0000000000..b054d9ad3e Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-2.jpeg differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-3.jpeg b/packages/core-mobile/app/assets/avatars/avatar-3.jpeg new file mode 100644 index 0000000000..ab3ed1be9e Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-3.jpeg differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-4.jpeg b/packages/core-mobile/app/assets/avatars/avatar-4.jpeg new file mode 100644 index 0000000000..f1e63907bc Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-4.jpeg differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-5.jpeg b/packages/core-mobile/app/assets/avatars/avatar-5.jpeg new file mode 100644 index 0000000000..ba4e4e02dd Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-5.jpeg differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-6.png b/packages/core-mobile/app/assets/avatars/avatar-6.png new file mode 100644 index 0000000000..35afc7e6eb Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-6.png differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-7.png b/packages/core-mobile/app/assets/avatars/avatar-7.png new file mode 100644 index 0000000000..84b544529a Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-7.png differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-8.png b/packages/core-mobile/app/assets/avatars/avatar-8.png new file mode 100644 index 0000000000..530a2c8912 Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-8.png differ diff --git a/packages/core-mobile/app/assets/avatars/avatar-9.jpeg b/packages/core-mobile/app/assets/avatars/avatar-9.jpeg new file mode 100644 index 0000000000..d2652f9d25 Binary files /dev/null and b/packages/core-mobile/app/assets/avatars/avatar-9.jpeg differ diff --git a/packages/core-mobile/app/new/components/SimpleTextInput.tsx b/packages/core-mobile/app/new/components/SimpleTextInput.tsx new file mode 100644 index 0000000000..87bcc196ea --- /dev/null +++ b/packages/core-mobile/app/new/components/SimpleTextInput.tsx @@ -0,0 +1,58 @@ +import React from 'react' +import { + Icons, + TextInput, + TouchableOpacity, + useTheme, + View +} from '@avalabs/k2-alpine' + +export const SimpleTextInput = ({ + name, + setName, + placeholder +}: { + name: string + setName: (name: string) => void + placeholder?: string +}): React.JSX.Element => { + const { + theme: { colors } + } = useTheme() + return ( + + + {name.length !== 0 && ( + setName('')}> + + + )} + + ) +} diff --git a/packages/core-mobile/app/new/components/onboarding/AddRecoveryMethods.tsx b/packages/core-mobile/app/new/components/onboarding/AddRecoveryMethods.tsx new file mode 100644 index 0000000000..6f1fc8e846 --- /dev/null +++ b/packages/core-mobile/app/new/components/onboarding/AddRecoveryMethods.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import { View, Text, Button } from '@avalabs/k2-alpine' +import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' +import { + RecoveryMethod, + RecoveryMethods +} from 'new/hooks/useAvailableRecoveryMethods' +import { OidcAuth } from 'new/types' +import { RecoveryMethodList } from '../../components/RecoveryMethodList' + +export const AddRecoveryMethods = ({ + selectedMethod, + setSelectedMethod, + oidcAuth, + availableRecoveryMethods, + allowsUserToAddLater, + onNext, + onSkip +}: { + selectedMethod?: RecoveryMethods + setSelectedMethod: (method: RecoveryMethods) => void + oidcAuth?: OidcAuth + availableRecoveryMethods: RecoveryMethod[] + allowsUserToAddLater: boolean + onNext: () => void + onSkip: () => void +}): JSX.Element => { + return ( + + + + + Add a recovery method + + + Add recovery methods to securely restore access in case you lose + your credentials. + + + + + + {oidcAuth === undefined && allowsUserToAddLater && ( + + )} + + + + ) +} diff --git a/packages/core-mobile/app/new/routes/onboarding/analyticsConsent.tsx b/packages/core-mobile/app/new/components/onboarding/AnalyticsConsent.tsx similarity index 76% rename from packages/core-mobile/app/new/routes/onboarding/analyticsConsent.tsx rename to packages/core-mobile/app/new/components/onboarding/AnalyticsConsent.tsx index 089328114a..63d6deeb63 100644 --- a/packages/core-mobile/app/new/routes/onboarding/analyticsConsent.tsx +++ b/packages/core-mobile/app/new/components/onboarding/AnalyticsConsent.tsx @@ -1,5 +1,4 @@ import React, { useEffect } from 'react' -import { useRouter } from 'expo-router' import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' import { Button, @@ -9,27 +8,17 @@ import { View } from '@avalabs/k2-alpine' import { useDispatch } from 'react-redux' -import { useAnalyticsConsent } from 'hooks/useAnalyticsConsent' import { setViewOnce, ViewOnceKey } from 'store/viewOnce' import ScreenHeader from 'new/components/ScreenHeader' -export default function AnalyticsConsent(): JSX.Element { - const router = useRouter() - +export const AnalyticsConsent = ({ + onAcceptAnalytics, + onRejectAnalytics +}: { + onAcceptAnalytics: () => void + onRejectAnalytics: () => void +}): JSX.Element => { const dispatch = useDispatch() - const { accept, reject } = useAnalyticsConsent() - - function handleAcceptAnalytics(): void { - accept() - - router.navigate('./recoveryPhrase') - } - - function handleRejectAnalytics(): void { - reject() - - router.navigate('./recoveryPhrase') - } useEffect(() => { return () => { @@ -68,10 +57,10 @@ export default function AnalyticsConsent(): JSX.Element { backgroundColor: '$surfacePrimary', gap: 16 }}> - - diff --git a/packages/core-mobile/app/new/components/onboarding/Confirmation.tsx b/packages/core-mobile/app/new/components/onboarding/Confirmation.tsx new file mode 100644 index 0000000000..b0a940df05 --- /dev/null +++ b/packages/core-mobile/app/new/components/onboarding/Confirmation.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' +import { + Avatar, + Button, + SafeAreaView, + ScrollView, + View, + useTheme, + Text +} from '@avalabs/k2-alpine' +import { Platform } from 'react-native' +import { KeyboardAvoidingView } from 'react-native' +import { AVATARS } from 'new/consts/avatars' + +export const Confirmation = ({ + selectedAvatarId, + onNext +}: { + selectedAvatarId?: string + onNext: () => void +}): JSX.Element => { + const { + theme: { colors } + } = useTheme() + + const avatar = AVATARS.find(a => a.id === selectedAvatarId) + + return ( + + + + + + {avatar?.source && ( + + )} + + + + That’s it!{'\n'} Enjoy your wallet + + + You can now start buying, swapping, sending, receiving crypto + and collectibles with no added fees + + + + + + + + + + ) +} diff --git a/packages/core-mobile/app/new/routes/onboarding/createPin.tsx b/packages/core-mobile/app/new/components/onboarding/CreatePin.tsx similarity index 74% rename from packages/core-mobile/app/new/routes/onboarding/createPin.tsx rename to packages/core-mobile/app/new/components/onboarding/CreatePin.tsx index 8bf17df4db..af5ba4dd8c 100644 --- a/packages/core-mobile/app/new/routes/onboarding/createPin.tsx +++ b/packages/core-mobile/app/new/components/onboarding/CreatePin.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useRef, useState } from 'react' -import { router, useFocusEffect } from 'expo-router' +import React, { useEffect, useRef } from 'react' +import { useFocusEffect } from 'expo-router' import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' import { GroupList, @@ -14,9 +14,16 @@ import { Platform, Switch } from 'react-native' import { KeyboardAvoidingView } from 'react-native' import ScreenHeader from 'new/components/ScreenHeader' -export default function CreatePin(): JSX.Element { +export const CreatePin = ({ + useBiometrics, + setUseBiometrics, + onEnteredValidPin +}: { + useBiometrics: boolean + setUseBiometrics: (value: boolean) => void + onEnteredValidPin: () => void +}): React.JSX.Element => { const ref = useRef(null) - const [useBiometrics, setUseBiometrics] = useState(true) const { onEnterChosenPin, @@ -40,10 +47,8 @@ export default function CreatePin(): JSX.Element { }) useEffect(() => { - if (validPin) { - router.navigate('') - } - }, [validPin]) + validPin && onEnteredValidPin() + }, [onEnteredValidPin, validPin]) return ( @@ -90,19 +95,21 @@ export default function CreatePin(): JSX.Element { backgroundColor: '$surfacePrimary', gap: 16 }}> - - ) - } - ]} - /> + {!chosenPinEntered && ( + + ) + } + ]} + /> + )} diff --git a/packages/core-mobile/app/new/routes/onboarding/recoveryPhrase.tsx b/packages/core-mobile/app/new/components/onboarding/RecoveryPhrase.tsx similarity index 68% rename from packages/core-mobile/app/new/routes/onboarding/recoveryPhrase.tsx rename to packages/core-mobile/app/new/components/onboarding/RecoveryPhrase.tsx index bed45911f0..167970fe3d 100644 --- a/packages/core-mobile/app/new/routes/onboarding/recoveryPhrase.tsx +++ b/packages/core-mobile/app/new/components/onboarding/RecoveryPhrase.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from 'react' -import { useRouter } from 'expo-router' +import React from 'react' import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' import { Button, @@ -11,16 +10,19 @@ import { useTheme, View } from '@avalabs/k2-alpine' -import { InteractionManager } from 'react-native' -import WalletSDK from 'utils/WalletSDK' import MnemonicScreen from 'new/components/MnemonicPhrase' import ScreenHeader from 'new/components/ScreenHeader' -export default function RecoveryPhrase(): JSX.Element { - const router = useRouter() +export const RecoveryPhrase = ({ + onNext, + mnemonic, + isLoading +}: { + onNext: () => void + mnemonic: string + isLoading: boolean +}): React.JSX.Element => { const { theme } = useTheme() - const [localMnemonic, setLocalMnemonic] = useState('') - const [isLoading, setIsLoading] = useState(true) function handleNext(): void { showAlert({ @@ -31,27 +33,12 @@ export default function RecoveryPhrase(): JSX.Element { { text: 'Dismiss', style: 'cancel', - onPress: () => { - router.navigate({ - pathname: './verifyRecoveryPhrase', - params: { mnemonic: localMnemonic } - }) - } + onPress: onNext } ] }) } - useEffect(() => { - InteractionManager.runAfterInteractions(() => { - ;(async () => { - const newPhrase = await WalletSDK.generateMnemonic() - setLocalMnemonic(newPhrase) - setIsLoading(false) - })() - }) - }, []) - return ( @@ -68,7 +55,7 @@ export default function RecoveryPhrase(): JSX.Element { Losing this phrase will result in lost funds - + void + onNext: () => void +}): React.JSX.Element => { + const { + theme: { colors } + } = useTheme() + + const avatar = avatars.find(a => a.id === selectedAvatarId) + + return ( + + + + + + + + {avatar?.source && ( + + )} + + + + + + + + + ) +} diff --git a/packages/core-mobile/app/new/components/onboarding/SetWalletName.tsx b/packages/core-mobile/app/new/components/onboarding/SetWalletName.tsx new file mode 100644 index 0000000000..09c3619645 --- /dev/null +++ b/packages/core-mobile/app/new/components/onboarding/SetWalletName.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' +import { Button, SafeAreaView, ScrollView, View } from '@avalabs/k2-alpine' +import { Platform } from 'react-native' +import { KeyboardAvoidingView } from 'react-native' +import ScreenHeader from 'new/components/ScreenHeader' +import { SimpleTextInput } from 'new/components/SimpleTextInput' + +export const SetWalletName = ({ + name, + setName, + onNext +}: { + name: string + setName: (value: string) => void + onNext: () => void +}): React.JSX.Element => { + return ( + + + + + + + + + + + + + + ) +} diff --git a/packages/core-mobile/app/new/routes/onboarding/index.tsx b/packages/core-mobile/app/new/components/onboarding/TermsAndConditions.tsx similarity index 94% rename from packages/core-mobile/app/new/routes/onboarding/index.tsx rename to packages/core-mobile/app/new/components/onboarding/TermsAndConditions.tsx index 52ed819325..c5a13cc97a 100644 --- a/packages/core-mobile/app/new/routes/onboarding/index.tsx +++ b/packages/core-mobile/app/new/components/onboarding/TermsAndConditions.tsx @@ -1,5 +1,4 @@ import React from 'react' -import { useRouter } from 'expo-router' import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' import { alpha, @@ -12,15 +11,14 @@ import { import { useSafeAreaInsets } from 'react-native-safe-area-context' import { LinearGradient } from 'expo-linear-gradient' -export default function Index(): JSX.Element { +export const TermsAndConditions = ({ + onAgreeAndContinue +}: { + onAgreeAndContinue: () => void +}): JSX.Element => { const { bottom } = useSafeAreaInsets() - const router = useRouter() const { theme } = useTheme() - const handleAgreeAndContinue = (): void => { - router.navigate('./onboarding/analyticsConsent') - } - return ( @@ -108,7 +106,7 @@ export default function Index(): JSX.Element { paddingBottom: 16 + bottom, backgroundColor: '$surfacePrimary' }}> - diff --git a/packages/core-mobile/app/new/routes/onboarding/verifyRecoveryPhrase.tsx b/packages/core-mobile/app/new/components/onboarding/VerifyRecoveryPhrase.tsx similarity index 95% rename from packages/core-mobile/app/new/routes/onboarding/verifyRecoveryPhrase.tsx rename to packages/core-mobile/app/new/components/onboarding/VerifyRecoveryPhrase.tsx index 311d36f5ac..219cea0d1a 100644 --- a/packages/core-mobile/app/new/routes/onboarding/verifyRecoveryPhrase.tsx +++ b/packages/core-mobile/app/new/components/onboarding/VerifyRecoveryPhrase.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { useGlobalSearchParams, useRouter } from 'expo-router' import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' import { Button, @@ -18,9 +17,13 @@ import Animated, { } from 'react-native-reanimated' import { useCheckMnemonic } from 'new/hooks/useCheckMnemonic' -export default function VerifyRecoveryPhrase(): JSX.Element { - const router = useRouter() - const { mnemonic } = useGlobalSearchParams<{ mnemonic: string }>() +export const VerifyRecoveryPhrase = ({ + mnemonic, + onVerified +}: { + mnemonic?: string + onVerified: () => void +}): React.JSX.Element => { const mnemonics = useMemo( () => (mnemonic ? mnemonic.split(' ') : []), [mnemonic] @@ -59,7 +62,7 @@ export default function VerifyRecoveryPhrase(): JSX.Element { if (verify(selectedWord1, selectedWord2, selectedWord3)) { AnalyticsService.capture('OnboardingMnemonicVerified') - router.navigate('./createPin') + onVerified() } else { showAlert({ title: 'Invalid phrase', diff --git a/packages/core-mobile/app/new/components/totp/VerifyCode.tsx b/packages/core-mobile/app/new/components/totp/VerifyCode.tsx index acb5084e3d..808f6e1262 100644 --- a/packages/core-mobile/app/new/components/totp/VerifyCode.tsx +++ b/packages/core-mobile/app/new/components/totp/VerifyCode.tsx @@ -1,12 +1,13 @@ import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { View, Text, useTheme, Card, TextInput, - Pressable + Pressable, + showAlert } from '@avalabs/k2-alpine' import AnalyticsService from 'services/analytics/AnalyticsService' import Logger from 'utils/Logger' @@ -40,49 +41,75 @@ export const VerifyCode = ({ return cleanedInput.replace(/(\d{3})(?=\d)/g, '$1 ') }, [cleanedInput]) - const handleVerifyCode = async (changedText: string): Promise => { - const numericText = changedText.replace(/[^0-9]/g, '') - const cleanChangedText = numericText.replace(/\s/g, '') - setCode(cleanChangedText) - if (cleanChangedText.length < 6) { - setShowError(false) - return - } + const handleVerifyCode = useCallback( + async (changedText: string): Promise => { + const numericText = changedText.replace(/[^0-9]/g, '') + const cleanChangedText = numericText.replace(/\s/g, '') + setCode(cleanChangedText) + if (cleanChangedText.length < 6) { + setShowError(false) + return + } - setIsVerifying(true) + setIsVerifying(true) - try { - const result = await onVerifyCode(cleanChangedText) - if (result.success === false) { - throw new Error(result.error.message) + try { + const result = await onVerifyCode(cleanChangedText) + if (result.success === false) { + throw new Error(result.error.message) + } + setIsVerifying(false) + onVerifySuccess() + AnalyticsService.capture('TotpValidationSuccess') + } catch (error) { + setShowError(true) + setIsVerifying(false) + AnalyticsService.capture('TotpValidationFailed', { + error: error as string + }) } - setIsVerifying(false) - onVerifySuccess() - AnalyticsService.capture('TotpValidationSuccess') - } catch (error) { - setShowError(true) - setIsVerifying(false) - AnalyticsService.capture('TotpValidationFailed', { - error: error as string - }) - } - } + }, + [onVerifyCode, onVerifySuccess] + ) const handleTextInputFocus = (): void => { inputRef.current?.focus() } - const textInputStyle = showError - ? { - borderColor: colors.$textDanger, - borderWidth: 1 - } - : undefined - useEffect(() => { handleTextInputFocus() }, []) + const handleRetry = useCallback((): void => { + setShowError(false) + handleVerifyCode(code).catch(Logger.error) + }, [code, handleVerifyCode]) + + const handleCancel = (): void => { + setShowError(false) + } + + useEffect(() => { + if (showError) { + showAlert({ + title: 'Incorrect code', + description: 'The code you entered is incorrect. please try again', + buttons: [ + { + text: 'Retry', + style: 'default', + onPress: handleRetry + }, + { + text: 'Cancel', + style: 'cancel', + onPress: handleCancel + } + ] + }) + } + }, [handleRetry, showError]) + return ( - {showError && ( - - Incorrect code. Try again. - - )} diff --git a/packages/core-mobile/app/new/consts/avatars.ts b/packages/core-mobile/app/new/consts/avatars.ts new file mode 100644 index 0000000000..b100e5d344 --- /dev/null +++ b/packages/core-mobile/app/new/consts/avatars.ts @@ -0,0 +1,30 @@ +import { ImageSourcePropType } from 'react-native' + +export const AVATARS: { id: string; source: ImageSourcePropType }[] = [ + require('../../assets/avatars/avatar-1.jpeg'), + require('../../assets/avatars/avatar-2.jpeg'), + require('../../assets/avatars/avatar-3.jpeg'), + require('../../assets/avatars/avatar-4.jpeg'), + require('../../assets/avatars/avatar-5.jpeg'), + require('../../assets/avatars/avatar-6.png'), + require('../../assets/avatars/avatar-7.png'), + require('../../assets/avatars/avatar-8.png'), + require('../../assets/avatars/avatar-9.jpeg'), + { + uri: 'https://miro.medium.com/v2/resize:fit:1256/format:webp/1*xm2-adeU3YD4MsZikpc5UQ.png' + }, + { + uri: 'https://www.cnet.com/a/img/resize/7589227193923c006f9a7fd904b77bc898e105ff/hub/2021/11/29/f566750f-79b6-4be9-9c32-8402f58ba0ef/richerd.png?auto=webp&width=768' + }, + { + uri: 'https://i.seadn.io/s/raw/files/a9cb8c2298a64819a3036083818d0447.jpg?auto=format&dpr=1&w=1000' + }, + { + uri: 'https://i.seadn.io/gcs/files/441e674e79460fc975d976465bb3634d.png?auto=format&dpr=1&w=1000' + }, + { + uri: 'https://www.svgrepo.com/show/19461/url-link.svg' + } +].map((avatar, index) => { + return { id: index.toString(), source: avatar } +}) diff --git a/packages/core-mobile/app/new/contexts/OnboardingProvider.tsx b/packages/core-mobile/app/new/contexts/OnboardingProvider.tsx new file mode 100644 index 0000000000..a3a076cbe3 --- /dev/null +++ b/packages/core-mobile/app/new/contexts/OnboardingProvider.tsx @@ -0,0 +1,59 @@ +import React, { + createContext, + Dispatch, + ReactNode, + SetStateAction, + useContext, + useEffect, + useState +} from 'react' +import { WalletType } from 'services/wallet/types' +import SeedlessService from 'seedless/services/SeedlessService' +import Logger from 'utils/Logger' + +export interface OnboardingContextState { + walletType: WalletType + setWalletType: Dispatch> + hasWalletName: boolean +} + +export const OnboardingContext = createContext( + {} as OnboardingContextState +) + +export const OnboardingProvider = ({ + children +}: { + children: ReactNode +}): React.JSX.Element => { + const [walletType, setWalletType] = useState(WalletType.UNSET) + const [hasWalletName, setHasWalletName] = useState(false) + + useEffect(() => { + const checkHasWalletName = async (): Promise => { + if ( + walletType === WalletType.SEEDLESS && + (await SeedlessService.getAccountName()) !== undefined + ) { + setHasWalletName(true) + } + } + checkHasWalletName().catch(Logger.error) + }, [walletType]) + + const state: OnboardingContextState = { + walletType, + setWalletType, + hasWalletName + } + + return ( + + {children} + + ) +} + +export function useOnboardingContext(): OnboardingContextState { + return useContext(OnboardingContext) +} diff --git a/packages/core-mobile/app/new/contexts/SignupProvider.tsx b/packages/core-mobile/app/new/contexts/RecoveryMethodProvider.tsx similarity index 67% rename from packages/core-mobile/app/new/contexts/SignupProvider.tsx rename to packages/core-mobile/app/new/contexts/RecoveryMethodProvider.tsx index 37d07fc856..c767adea3c 100644 --- a/packages/core-mobile/app/new/contexts/SignupProvider.tsx +++ b/packages/core-mobile/app/new/contexts/RecoveryMethodProvider.tsx @@ -10,15 +10,12 @@ import React, { useState } from 'react' import SeedlessService from 'seedless/services/SeedlessService' -import { useRouter } from 'expo-router' import { copyToClipboard } from 'new/utils/clipboard' import { TotpErrors } from 'seedless/errors' import { Result } from 'types/result' -import { hideLogoModal, showLogoModal } from 'new/components/LogoModal' import { OidcAuth } from 'new/types' -export interface SignupContextState { - handleAccountVerified: () => Promise +export interface RecoveryMethodContextState { handleCopyCode: () => void onVerifyCode: (code: string) => Promise> totpKey?: string @@ -28,16 +25,15 @@ export interface SignupContextState { setOidcAuth: Dispatch> } -export const SignupContext = createContext( - {} as SignupContextState +export const RecoveryMethodContext = createContext( + {} as RecoveryMethodContextState ) -export const SignupProvider = ({ +export const RecoveryMethodProvider = ({ children }: { children: ReactNode }): React.JSX.Element => { - const router = useRouter() const [oidcAuth, setOidcAuth] = useState() const [totpChallenge, setTotpChallenge] = useState() @@ -70,20 +66,7 @@ export const SignupProvider = ({ [oidcAuth, totpChallenge] ) - const handleAccountVerified = useCallback(async (): Promise => { - showLogoModal() - const walletName = await SeedlessService.getAccountName() - hideLogoModal() - - if (walletName) { - router.navigate('./createPin') - return - } - router.navigate('./nameYourWallet') - }, [router]) - - const state: SignupContextState = { - handleAccountVerified, + const state: RecoveryMethodContextState = { handleCopyCode, onVerifyCode, totpKey, @@ -94,10 +77,12 @@ export const SignupProvider = ({ } return ( - {children} + + {children} + ) } -export function useSignupContext(): SignupContextState { - return useContext(SignupContext) +export function useRecoveryMethodContext(): RecoveryMethodContextState { + return useContext(RecoveryMethodContext) } diff --git a/packages/core-mobile/app/new/routes/_layout.tsx b/packages/core-mobile/app/new/routes/_layout.tsx index c4fac06425..da201b6ab8 100644 --- a/packages/core-mobile/app/new/routes/_layout.tsx +++ b/packages/core-mobile/app/new/routes/_layout.tsx @@ -20,8 +20,9 @@ import { Platform } from 'react-native' import { ApplicationContextProvider } from 'contexts/ApplicationContext' import { StackActions } from '@react-navigation/native' import { LogoModal } from 'new/components/LogoModal' -import { SignupProvider } from 'new/contexts/SignupProvider' +import { RecoveryMethodProvider } from 'new/contexts/RecoveryMethodProvider' import { stackNavigatorScreenOptions } from 'new/utils/navigation/screenOptions' +import { OnboardingProvider } from 'new/contexts/OnboardingProvider' export default function RootLayout(): JSX.Element | null { const router = useRouter() @@ -82,48 +83,50 @@ export default function RootLayout(): JSX.Element | null { - - - - - - - - - - - - - {enabledPrivacyScreen && } - + + + + + + + + + + + + + + {enabledPrivacyScreen && } + + diff --git a/packages/core-mobile/app/new/routes/accessWallet.tsx b/packages/core-mobile/app/new/routes/accessWallet.tsx index d293ad4c60..65527f6620 100644 --- a/packages/core-mobile/app/new/routes/accessWallet.tsx +++ b/packages/core-mobile/app/new/routes/accessWallet.tsx @@ -9,9 +9,11 @@ import { } from '@avalabs/k2-alpine' import Encrypted from 'assets/icons/encrypted.svg' import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' +import { useRouter } from 'expo-router' const AccessWalletScreen = (): JSX.Element => { const { theme } = useTheme() + const { navigate } = useRouter() const handleEnterRecoveryPhrase = (): void => { // navigate(AppNavigation.Onboard.Welcome, { @@ -23,12 +25,7 @@ const AccessWalletScreen = (): JSX.Element => { } const handleCreateMnemonicWallet = (): void => { - // navigate(AppNavigation.Onboard.Welcome, { - // screen: AppNavigation.Onboard.AnalyticsConsent, - // params: { - // nextScreen: AppNavigation.Onboard.CreateWalletStack - // } - // }) + navigate('./mnemonicOnboarding/termsAndConditions') } return ( diff --git a/packages/core-mobile/app/new/routes/addRecoveryMethods.tsx b/packages/core-mobile/app/new/routes/addRecoveryMethods.tsx deleted file mode 100644 index 7f681e767c..0000000000 --- a/packages/core-mobile/app/new/routes/addRecoveryMethods.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useState } from 'react' -import { View, Text, Button } from '@avalabs/k2-alpine' -import { useLocalSearchParams, useRouter } from 'expo-router' -import BlurredBarsContentLayout from 'new/components/navigation/BlurredBarsContentLayout' -import AnalyticsService from 'services/analytics/AnalyticsService' -import { - RecoveryMethods, - useAvailableRecoveryMethods -} from 'new/hooks/useAvailableRecoveryMethods' -import { useSignupContext } from 'new/contexts/SignupProvider' -import { RecoveryMethodList } from '../components/RecoveryMethodList' - -const AddRecoveryMethods = (): JSX.Element => { - const { navigate } = useRouter() - const { oidcAuth, handleAccountVerified } = useSignupContext() - const { allowsUserToAddLater } = useLocalSearchParams<{ - allowsUserToAddLater: string - }>() - const availableRecoveryMethods = useAvailableRecoveryMethods() - - const [selectedMethod, setSelectedMethod] = useState( - availableRecoveryMethods.length > 0 - ? availableRecoveryMethods[0]?.type - : undefined - ) - - const handleOnPress = (): void => { - if (selectedMethod === RecoveryMethods.Passkey) { - // navigate({ - // pathname: './fidoNameInput', - // params: { - // title: 'How would you like to name your passkey?', - // description: 'Add a Passkey name, so it’s easier to find later', - // textInputPlaceholder: 'Passkey name', - // fidoType: FidoType.PASS_KEY - // } - // }) - return - } - if (selectedMethod === RecoveryMethods.Yubikey) { - // navigate({ - // pathname: './fidoNameInput', - // params: { - // title: 'How would you like to name your YubiKey?', - // description: 'Add a YubiKey name, so it’s easier to find later', - // textInputPlaceholder: 'YubiKey name', - // fidoType: FidoType.YUBI_KEY - // } - // }) - return - } - if (selectedMethod === RecoveryMethods.Authenticator) { - navigate('./authenticatorSetup') - AnalyticsService.capture('SeedlessAddMfa', { type: 'Authenticator' }) - } - } - - return ( - - - - - Add a recovery method - - - Add recovery methods to securely restore access in case you lose - your credentials. - - - - - - {oidcAuth === undefined && - allowsUserToAddLater?.toLowerCase() === 'true' && ( - - )} - - - - ) -} - -export default AddRecoveryMethods diff --git a/packages/core-mobile/app/new/routes/onboarding/_layout.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/_layout.tsx similarity index 50% rename from packages/core-mobile/app/new/routes/onboarding/_layout.tsx rename to packages/core-mobile/app/new/routes/mnemonicOnboarding/_layout.tsx index 1891c346ca..3d34a13179 100644 --- a/packages/core-mobile/app/new/routes/onboarding/_layout.tsx +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/_layout.tsx @@ -2,25 +2,24 @@ import React, { useEffect, useState } from 'react' import { Stack } from 'new/components/navigation/Stack' import { PageControl } from '@avalabs/k2-alpine' import { stackNavigatorScreenOptions } from 'new/utils/navigation/screenOptions' -import { useRootNavigationState } from 'expo-router' +import { useNavigationContainerRef } from 'expo-router' -export default function OnboardingLayout(): JSX.Element { +export default function MnemonicOnboardingLayout(): JSX.Element { const [currentPage, setCurrentPage] = useState(0) - - const navigationState = useRootNavigationState() + const rootState = useNavigationContainerRef().getRootState() useEffect(() => { - const onboardingRoute = navigationState.routes.find( - route => route.name === 'onboarding' + const onboardingRoute = rootState.routes.find( + route => route.name === 'mnemonicOnboarding' ) if (onboardingRoute?.state?.index !== undefined) { setCurrentPage(onboardingRoute.state.index) } - }, [navigationState]) + }, [rootState]) const renderPageControl = (): React.ReactNode => ( ) @@ -31,13 +30,20 @@ export default function OnboardingLayout(): JSX.Element { ...stackNavigatorScreenOptions, headerTitle: renderPageControl }}> - - - - - + {MNEMONIC_ONBOARDING_SCREENS.map(screen => { + return + })} ) } -const NUMBER_OF_ONBOARDING_PAGES = 5 +const MNEMONIC_ONBOARDING_SCREENS = [ + 'termsAndConditions', + 'analyticsConsent', + 'recoveryPhrase', + 'verifyRecoveryPhrase', + 'createPin', + 'setWalletName', + 'selectAvatar', + 'confirmation' +] diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/analyticsConsent.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/analyticsConsent.tsx new file mode 100644 index 0000000000..bc4d043d56 --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/analyticsConsent.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { useAnalyticsConsent } from 'hooks/useAnalyticsConsent' +import { useRouter } from 'expo-router' +import { AnalyticsConsent as Component } from 'new/components/onboarding/AnalyticsConsent' + +export default function AnalyticsConsent(): JSX.Element { + const { navigate } = useRouter() + const { accept, reject } = useAnalyticsConsent() + + function handleAcceptAnalytics(): void { + accept() + navigate('./recoveryPhrase') + } + + function handleRejectAnalytics(): void { + reject() + navigate('./recoveryPhrase') + } + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/confirmation.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/confirmation.tsx new file mode 100644 index 0000000000..1f17fca364 --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/confirmation.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { Confirmation as Component } from 'new/components/onboarding/Confirmation' +import { useLocalSearchParams } from 'expo-router' +import { useWallet } from 'hooks/useWallet' +import Logger from 'utils/Logger' +import { WalletType } from 'services/wallet/types' + +export default function Confirmation(): JSX.Element { + const { login } = useWallet() + const { mnemonic, selectedAvatarId } = useLocalSearchParams<{ + mnemonic: string + selectedAvatarId: string + }>() + + const handleNext = (): void => { + mnemonic && login(mnemonic, WalletType.MNEMONIC).catch(Logger.error) + } + + return +} diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/createPin.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/createPin.tsx new file mode 100644 index 0000000000..ab3340ad3f --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/createPin.tsx @@ -0,0 +1,20 @@ +import React, { useState } from 'react' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { CreatePin as Component } from 'new/components/onboarding/CreatePin' + +export default function CreatePin(): JSX.Element { + const [useBiometrics, setUseBiometrics] = useState(true) + const { navigate } = useRouter() + const { mnemonic } = useLocalSearchParams<{ mnemonic: string }>() + + const handleEnteredValidPin = (): void => { + navigate({ pathname: './setWalletName', params: { mnemonic } }) + } + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/recoveryPhrase.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/recoveryPhrase.tsx new file mode 100644 index 0000000000..9c81a5ad76 --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/recoveryPhrase.tsx @@ -0,0 +1,36 @@ +import React, { useEffect, useState } from 'react' +import { useRouter } from 'expo-router' +import { InteractionManager } from 'react-native' +import WalletSDK from 'utils/WalletSDK' +import { RecoveryPhrase as Component } from 'new/components/onboarding/RecoveryPhrase' + +export default function RecoveryPhrase(): JSX.Element { + const { navigate } = useRouter() + const [localMnemonic, setLocalMnemonic] = useState('') + const [isLoading, setIsLoading] = useState(true) + + function handleNext(): void { + navigate({ + pathname: './verifyRecoveryPhrase', + params: { mnemonic: localMnemonic } + }) + } + + useEffect(() => { + InteractionManager.runAfterInteractions(() => { + ;(async () => { + const newPhrase = await WalletSDK.generateMnemonic() + setLocalMnemonic(newPhrase) + setIsLoading(false) + })() + }) + }, []) + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/selectAvatar.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/selectAvatar.tsx new file mode 100644 index 0000000000..de93a5951e --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/selectAvatar.tsx @@ -0,0 +1,30 @@ +import React, { useState } from 'react' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { SelectAvatar as Component } from 'new/components/onboarding/SelectAvatar' +import { AVATARS } from 'new/consts/avatars' + +export default function SelectAvatar(): JSX.Element { + const { navigate } = useRouter() + const [selectedAvatarId, setSelectedAvatarId] = useState( + AVATARS[0]?.id + ) + const { mnemonic } = useLocalSearchParams<{ + mnemonic: string + }>() + + const handleNext = (): void => { + navigate({ + pathname: './confirmation', + params: { mnemonic, selectedAvatarId } + }) + } + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/setWalletName.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/setWalletName.tsx new file mode 100644 index 0000000000..db01615cff --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/setWalletName.tsx @@ -0,0 +1,21 @@ +import React, { useState } from 'react' +import AnalyticsService from 'services/analytics/AnalyticsService' +import { useDispatch } from 'react-redux' +import { setWalletName } from 'store/account' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { SetWalletName as Component } from 'new/components/onboarding/SetWalletName' + +export default function SetWalletName(): JSX.Element { + const [name, setName] = useState('') + const dispatch = useDispatch() + const { navigate } = useRouter() + const { mnemonic } = useLocalSearchParams<{ mnemonic: string }>() + + const handleNext = (): void => { + AnalyticsService.capture('Onboard:WalletNameSet') + dispatch(setWalletName(name)) + navigate({ pathname: './selectAvatar', params: { mnemonic } }) + } + + return +} diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/termsAndConditions.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/termsAndConditions.tsx new file mode 100644 index 0000000000..020008bd09 --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/termsAndConditions.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { TermsAndConditions as Component } from 'new/components/onboarding/TermsAndConditions' +import { useRouter } from 'expo-router' + +export default function TermsAndConditions(): JSX.Element { + const { navigate } = useRouter() + + const handleAgreeAndContinue = (): void => { + navigate('./analyticsConsent') + } + + return +} diff --git a/packages/core-mobile/app/new/routes/mnemonicOnboarding/verifyRecoveryPhrase.tsx b/packages/core-mobile/app/new/routes/mnemonicOnboarding/verifyRecoveryPhrase.tsx new file mode 100644 index 0000000000..d374cb4815 --- /dev/null +++ b/packages/core-mobile/app/new/routes/mnemonicOnboarding/verifyRecoveryPhrase.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { useGlobalSearchParams, useRouter } from 'expo-router' +import { VerifyRecoveryPhrase as Component } from 'new/components/onboarding/VerifyRecoveryPhrase' + +export default function VerifyRecoveryPhrase(): JSX.Element { + const { navigate } = useRouter() + const { mnemonic } = useGlobalSearchParams<{ mnemonic: string }>() + + const handleVerified = (): void => { + navigate({ pathname: './createPin', params: { mnemonic } }) + } + + return +} diff --git a/packages/core-mobile/app/new/routes/(totp)/_layout.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/_layout.tsx similarity index 100% rename from packages/core-mobile/app/new/routes/(totp)/_layout.tsx rename to packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/_layout.tsx diff --git a/packages/core-mobile/app/new/routes/(totp)/authenticatorSetup.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/authenticatorSetup.tsx similarity index 86% rename from packages/core-mobile/app/new/routes/(totp)/authenticatorSetup.tsx rename to packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/authenticatorSetup.tsx index 5a24b74324..90e0223856 100644 --- a/packages/core-mobile/app/new/routes/(totp)/authenticatorSetup.tsx +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/authenticatorSetup.tsx @@ -1,15 +1,15 @@ import React, { useEffect } from 'react' -import { useSignupContext } from 'new/contexts/SignupProvider' +import { useRecoveryMethodContext } from 'new/contexts/RecoveryMethodProvider' import useSeedlessManageMFA from 'new/hooks/useSeedlessManageMFA' import AnalyticsService from 'services/analytics/AnalyticsService' import Logger from 'utils/Logger' import { useRouter } from 'expo-router' -import { AuthenticatorSetup as AuthenticatorSetupComponent } from '../../components/totp/AuthenticatorSetup' -import { Loader } from '../../components/totp/Loader' +import { AuthenticatorSetup as AuthenticatorSetupComponent } from '../../../components/totp/AuthenticatorSetup' +import { Loader } from '../../../components/totp/Loader' export default function AuthenticatorSetup(): JSX.Element { const { totpKey, handleCopyCode, totpChallenge, setTotpChallenge } = - useSignupContext() + useRecoveryMethodContext() const router = useRouter() const { totpResetInit } = useSeedlessManageMFA() diff --git a/packages/core-mobile/app/new/routes/(totp)/copyCode.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/copyCode.tsx similarity index 59% rename from packages/core-mobile/app/new/routes/(totp)/copyCode.tsx rename to packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/copyCode.tsx index b8bbd5dac2..76193d2d84 100644 --- a/packages/core-mobile/app/new/routes/(totp)/copyCode.tsx +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/copyCode.tsx @@ -1,11 +1,11 @@ import React from 'react' -import { useSignupContext } from 'new/contexts/SignupProvider' +import { useRecoveryMethodContext } from 'new/contexts/RecoveryMethodProvider' import { useRouter } from 'expo-router' -import { CopyCode as CopyCodeComponent } from '../../components/totp/CopyCode' -import { Loader } from '../../components/totp/Loader' +import { CopyCode as CopyCodeComponent } from '../../../components/totp/CopyCode' +import { Loader } from '../../../components/totp/Loader' export default function CopyCode(): JSX.Element { - const { handleCopyCode, totpKey } = useSignupContext() + const { handleCopyCode, totpKey } = useRecoveryMethodContext() const router = useRouter() const handleBack = (): void => { diff --git a/packages/core-mobile/app/new/routes/(totp)/scanQrCode.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/scanQrCode.tsx similarity index 68% rename from packages/core-mobile/app/new/routes/(totp)/scanQrCode.tsx rename to packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/scanQrCode.tsx index fb8d4457ef..bc38de2218 100644 --- a/packages/core-mobile/app/new/routes/(totp)/scanQrCode.tsx +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/scanQrCode.tsx @@ -1,10 +1,10 @@ import React from 'react' -import { useSignupContext } from 'new/contexts/SignupProvider' +import { useRecoveryMethodContext } from 'new/contexts/RecoveryMethodProvider' import { useRouter } from 'expo-router' -import { ScanQrCode as ScanQrCodeComponent } from '../../components/totp/ScanQrCode' +import { ScanQrCode as ScanQrCodeComponent } from '../../../components/totp/ScanQrCode' export default function ScanQrCode(): JSX.Element { - const { totpChallenge } = useSignupContext() + const { totpChallenge } = useRecoveryMethodContext() const router = useRouter() const goToVerifyCode = (): void => { diff --git a/packages/core-mobile/app/new/routes/(totp)/verifyCode.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/verifyCode.tsx similarity index 63% rename from packages/core-mobile/app/new/routes/(totp)/verifyCode.tsx rename to packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/verifyCode.tsx index 33a1a9b78a..89f6096a4c 100644 --- a/packages/core-mobile/app/new/routes/(totp)/verifyCode.tsx +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/(totp)/verifyCode.tsx @@ -1,21 +1,20 @@ import React, { useCallback } from 'react' -import { useSignupContext } from 'new/contexts/SignupProvider' +import { useRecoveryMethodContext } from 'new/contexts/RecoveryMethodProvider' import { useRouter } from 'expo-router' import AnalyticsService from 'services/analytics/AnalyticsService' -import { VerifyCode as VerifyCodeComponent } from '../../components/totp/VerifyCode' +import { VerifyCode as VerifyCodeComponent } from '../../../components/totp/VerifyCode' export default function VerifyCode(): JSX.Element { - const { onVerifyCode, handleAccountVerified } = useSignupContext() + const { onVerifyCode } = useRecoveryMethodContext() const router = useRouter() const onVerifySuccess = useCallback((): void => { router.dismissAll() - router.back() - handleAccountVerified() + router.navigate('./analyticsConsent') AnalyticsService.capture('SeedlessMfaVerified', { type: 'Authenticator' }) - }, [router, handleAccountVerified]) + }, [router]) return ( { + const onboardingRoute = rootState.routes.find( + r => r.name === 'seedlessOnboarding' + ) + if (onboardingRoute?.state?.index !== undefined) { + setCurrentPage(onboardingRoute.state.index) + } + }, [rootState]) + + // Return the onboarding screens based on the hasWalletName + // if hasWalletName is true, the function should not include the 'setWalletName' screen + const onboardingScreens = useMemo(() => { + return SEEDLESS_ONBOARDING_SCREENS.reduce((acc, screen) => { + if (hasWalletName === true && screen === 'setWalletName') { + return acc + } + return [...acc, screen] + }, [] as string[]) + }, [hasWalletName]) + + const renderPageControl = (): React.ReactNode => ( + + ) + + return ( + + {onboardingScreens.map(screen => { + return + })} + + ) +} + +const SEEDLESS_ONBOARDING_SCREENS = [ + 'termsAndConditions', + 'addRecoveryMethods', + 'analyticsConsent', + 'createPin', + 'setWalletName', + 'selectAvatar', + 'confirmation' +] diff --git a/packages/core-mobile/app/new/routes/seedlessOnboarding/addRecoveryMethods.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/addRecoveryMethods.tsx new file mode 100644 index 0000000000..5405c55022 --- /dev/null +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/addRecoveryMethods.tsx @@ -0,0 +1,70 @@ +import React, { useState } from 'react' +import { useRouter } from 'expo-router' +import AnalyticsService from 'services/analytics/AnalyticsService' +import { + RecoveryMethods, + useAvailableRecoveryMethods +} from 'new/hooks/useAvailableRecoveryMethods' +import { useRecoveryMethodContext } from 'new/contexts/RecoveryMethodProvider' +import { AddRecoveryMethods as Component } from 'new/components/onboarding/AddRecoveryMethods' + +const AddRecoveryMethods = (): JSX.Element => { + const { navigate } = useRouter() + const { oidcAuth } = useRecoveryMethodContext() + const availableRecoveryMethods = useAvailableRecoveryMethods() + + const [selectedMethod, setSelectedMethod] = useState( + availableRecoveryMethods.length > 0 + ? availableRecoveryMethods[0]?.type + : undefined + ) + + const handleOnNext = (): void => { + if (selectedMethod === RecoveryMethods.Passkey) { + // navigate({ + // pathname: './fidoNameInput', + // params: { + // title: 'How would you like to name your passkey?', + // description: 'Add a Passkey name, so it’s easier to find later', + // textInputPlaceholder: 'Passkey name', + // fidoType: FidoType.PASS_KEY + // } + // }) + return + } + if (selectedMethod === RecoveryMethods.Yubikey) { + // navigate({ + // pathname: './fidoNameInput', + // params: { + // title: 'How would you like to name your YubiKey?', + // description: 'Add a YubiKey name, so it’s easier to find later', + // textInputPlaceholder: 'YubiKey name', + // fidoType: FidoType.YUBI_KEY + // } + // }) + return + } + if (selectedMethod === RecoveryMethods.Authenticator) { + navigate('./(totp)/authenticatorSetup') + AnalyticsService.capture('SeedlessAddMfa', { type: 'Authenticator' }) + } + } + + const handleOnSkip = (): void => { + navigate('./analyticsConsent') + } + + return ( + + ) +} + +export default AddRecoveryMethods diff --git a/packages/core-mobile/app/new/routes/seedlessOnboarding/analyticsConsent.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/analyticsConsent.tsx new file mode 100644 index 0000000000..a19b19e309 --- /dev/null +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/analyticsConsent.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { useAnalyticsConsent } from 'hooks/useAnalyticsConsent' +import { useRouter } from 'expo-router' +import { AnalyticsConsent as Component } from 'new/components/onboarding/AnalyticsConsent' + +export default function AnalyticsConsent(): JSX.Element { + const { navigate } = useRouter() + const { accept, reject } = useAnalyticsConsent() + + function handleAcceptAnalytics(): void { + accept() + navigate('./createPin') + } + + function handleRejectAnalytics(): void { + reject() + navigate('./createPin') + } + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/seedlessOnboarding/confirmation.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/confirmation.tsx new file mode 100644 index 0000000000..4f55824f85 --- /dev/null +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/confirmation.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { Confirmation as Component } from 'new/components/onboarding/Confirmation' +import { useWallet } from 'hooks/useWallet' +import Logger from 'utils/Logger' +import { WalletType } from 'services/wallet/types' +import { SEEDLESS_MNEMONIC_STUB } from 'seedless/consts' +import { useLocalSearchParams } from 'expo-router' + +export default function Confirmation(): JSX.Element { + const { login } = useWallet() + const { selectedAvatarId } = useLocalSearchParams<{ + selectedAvatarId: string + }>() + + const handleNext = (): void => { + login(SEEDLESS_MNEMONIC_STUB, WalletType.SEEDLESS).catch(Logger.error) + } + return +} diff --git a/packages/core-mobile/app/new/routes/seedlessOnboarding/createPin.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/createPin.tsx new file mode 100644 index 0000000000..ec6c315ce9 --- /dev/null +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/createPin.tsx @@ -0,0 +1,25 @@ +import React, { useCallback, useState } from 'react' +import { useRouter } from 'expo-router' +import { CreatePin as Component } from 'new/components/onboarding/CreatePin' +import { useOnboardingContext } from 'new/contexts/OnboardingProvider' + +export default function CreatePin(): JSX.Element { + const { hasWalletName } = useOnboardingContext() + const [useBiometrics, setUseBiometrics] = useState(true) + const { navigate } = useRouter() + + const handleEnteredValidPin = useCallback(() => { + if (hasWalletName) { + navigate('./selectAvatar') + } + navigate('./setWalletName') + }, [hasWalletName, navigate]) + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/seedlessOnboarding/selectAvatar.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/selectAvatar.tsx new file mode 100644 index 0000000000..104b979653 --- /dev/null +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/selectAvatar.tsx @@ -0,0 +1,26 @@ +import React, { useState } from 'react' +import { useRouter } from 'expo-router' +import { SelectAvatar as Component } from 'new/components/onboarding/SelectAvatar' +import { AVATARS } from 'new/consts/avatars' + +export default function SelectAvatar(): JSX.Element { + const { navigate } = useRouter() + const [selectedAvatarId, setSelectedAvatarId] = useState( + AVATARS[0]?.id + ) + const handleNext = (): void => { + navigate({ + pathname: './confirmation', + params: { selectedAvatarId } + }) + } + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/seedlessOnboarding/setWalletName.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/setWalletName.tsx new file mode 100644 index 0000000000..4e69d4e734 --- /dev/null +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/setWalletName.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react' +import AnalyticsService from 'services/analytics/AnalyticsService' +import { useDispatch } from 'react-redux' +import { setAccountTitle } from 'store/account' +import { WalletType } from 'services/wallet/types' +import { useRouter } from 'expo-router' +import { SetWalletName as Component } from 'new/components/onboarding/SetWalletName' + +export default function SetWalletName(): JSX.Element { + const [name, setName] = useState('') + const dispatch = useDispatch() + const { navigate } = useRouter() + + const handleNext = (): void => { + AnalyticsService.capture('Onboard:WalletNameSet') + dispatch( + setAccountTitle({ + title: name, + walletType: WalletType.SEEDLESS, + accountIndex: 0 + }) + ) + navigate('./selectAvatar') + } + + return +} diff --git a/packages/core-mobile/app/new/routes/seedlessOnboarding/termsAndConditions.tsx b/packages/core-mobile/app/new/routes/seedlessOnboarding/termsAndConditions.tsx new file mode 100644 index 0000000000..2544b6fda9 --- /dev/null +++ b/packages/core-mobile/app/new/routes/seedlessOnboarding/termsAndConditions.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { TermsAndConditions as Component } from 'new/components/onboarding/TermsAndConditions' +import { useRouter } from 'expo-router' + +export default function TermsAndConditions(): JSX.Element { + const { navigate } = useRouter() + + const handleAgreeAndContinue = (): void => { + navigate('./addRecoveryMethods') + } + + return +} diff --git a/packages/core-mobile/app/new/routes/signup.tsx b/packages/core-mobile/app/new/routes/signup.tsx index 58c4164881..f84e29b624 100644 --- a/packages/core-mobile/app/new/routes/signup.tsx +++ b/packages/core-mobile/app/new/routes/signup.tsx @@ -15,11 +15,11 @@ import { hideLogoModal, showLogoModal } from 'new/components/LogoModal' import { router } from 'expo-router' import AnalyticsService from 'services/analytics/AnalyticsService' import { showSnackbar } from 'new/utils/toast' -import { useSignupContext } from 'new/contexts/SignupProvider' +import { useRecoveryMethodContext } from 'new/contexts/RecoveryMethodProvider' export default function Signup(): JSX.Element { const { theme } = useTheme() - const { setOidcAuth, handleAccountVerified } = useSignupContext() + const { setOidcAuth } = useRecoveryMethodContext() const isSeedlessOnboardingBlocked = useSelector( selectIsSeedlessOnboardingBlocked ) @@ -31,7 +31,7 @@ export default function Signup(): JSX.Element { }, [isRegistering]) const handleSignupWithMnemonic = (): void => { - router.navigate('onboarding') + router.navigate('./mnemonicOnboarding/termsAndConditions') AnalyticsService.capture('RecoveryPhraseClicked') } @@ -45,10 +45,11 @@ export default function Signup(): JSX.Element { mfaId: string }): void => { setOidcAuth(oidcAuth) - router.navigate({ - pathname: './addRecoveryMethods', - params: { allowsUserToAddLater: 'true' } - }) + router.navigate('./seedlessOnboarding/termsAndConditions') + } + + const handleAccountVerified = (): void => { + router.navigate('./seedlessOnboarding/termsAndConditions') } const handleVerifyMfaMethod = ( diff --git a/packages/k2-alpine/package.json b/packages/k2-alpine/package.json index 7f0916453a..4003e7f154 100644 --- a/packages/k2-alpine/package.json +++ b/packages/k2-alpine/package.json @@ -27,7 +27,6 @@ "react": "18.3.1", "react-native": "0.73.7", "react-native-dialog": "9.3.0", - "react-native-gesture-handler": "2.14.1", "react-native-reanimated": "3.6.2", "react-native-reanimated-carousel": "v4.0.0-canary.22", "react-native-svg": "15.7.1" @@ -37,6 +36,7 @@ "expo-image": "1.10.6", "react": "18.3.1", "react-native": "0.73.7", + "react-native-gesture-handler": "2.20.0", "react-native-reanimated": "3.6.2" }, "devDependencies": { diff --git a/packages/k2-alpine/src/components/Avatar/Avatar.stories.tsx b/packages/k2-alpine/src/components/Avatar/Avatar.stories.tsx index e3c9ca8955..da2283e888 100644 --- a/packages/k2-alpine/src/components/Avatar/Avatar.stories.tsx +++ b/packages/k2-alpine/src/components/Avatar/Avatar.stories.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { Switch } from 'react-native' import { ScrollView, Text, View } from '../Primitives' import { useTheme } from '../..' -import AvatarSelector from './AvatarSelector' +import { AvatarSelector } from './AvatarSelector' import { Avatar } from './Avatar' export default { diff --git a/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx b/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx index c8f33f1963..1ae5d1ac58 100644 --- a/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx +++ b/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx @@ -4,7 +4,7 @@ import Carousel from 'react-native-reanimated-carousel' import { Pressable } from '../Primitives' import { Avatar } from './Avatar' -const AvatarSelector = ({ +export const AvatarSelector = ({ avatars, selectedId, onSelect @@ -90,5 +90,3 @@ const configuration = { } const SCREEN_WIDTH = Dimensions.get('window').width - -export default AvatarSelector diff --git a/packages/k2-alpine/src/components/index.ts b/packages/k2-alpine/src/components/index.ts index e68c29d1cd..440122dbbc 100644 --- a/packages/k2-alpine/src/components/index.ts +++ b/packages/k2-alpine/src/components/index.ts @@ -11,3 +11,4 @@ export * from './Avatar/Avatar' export * from './PinInput/PinInput' export * from './PageControl/PageControl' export * from './GroupList/GroupList' +export * from './Avatar/AvatarSelector' diff --git a/yarn.lock b/yarn.lock index baa06b6224..741ece00be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -548,7 +548,6 @@ __metadata: react-dom: 18.3.1 react-native: 0.73.7 react-native-dialog: 9.3.0 - react-native-gesture-handler: 2.14.1 react-native-reanimated: 3.6.2 react-native-reanimated-carousel: v4.0.0-canary.22 react-native-safe-area-context: 4.11.0 @@ -561,6 +560,7 @@ __metadata: expo-image: 1.10.6 react: 18.3.1 react-native: 0.73.7 + react-native-gesture-handler: 2.20.0 react-native-reanimated: 3.6.2 languageName: unknown linkType: soft @@ -26151,22 +26151,6 @@ __metadata: languageName: node linkType: hard -"react-native-gesture-handler@npm:2.14.1": - version: 2.14.1 - resolution: "react-native-gesture-handler@npm:2.14.1" - dependencies: - "@egjs/hammerjs": ^2.0.17 - hoist-non-react-statics: ^3.3.0 - invariant: ^2.2.4 - lodash: ^4.17.21 - prop-types: ^15.7.2 - peerDependencies: - react: "*" - react-native: "*" - checksum: a037e8c5a88a9fc79c283f3064d7653ec8615cb05fc62622eaccb5f3db489ede9c3a0685b7aad210c7efabfd8f5aa34e4f19204318dfda64c8829266d78e0cae - languageName: node - linkType: hard - "react-native-gesture-handler@npm:2.20.0": version: 2.20.0 resolution: "react-native-gesture-handler@npm:2.20.0" @@ -26661,7 +26645,7 @@ react-native-webview@ava-labs/react-native-webview: peerDependencies: react: "*" react-native: "*" - checksum: a187edd718e1ea3a6b1e5da167744e6ee324bc3c3e492bcb0a9d028ab68a82907f053f37c23aa4229d6a9091541cee3c73549c3c850056e4cf5eb5b3cb2c9ffc + checksum: 6e268fad7aa8b8e56fd28cc95f94f35a33fdac4cec0085ae71a766d092760e3f9af35218706113ff7ae99a74baabc5112d32005dce9e66bdf4fda676fad9aa4e languageName: node linkType: hard