Skip to content

Commit

Permalink
onboarding
Browse files Browse the repository at this point in the history
  • Loading branch information
onghwan committed Dec 20, 2024
1 parent bbda469 commit 564ec4b
Show file tree
Hide file tree
Showing 14 changed files with 611 additions and 75 deletions.
29 changes: 29 additions & 0 deletions packages/core-mobile/app/new/components/ScreenHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Text, View } from '@avalabs/k2-alpine'
import React from 'react'

export default function ScreenHeader({
title,
description
}: {
title: string
description?: string | JSX.Element
}): JSX.Element {
return (
<View>
<Text
sx={{ marginRight: 10, marginTop: 8, marginBottom: 10 }}
variant="heading2">
{title}
</Text>
{description !== undefined && (
<View sx={{ marginTop: 8 }}>
{typeof description === 'string' ? (
<Text variant="subtitle1">{description}</Text>
) : (
description
)}
</View>
)}
</View>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { Dispatch } from 'react'
import { Button, Text, View } from '@avalabs/k2-alpine'

type Props = {
title?: string
wordIndex: number
wordOptions: string[]
testID?: string
selectedWordIndex?: number
setSelectedWordIndex: Dispatch<number>
}

export default function WordSelection({
title,
wordOptions,
selectedWordIndex,
setSelectedWordIndex
}: Props): JSX.Element {
const onSelection = (word: string, index: number): void => {
setSelectedWordIndex(index)
}

const [word0, word1, word2] = [
wordOptions[0] ?? '',
wordOptions[1] ?? '',
wordOptions[2] ?? ''
]

return (
<View sx={{ gap: 10 }}>
<Text variant="subtitle1" testID="word_selection__word_number">
{title ?? ' '}
</Text>
<View
sx={{
flexDirection: 'row',
justifyContent: 'space-between',
gap: 8
}}>
<Word
testID="word_selection__word_one"
selected={selectedWordIndex === 0}
word={word0}
onSelected={word => onSelection(word, 0)}
/>
<Word
testID="word_selection__word_two"
selected={selectedWordIndex === 1}
word={word1}
onSelected={word => onSelection(word, 1)}
/>
<Word
testID="word_selection__word_three"
selected={selectedWordIndex === 2}
word={word2}
onSelected={word => onSelection(word, 2)}
/>
</View>
</View>
)
}

function Word({
word,
onSelected,
selected,
testID
}: {
word: string
onSelected: (word: string) => void
selected: boolean
testID?: string
}): JSX.Element {
return (
<View testID={testID} style={{ flexGrow: 1 }}>
<Button
testID="word_selection__select_word_button"
type={selected ? 'primary' : 'secondary'}
size="large"
onPress={() => onSelected(word)}>
{word}
</Button>
</View>
)
}
121 changes: 121 additions & 0 deletions packages/core-mobile/app/new/hooks/useCheckMnemonic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useEffect, useState } from 'react'

type WordSelection = {
index: number
wordOptions: string[]
}

export type UseCheckMnemonicData = {
firstWordSelection: WordSelection
secondWordSelection: WordSelection
thirdWordSelection: WordSelection
verify: (
first: string | undefined,
second: string | undefined,
third: string | undefined
) => boolean
}

export function useCheckMnemonic(mnemonics: string[]): UseCheckMnemonicData {
const [firstWordSelection, setFirstWordSelection] = useState<WordSelection>({
index: 0,
wordOptions: []
})
const [secondWordSelection, setSecondWordSelection] = useState<WordSelection>(
{
index: 0,
wordOptions: []
}
)
const [thirdWordSelection, setThirdWordSelection] = useState<WordSelection>({
index: 0,
wordOptions: []
})

useEffect(() => {
const pool = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23
]
const indices = selectXRandNumbers(3, pool)

const firstWordOptions = selectXRandWordsIncluding({
included: indices[0] as number,
numOfNumbers: 3,
pool,
mnemonics
})

const secondWordOptions = selectXRandWordsIncluding({
included: indices[1] as number,
numOfNumbers: 3,
pool,
mnemonics
})

const thirdWordOptions = selectXRandWordsIncluding({
included: indices[2] as number,
numOfNumbers: 3,
pool,
mnemonics
})

setFirstWordSelection({
index: indices[0] as number,
wordOptions: firstWordOptions
})
setSecondWordSelection({
index: indices[1] as number,
wordOptions: secondWordOptions
})
setThirdWordSelection({
index: indices[2] as number,
wordOptions: thirdWordOptions
})
}, [mnemonics])

const verify = (
first: string | undefined,
second: string | undefined,
third: string | undefined
): boolean => {
return (
mnemonics[firstWordSelection.index] === first &&
mnemonics[secondWordSelection.index] === second &&
mnemonics[thirdWordSelection.index] === third
)
}

return <UseCheckMnemonicData>{
firstWordSelection,
secondWordSelection,
thirdWordSelection,
verify
}
}

function selectXRandNumbers(numOfNumbers: number, pool: number[]): number[] {
for (let i = 0; i < numOfNumbers; i++) {
const randomIndex = Math.floor(Math.random() * (pool.length - i)) + i //pick random from pool
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
;[pool[i], pool[randomIndex]] = [pool[randomIndex]!, pool[i]!] //put it on front of pool
}
return pool.splice(0, numOfNumbers) //first [numOfNumbers] nums are now non-repeating random from pool and won't be duplicated in UI causing test failures
}

function selectXRandWordsIncluding({
included,
numOfNumbers,
pool,
mnemonics
}: {
included: number
numOfNumbers: number
pool: number[]
mnemonics: string[]
}): string[] {
const randoms = selectXRandNumbers(numOfNumbers, pool)
const indexToRandomInject = Math.floor(Math.random() * numOfNumbers)
randoms[indexToRandomInject] = included
return randoms.map(wordIndex => mnemonics[wordIndex] as string)
}
14 changes: 1 addition & 13 deletions packages/core-mobile/app/new/hooks/useCreatePin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useState } from 'react'

export type UseCreatePinProps = {
title: string
onEnterChosenPin: (pinKey: string) => void
onEnterConfirmedPin: (pinKey: string) => void
chosenPinEntered: boolean
Expand All @@ -11,27 +10,16 @@ export type UseCreatePinProps = {
}

export function useCreatePin({
isResettingPin = false,
onError
}: {
isResettingPin?: boolean
onError: () => Promise<void>
}): UseCreatePinProps {
const [title, setTitle] = useState('Create Pin')
const [chosenPin, setChosenPin] = useState('')
const [confirmedPin, setConfirmedPin] = useState('')
const [chosenPinEntered, setChosenPinEntered] = useState(false)
const [confirmedPinEntered, setConfirmedPinEntered] = useState(false)
const [validPin, setValidPin] = useState<string | undefined>(undefined)

useEffect(() => {
if (isResettingPin) {
setTitle(chosenPinEntered ? 'Confirm new Pin' : 'Create new Pin')
} else {
setTitle(chosenPinEntered ? 'Confirm Pin' : 'Create Pin')
}
}, [chosenPinEntered, isResettingPin])

function resetConfirmPinProcess(): void {
setValidPin(undefined)
setConfirmedPinEntered(false)
Expand All @@ -58,6 +46,7 @@ export function useCreatePin({
if (chosenPin.length === 6) {
return
}

setChosenPin(pinValue)
if (pinValue.length === 6) {
// setTimeout(() => {
Expand All @@ -77,7 +66,6 @@ export function useCreatePin({
}

return {
title,
onEnterChosenPin,
onEnterConfirmedPin,
chosenPinEntered,
Expand Down
1 change: 1 addition & 0 deletions packages/core-mobile/app/new/routes/onboarding/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default function OnboardingLayout(): JSX.Element {
<Stack.Screen name="index" />
<Stack.Screen name="analyticsConsent" />
<Stack.Screen name="recoveryPhrase" />
<Stack.Screen name="verifyRecoveryPhrase" />
<Stack.Screen name="createPin" />
</Stack>
)
Expand Down
41 changes: 21 additions & 20 deletions packages/core-mobile/app/new/routes/onboarding/analyticsConsent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
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()
Expand Down Expand Up @@ -40,26 +41,26 @@ export default function AnalyticsConsent(): JSX.Element {
<BlurredBarsContentLayout>
<SafeAreaView sx={{ flex: 1 }}>
<ScrollView sx={{ flex: 1 }} contentContainerSx={{ padding: 16 }}>
<Text
sx={{ marginRight: 10, marginTop: 8, marginBottom: 10 }}
variant="heading2">
Unlock airdrops
</Text>
<View sx={{ marginTop: 8, gap: 20 }}>
<Text testID="anlaysticsContent" variant="subtitle1">
As a Core user, you have the option to opt-in for{' '}
<Text variant="body1" sx={{ fontWeight: '700' }}>
airdrop rewards
</Text>{' '}
based on your activity and engagement. Core will collect anonymous
interaction data to power this feature.
</Text>
<Text testID="anlaysticsContent" variant="subtitle1">
Core is committed to protecting your privacy. We will never sell
or share your data. If you wish, you can disable this at any time
in the settings menu.
</Text>
</View>
<ScreenHeader
title="Unlock airdrops"
description={
<View sx={{ gap: 20 }}>
<Text testID="anlaysticsContent" variant="subtitle1">
As a Core user, you have the option to opt-in for{' '}
<Text variant="body1" sx={{ fontWeight: '700' }}>
airdrop rewards
</Text>{' '}
based on your activity and engagement. Core will collect
anonymous interaction data to power this feature.
</Text>
<Text testID="anlaysticsContent" variant="subtitle1">
Core is committed to protecting your privacy. We will never
sell or share your data. If you wish, you can disable this at
any time in the settings menu.
</Text>
</View>
}
/>
</ScrollView>
<View
sx={{
Expand Down
Loading

0 comments on commit 564ec4b

Please sign in to comment.