Merge branch 'main' into hn.UI-feedback-fixes
This commit is contained in:
commit
01020dd26b
|
@ -17,19 +17,5 @@ export default meta
|
||||||
type Story = StoryObj<typeof meta>
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {},
|
||||||
continueHandler: () => {},
|
|
||||||
activeStep: 0,
|
|
||||||
isConfirmPhraseStage: false,
|
|
||||||
subStepValidatorSetup: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Disabled: Story = {
|
|
||||||
args: {
|
|
||||||
continueHandler: () => {},
|
|
||||||
activeStep: 0,
|
|
||||||
isConfirmPhraseStage: true,
|
|
||||||
subStepValidatorSetup: 0,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,84 +1,125 @@
|
||||||
import { Stack, YStack } from 'tamagui'
|
import { Stack, XStack } from 'tamagui'
|
||||||
import { Button, InformationBox } from '@status-im/components'
|
import { Button, InformationBox } from '@status-im/components'
|
||||||
import { CloseCircleIcon } from '@status-im/icons'
|
import { CloseCircleIcon } from '@status-im/icons'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { RootState } from '../../redux/store'
|
import { RootState } from '../../redux/store'
|
||||||
import LinkWithArrow from '../../components/General/LinkWithArrow'
|
import LinkWithArrow from '../../components/General/LinkWithArrow'
|
||||||
|
import { setActiveStep, setSubStepValidatorSetup } from '../../redux/ValidatorOnboarding/slice'
|
||||||
|
import { KEYSTORE_FILES } from '../../constants'
|
||||||
|
import {
|
||||||
|
setIsConfirmPhraseStage,
|
||||||
|
setIsCopyPastedPhrase,
|
||||||
|
setValidWords,
|
||||||
|
} from '../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||||
|
|
||||||
type ContinueButton = {
|
const ContinueButton = () => {
|
||||||
continueHandler: () => void
|
const [isDisabled, setIsDisabled] = useState(false)
|
||||||
activeStep: number
|
const {
|
||||||
isConfirmPhraseStage: boolean
|
isCopyPastedPhrase,
|
||||||
subStepValidatorSetup: number
|
mnemonic,
|
||||||
isValidatorSet?: boolean
|
validWords,
|
||||||
}
|
isConfirmPhraseStage,
|
||||||
|
recoveryMechanism,
|
||||||
const ContinueButton = ({
|
generatedMnemonic,
|
||||||
continueHandler,
|
} = useSelector((state: RootState) => state.keyGeneration)
|
||||||
activeStep,
|
const { activeStep, subStepValidatorSetup } = useSelector(
|
||||||
isConfirmPhraseStage,
|
(state: RootState) => state.validatorOnboarding,
|
||||||
subStepValidatorSetup,
|
|
||||||
isValidatorSet,
|
|
||||||
}: ContinueButton) => {
|
|
||||||
const { isCopyPastedPhrase, words, validWords } = useSelector(
|
|
||||||
(state: RootState) => state.keyGeneration,
|
|
||||||
)
|
)
|
||||||
const { isWalletConnected } = useSelector((state: RootState) => state.deposit)
|
const dispatch = useDispatch()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const isActivationValScreen = activeStep === 3 && subStepValidatorSetup === 3
|
||||||
|
|
||||||
const isActivationValidatorScreen = activeStep === 3 && subStepValidatorSetup === 3
|
useEffect(() => {
|
||||||
|
const getDisabledButton = () => {
|
||||||
|
if (activeStep === 4 && isConfirmPhraseStage) {
|
||||||
|
if (validWords.some(w => w === false)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const isDisabled = () => {
|
setIsDisabled(getDisabledButton())
|
||||||
const isDepositWalletConnected = isWalletConnected === false && activeStep === 5
|
}, [activeStep, subStepValidatorSetup, isConfirmPhraseStage, mnemonic, validWords])
|
||||||
let isEmptyPhrase = false
|
|
||||||
let isNotValidWords = false
|
const handleStep3 = () => {
|
||||||
|
subStepValidatorSetup < 3
|
||||||
|
? dispatch(setSubStepValidatorSetup(subStepValidatorSetup + 1))
|
||||||
|
: dispatch(setSubStepValidatorSetup(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStep4 = () => {
|
||||||
|
if (!isConfirmPhraseStage && recoveryMechanism === KEYSTORE_FILES) {
|
||||||
|
return dispatch(setActiveStep(activeStep + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isConfirmPhraseStage) {
|
||||||
|
return dispatch(setIsConfirmPhraseStage(true))
|
||||||
|
}
|
||||||
|
|
||||||
if (isConfirmPhraseStage) {
|
if (isConfirmPhraseStage) {
|
||||||
isEmptyPhrase = words.some(word => word === '')
|
const newValidWords = mnemonic.map((w, index) => generatedMnemonic[index] === w)
|
||||||
isNotValidWords = validWords.every(word => word === false)
|
dispatch(setValidWords(newValidWords))
|
||||||
}
|
|
||||||
|
|
||||||
if (isEmptyPhrase || isNotValidWords || isDepositWalletConnected) {
|
if (!newValidWords.includes(false)) {
|
||||||
return true
|
setActiveStep(activeStep + 1)
|
||||||
|
dispatch(setIsConfirmPhraseStage(false))
|
||||||
|
if (isCopyPastedPhrase) {
|
||||||
|
dispatch(setIsCopyPastedPhrase(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const continueHandler = () => {
|
||||||
|
if (activeStep === 3) {
|
||||||
|
handleStep3()
|
||||||
|
} else if (activeStep === 4) {
|
||||||
|
handleStep4()
|
||||||
|
} else {
|
||||||
|
if (activeStep < 5) {
|
||||||
|
setActiveStep(activeStep + 1)
|
||||||
|
} else {
|
||||||
|
navigate('/')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<YStack style={{ width: '100%', alignItems: 'center', zIndex: 999, marginTop: '30px' }}>
|
<XStack
|
||||||
<Stack style={{ width: '100%' }}>
|
style={{
|
||||||
{isCopyPastedPhrase && (
|
width: '100%',
|
||||||
|
justifyContent: isActivationValScreen ? 'space-between' : 'end',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 1000,
|
||||||
|
marginTop: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isCopyPastedPhrase && (
|
||||||
|
<Stack style={{ width: '100%', position: 'absolute' }}>
|
||||||
<InformationBox
|
<InformationBox
|
||||||
message="You have copy and pasted the entire Recovery Phrase. Please ensure you have secured it appropriately prior to continuing."
|
message="You have copy and pasted the entire Recovery Phrase. Please ensure you have secured it appropriately prior to continuing."
|
||||||
variant="error"
|
variant="error"
|
||||||
icon={<CloseCircleIcon size={20} />}
|
icon={<CloseCircleIcon size={20} />}
|
||||||
/>
|
/>
|
||||||
)}
|
</Stack>
|
||||||
{isActivationValidatorScreen && (
|
)}
|
||||||
<LinkWithArrow
|
{isActivationValScreen && (
|
||||||
text="Skip to Dashboard"
|
<LinkWithArrow
|
||||||
to="/"
|
text="Skip to Dashboard"
|
||||||
arrowRight={true}
|
to="/dashboard"
|
||||||
style={{ fontWeight: 'bold', zIndex: 1000 }}
|
arrowRight={true}
|
||||||
/>
|
style={{ fontWeight: 'bold', zIndex: 999 }}
|
||||||
)}
|
/>
|
||||||
</Stack>
|
)}
|
||||||
<Stack
|
<Button onPress={continueHandler} size={40} disabled={isDisabled || (isValidatorSet === false && activeStep === 3)}>
|
||||||
style={{
|
{activeStep < 5 ? 'Continue' : 'Continue to Dashboard'}
|
||||||
width: '100%',
|
</Button>
|
||||||
zIndex: 999,
|
</XStack>
|
||||||
alignItems: 'end',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onPress={continueHandler}
|
|
||||||
size={40}
|
|
||||||
disabled={isDisabled() || (isValidatorSet === false && activeStep === 3)}
|
|
||||||
>
|
|
||||||
{activeStep < 6 ? 'Continue' : 'Continue to Dashboard'}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</YStack>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,13 @@ const meta = {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
activeStep: {
|
||||||
|
options: [0, 1, 2, 3, 4, 5],
|
||||||
|
control: { type: 'radio' },
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
} satisfies Meta<typeof FormStepper>
|
} satisfies Meta<typeof FormStepper>
|
||||||
|
|
||||||
export default meta
|
export default meta
|
||||||
|
@ -17,48 +24,41 @@ type Story = StoryObj<typeof meta>
|
||||||
export const OverviewActive: Story = {
|
export const OverviewActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeStep: 0,
|
activeStep: 0,
|
||||||
changeActiveStep: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdvisoriesActive: Story = {
|
export const AdvisoriesActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeStep: 1,
|
activeStep: 1,
|
||||||
changeActiveStep: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientSetupActive: Story = {
|
export const ClientSetupActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeStep: 2,
|
activeStep: 2,
|
||||||
changeActiveStep: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ValidatorSetupActive: Story = {
|
export const ValidatorSetupActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeStep: 3,
|
activeStep: 3,
|
||||||
changeActiveStep: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KeyGenerationActive: Story = {
|
export const KeyGenerationActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeStep: 4,
|
activeStep: 4,
|
||||||
changeActiveStep: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActivationActive: Story = {
|
export const ActivationActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeStep: 5,
|
activeStep: 5,
|
||||||
changeActiveStep: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NoActiveStep: Story = {
|
export const NoActiveStep: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeStep: -1,
|
activeStep: -1,
|
||||||
changeActiveStep: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { Stepper, Step } from 'react-form-stepper'
|
import { Stepper, Step } from 'react-form-stepper'
|
||||||
|
|
||||||
import './FormStepper.css'
|
import './FormStepper.css'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
|
||||||
type FormStepperProps = {
|
import { setActiveStep } from '../../../redux/ValidatorOnboarding/slice'
|
||||||
activeStep: number
|
import './FormStepper.css'
|
||||||
changeActiveStep: (step: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{ label: 'Overview', subtitle: 'Get Started' },
|
{ label: 'Overview', subtitle: 'Get Started' },
|
||||||
|
@ -17,7 +16,13 @@ const steps = [
|
||||||
{ label: 'Activation', subtitle: 'Complete Setup' },
|
{ label: 'Activation', subtitle: 'Complete Setup' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const FormStepper = ({ activeStep, changeActiveStep }: FormStepperProps) => {
|
type FormStepperProps = {
|
||||||
|
activeStep: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormStepper = ({ activeStep }: FormStepperProps) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stepper
|
<Stepper
|
||||||
activeStep={activeStep}
|
activeStep={activeStep}
|
||||||
|
@ -37,7 +42,7 @@ const FormStepper = ({ activeStep, changeActiveStep }: FormStepperProps) => {
|
||||||
key={index}
|
key={index}
|
||||||
label={step.label}
|
label={step.label}
|
||||||
className="custom-step"
|
className="custom-step"
|
||||||
onClick={() => changeActiveStep(index)}
|
onClick={() => dispatch(setActiveStep(index))}
|
||||||
completed={activeStep > index - 1}
|
completed={activeStep > index - 1}
|
||||||
data-subtitle={step.subtitle}
|
data-subtitle={step.subtitle}
|
||||||
data-step={step.label}
|
data-step={step.label}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import wordlist from 'web-bip39/wordlists/english'
|
||||||
import { RootState } from '../../../../redux/store'
|
import { RootState } from '../../../../redux/store'
|
||||||
import {
|
import {
|
||||||
setIsCopyPastedPhrase,
|
setIsCopyPastedPhrase,
|
||||||
setMnemonic,
|
|
||||||
setValidWords,
|
setValidWords,
|
||||||
setWord,
|
setWord,
|
||||||
} from '../../../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
} from '../../../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||||
|
@ -18,9 +17,9 @@ type AutocompleteInputProps = {
|
||||||
const AutocompleteInput = ({ index }: AutocompleteInputProps) => {
|
const AutocompleteInput = ({ index }: AutocompleteInputProps) => {
|
||||||
const [suggestions, setSuggestions] = useState<string[]>([])
|
const [suggestions, setSuggestions] = useState<string[]>([])
|
||||||
const [isFocused, setIsFocused] = useState(false)
|
const [isFocused, setIsFocused] = useState(false)
|
||||||
const word = useSelector((state: RootState) => state.keyGeneration.words[index])
|
const word = useSelector((state: RootState) => state.keyGeneration.mnemonic[index])
|
||||||
const isValidWord = useSelector((state: RootState) => state.keyGeneration.validWords[index])
|
const isValidWord = useSelector((state: RootState) => state.keyGeneration.validWords[index])
|
||||||
const validWords = useSelector((state: RootState) => state.keyGeneration.validWords)
|
const { validWords, generatedMnemonic } = useSelector((state: RootState) => state.keyGeneration)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -32,39 +31,47 @@ const AutocompleteInput = ({ index }: AutocompleteInputProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = e.target
|
||||||
|
const newMnemonic = value.trim().split(' ')
|
||||||
|
const newMnemonicLength = newMnemonic.length
|
||||||
|
let newValidWords = [...validWords]
|
||||||
|
|
||||||
if (!isFocused) {
|
if (!isFocused) {
|
||||||
handleInputFocus()
|
handleInputFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = e.target.value
|
switch (newMnemonicLength) {
|
||||||
const mnemonic = value.trim().split(' ').slice(0, 24)
|
case 1:
|
||||||
const mnemonicLength = mnemonic.length
|
updateWord(index, value, newValidWords)
|
||||||
let newValidWords = [...validWords]
|
break
|
||||||
|
case 24:
|
||||||
if (mnemonicLength === 1) {
|
dispatch(setIsCopyPastedPhrase(true))
|
||||||
dispatch(setWord({ index, word: value }))
|
updateMultipleWords(newMnemonic, newValidWords)
|
||||||
|
break
|
||||||
newValidWords[index] = wordlist.includes(value) || getNewSuggestions(value).length > 0
|
default:
|
||||||
} else if (mnemonicLength === 24) {
|
const endIndex = Math.min(newMnemonicLength + index, 24)
|
||||||
dispatch(setMnemonic(mnemonic))
|
const partialMnemonic = newMnemonic.slice(0, endIndex - index)
|
||||||
dispatch(setIsCopyPastedPhrase(true))
|
dispatch(setIsCopyPastedPhrase(true))
|
||||||
|
updateMultipleWords(partialMnemonic, newValidWords, index)
|
||||||
mnemonic.forEach((m, i) => {
|
break
|
||||||
newValidWords[i] = wordlist.includes(m)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for (let i = index; i < mnemonicLength + index; i++) {
|
|
||||||
const mnemonicWord = mnemonic.shift() || ''
|
|
||||||
dispatch(setWord({ index: i, word: mnemonicWord }))
|
|
||||||
newValidWords[i] = wordlist.includes(mnemonicWord)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(setIsCopyPastedPhrase(true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setValidWords(newValidWords))
|
dispatch(setValidWords(newValidWords))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateWord = (idx: number, word: string, validWords: boolean[]) => {
|
||||||
|
dispatch(setWord({ index: idx, word }))
|
||||||
|
validWords[idx] = generatedMnemonic[idx] === word || generatedMnemonic[idx].startsWith(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMultipleWords = (words: string[], validWords: boolean[], startIndex: number = 0) => {
|
||||||
|
words.forEach((word, idx) => {
|
||||||
|
const actualIdx = startIndex + idx
|
||||||
|
dispatch(setWord({ index: actualIdx, word }))
|
||||||
|
validWords[actualIdx] = generatedMnemonic[actualIdx] === word
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleSuggestionClick = (e: React.MouseEvent, suggestion: string) => {
|
const handleSuggestionClick = (e: React.MouseEvent, suggestion: string) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
|
@ -72,7 +79,7 @@ const AutocompleteInput = ({ index }: AutocompleteInputProps) => {
|
||||||
dispatch(setWord({ index, word: suggestion }))
|
dispatch(setWord({ index, word: suggestion }))
|
||||||
|
|
||||||
let newValidWords = [...validWords]
|
let newValidWords = [...validWords]
|
||||||
newValidWords[index] = wordlist.includes(suggestion)
|
newValidWords[index] = generatedMnemonic[index] === suggestion
|
||||||
dispatch(setValidWords(newValidWords))
|
dispatch(setValidWords(newValidWords))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +91,7 @@ const AutocompleteInput = ({ index }: AutocompleteInputProps) => {
|
||||||
setIsFocused(false)
|
setIsFocused(false)
|
||||||
|
|
||||||
let newValidWords = [...validWords]
|
let newValidWords = [...validWords]
|
||||||
newValidWords[index] = wordlist.includes(word)
|
newValidWords[index] = generatedMnemonic[index] === word
|
||||||
dispatch(setValidWords(newValidWords))
|
dispatch(setValidWords(newValidWords))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import { Stack, YStack } from 'tamagui'
|
import { Stack, YStack } from 'tamagui'
|
||||||
import { Text } from '@status-im/components'
|
import { Text } from '@status-im/components'
|
||||||
import { useState } from 'react'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import KeyGenerationHeader from './KeyGenerationHeader/KeyGenerationHeader'
|
import KeyGenerationHeader from './KeyGenerationHeader/KeyGenerationHeader'
|
||||||
import RecoveryMechanism from './RecoveryMechanism/RecoveryMechanism'
|
import RecoveryMechanism from './RecoveryMechanism/RecoveryMechanism'
|
||||||
import KeystoreFiles from './KeystoreFiles'
|
import KeystoreFiles from './KeystoreFiles'
|
||||||
import RecoveryPhrase from './RecoveryPhrase'
|
import RecoveryPhrase from './RecoveryPhrase'
|
||||||
import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../constants'
|
|
||||||
import ConfirmRecoveryPhrase from './ConfirmRecoveryPhrase/ConfirmRecoveryPhrase'
|
import ConfirmRecoveryPhrase from './ConfirmRecoveryPhrase/ConfirmRecoveryPhrase'
|
||||||
|
import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../constants'
|
||||||
|
import { RootState } from '../../../redux/store'
|
||||||
|
|
||||||
type KeyGenerationProps = {
|
type KeyGenerationProps = {
|
||||||
isConfirmPhraseStage: boolean
|
isConfirmPhraseStage: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeyGeneration = ({ isConfirmPhraseStage }: KeyGenerationProps) => {
|
const KeyGeneration = ({ isConfirmPhraseStage }: KeyGenerationProps) => {
|
||||||
const [recoveryMechanism, setRecoveryMechanism] = useState(KEYSTORE_FILES)
|
const { recoveryMechanism } = useSelector((state: RootState) => state.keyGeneration)
|
||||||
|
|
||||||
const isKeystoreFiles =
|
const isKeystoreFiles =
|
||||||
recoveryMechanism === KEYSTORE_FILES || recoveryMechanism === BOTH_KEY_AND_RECOVERY
|
recoveryMechanism === KEYSTORE_FILES || recoveryMechanism === BOTH_KEY_AND_RECOVERY
|
||||||
|
@ -22,20 +23,13 @@ const KeyGeneration = ({ isConfirmPhraseStage }: KeyGenerationProps) => {
|
||||||
const isRecoveryPhrase =
|
const isRecoveryPhrase =
|
||||||
recoveryMechanism === RECOVERY_PHRASE || recoveryMechanism === BOTH_KEY_AND_RECOVERY
|
recoveryMechanism === RECOVERY_PHRASE || recoveryMechanism === BOTH_KEY_AND_RECOVERY
|
||||||
|
|
||||||
const handleRecMechanismChange = (value: string) => {
|
|
||||||
setRecoveryMechanism(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<YStack space={'$2'} style={{ width: '100%', padding: '16px 32px', alignItems: 'start' }}>
|
<YStack space={'$2'} style={{ width: '100%', padding: '16px 32px', alignItems: 'start' }}>
|
||||||
{isConfirmPhraseStage && <ConfirmRecoveryPhrase />}
|
{isConfirmPhraseStage && <ConfirmRecoveryPhrase />}
|
||||||
{isConfirmPhraseStage === false && (
|
{isConfirmPhraseStage === false && (
|
||||||
<>
|
<>
|
||||||
<KeyGenerationHeader />
|
<KeyGenerationHeader />
|
||||||
<RecoveryMechanism
|
<RecoveryMechanism recoveryMechanism={recoveryMechanism} />
|
||||||
recoveryMechanism={recoveryMechanism}
|
|
||||||
handleRecMechanismChange={handleRecMechanismChange}
|
|
||||||
/>
|
|
||||||
<Stack style={{ margin: '30px 0' }}>
|
<Stack style={{ margin: '30px 0' }}>
|
||||||
<Text size={27} weight={'semibold'}>
|
<Text size={27} weight={'semibold'}>
|
||||||
4 Validators
|
4 Validators
|
||||||
|
|
|
@ -5,7 +5,7 @@ import KeyGenerationTitle from '../KeyGenerationTitle'
|
||||||
|
|
||||||
const KeyGenerationHeader = () => {
|
const KeyGenerationHeader = () => {
|
||||||
return (
|
return (
|
||||||
<XStack style={{ width: '100%', alignItems: 'center', justifyContent: 'space-between' }}>
|
<XStack style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||||
<KeyGenerationTitle />
|
<KeyGenerationTitle />
|
||||||
<XStack space={'$2'}>
|
<XStack space={'$2'}>
|
||||||
<KeyGenerationSyncCard
|
<KeyGenerationSyncCard
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Stack, XStack, YStack } from 'tamagui'
|
import { Stack, XStack, YStack } from 'tamagui'
|
||||||
import { ClearIcon } from '@status-im/icons'
|
import { ClearIcon } from '@status-im/icons'
|
||||||
import { Text } from '@status-im/components'
|
import { Text } from '@status-im/components'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
import StandardGauge from '../../../../components/Charts/StandardGauge'
|
import StandardGauge from '../../../../components/Charts/StandardGauge'
|
||||||
import BorderBox from '../../../../components/General/BorderBox'
|
import BorderBox from '../../../../components/General/BorderBox'
|
||||||
|
@ -14,6 +15,16 @@ type KeyGenerationSyncCardProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeyGenerationSyncCard = ({ synced, total, title, color }: KeyGenerationSyncCardProps) => {
|
const KeyGenerationSyncCard = ({ synced, total, title, color }: KeyGenerationSyncCardProps) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(true)
|
||||||
|
|
||||||
|
const closeCardHanlder = () => {
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOpen === false) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BorderBox style={{ borderRadius: '10.1px', borderWidth: '0.5px' }}>
|
<BorderBox style={{ borderRadius: '10.1px', borderWidth: '0.5px' }}>
|
||||||
<XStack space={'$2'} alignItems="center">
|
<XStack space={'$2'} alignItems="center">
|
||||||
|
@ -48,7 +59,12 @@ const KeyGenerationSyncCard = ({ synced, total, title, color }: KeyGenerationSyn
|
||||||
{formatNumberForGauge(synced)} / {formatNumberForGauge(total)}
|
{formatNumberForGauge(synced)} / {formatNumberForGauge(total)}
|
||||||
</Text>
|
</Text>
|
||||||
</YStack>
|
</YStack>
|
||||||
<ClearIcon size={20} color="#A1ABBD" />
|
<ClearIcon
|
||||||
|
size={20}
|
||||||
|
color="#A1ABBD"
|
||||||
|
onClick={closeCardHanlder}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
</XStack>
|
</XStack>
|
||||||
</BorderBox>
|
</BorderBox>
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,26 +6,49 @@ import { useState } from 'react'
|
||||||
const KeystoreFiles = () => {
|
const KeystoreFiles = () => {
|
||||||
const [encryptedPassword, setEncryptedPassword] = useState('')
|
const [encryptedPassword, setEncryptedPassword] = useState('')
|
||||||
const [confirmEncryptedPassword, setConfirmEncryptedPassword] = useState('')
|
const [confirmEncryptedPassword, setConfirmEncryptedPassword] = useState('')
|
||||||
|
const [encryptedPasswordError, setEncryptedPasswordError] = useState(false)
|
||||||
|
const [confirmEncryptedPasswordError, setConfirmEncryptedPasswordError] = useState(false)
|
||||||
|
const [displayEncryptedPassword, setDisplayEncryptedPassword] = useState('')
|
||||||
|
const [displayConfirmEncryptedPassword, setDisplayConfirmEncryptedPassword] = useState('')
|
||||||
|
|
||||||
const generateKeystoreFilesHandler = () => {}
|
const generateKeystoreFilesHandler = () => {
|
||||||
|
if (
|
||||||
|
encryptedPassword !== confirmEncryptedPassword ||
|
||||||
|
encryptedPassword === '' ||
|
||||||
|
confirmEncryptedPassword === ''
|
||||||
|
) {
|
||||||
|
setEncryptedPasswordError(true)
|
||||||
|
return setConfirmEncryptedPasswordError(true)
|
||||||
|
}
|
||||||
|
|
||||||
const changeEncryptedPasswordHandler = (value: string) => {
|
setEncryptedPasswordError(false)
|
||||||
setEncryptedPassword(value)
|
setConfirmEncryptedPasswordError(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeConfirmEncryptedPasswordHandler = (value: string) => {
|
const changeEncryptedPasswordHandler = (e: any) => {
|
||||||
setConfirmEncryptedPassword(value)
|
const password = e.target.value
|
||||||
|
setEncryptedPassword(password)
|
||||||
|
setDisplayEncryptedPassword(getHidedPassword(password.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearEncryptedPasswordHandler = () => {
|
const changeConfirmEncryptedPasswordHandler = (e: any) => {
|
||||||
setEncryptedPassword('')
|
const password = e.target.value
|
||||||
|
setConfirmEncryptedPassword(password)
|
||||||
|
setDisplayConfirmEncryptedPassword(getHidedPassword(password.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearConfirmEncryptedPasswordHandler = () => {
|
const downloadKeyFilesHandler = () => {
|
||||||
setConfirmEncryptedPassword('')
|
const element = document.createElement('a')
|
||||||
|
const file = new Blob([''], { type: 'text/plain' })
|
||||||
|
element.href = URL.createObjectURL(file)
|
||||||
|
element.download = 'keystore_files.txt'
|
||||||
|
document.body.appendChild(element)
|
||||||
|
element.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadKeyFilesHandler = () => {}
|
const getHidedPassword = (passwordLength: number) => {
|
||||||
|
return '*'.repeat(passwordLength)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<YStack space={'$4'}>
|
<YStack space={'$4'}>
|
||||||
|
@ -42,11 +65,12 @@ const KeystoreFiles = () => {
|
||||||
size={16}
|
size={16}
|
||||||
color="#A1ABBD"
|
color="#A1ABBD"
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={clearEncryptedPasswordHandler}
|
onClick={() => setEncryptedPassword('')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
value={encryptedPassword}
|
error={encryptedPasswordError}
|
||||||
onChangeText={changeEncryptedPasswordHandler}
|
value={displayEncryptedPassword}
|
||||||
|
onChange={changeEncryptedPasswordHandler}
|
||||||
/>
|
/>
|
||||||
</YStack>
|
</YStack>
|
||||||
<YStack space={'$2'}>
|
<YStack space={'$2'}>
|
||||||
|
@ -60,11 +84,12 @@ const KeystoreFiles = () => {
|
||||||
size={16}
|
size={16}
|
||||||
color="#A1ABBD"
|
color="#A1ABBD"
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={clearConfirmEncryptedPasswordHandler}
|
onClick={() => setConfirmEncryptedPassword('')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
value={confirmEncryptedPassword}
|
error={confirmEncryptedPasswordError}
|
||||||
onChangeText={changeConfirmEncryptedPasswordHandler}
|
value={displayConfirmEncryptedPassword}
|
||||||
|
onChange={changeConfirmEncryptedPasswordHandler}
|
||||||
/>
|
/>
|
||||||
</YStack>
|
</YStack>
|
||||||
</YStack>
|
</YStack>
|
||||||
|
|
|
@ -10,6 +10,13 @@ const meta = {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
recoveryMechanism: {
|
||||||
|
options: [RECOVERY_PHRASE, KEYSTORE_FILES, BOTH_KEY_AND_RECOVERY],
|
||||||
|
control: { type: 'radio' },
|
||||||
|
defaultValue: KEYSTORE_FILES,
|
||||||
|
},
|
||||||
|
},
|
||||||
} satisfies Meta<typeof RecoveryMechanism>
|
} satisfies Meta<typeof RecoveryMechanism>
|
||||||
|
|
||||||
export default meta
|
export default meta
|
||||||
|
@ -18,27 +25,23 @@ type Story = StoryObj<typeof meta>
|
||||||
export const KeystoreFiles: Story = {
|
export const KeystoreFiles: Story = {
|
||||||
args: {
|
args: {
|
||||||
recoveryMechanism: KEYSTORE_FILES,
|
recoveryMechanism: KEYSTORE_FILES,
|
||||||
handleRecMechanismChange: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecoveryPhrase: Story = {
|
export const RecoveryPhrase: Story = {
|
||||||
args: {
|
args: {
|
||||||
recoveryMechanism: RECOVERY_PHRASE,
|
recoveryMechanism: RECOVERY_PHRASE,
|
||||||
handleRecMechanismChange: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BothKeystoreAndRecovery: Story = {
|
export const BothKeystoreAndRecovery: Story = {
|
||||||
args: {
|
args: {
|
||||||
recoveryMechanism: BOTH_KEY_AND_RECOVERY,
|
recoveryMechanism: BOTH_KEY_AND_RECOVERY,
|
||||||
handleRecMechanismChange: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithoutRecMechanism: Story = {
|
export const WithoutRecMechanism: Story = {
|
||||||
args: {
|
args: {
|
||||||
recoveryMechanism: '',
|
recoveryMechanism: '',
|
||||||
handleRecMechanismChange: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -6,15 +6,11 @@ import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../..
|
||||||
|
|
||||||
type RecoveryMechanismProps = {
|
type RecoveryMechanismProps = {
|
||||||
recoveryMechanism: string
|
recoveryMechanism: string
|
||||||
handleRecMechanismChange: (value: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cards = [RECOVERY_PHRASE, KEYSTORE_FILES, BOTH_KEY_AND_RECOVERY]
|
const cards = [RECOVERY_PHRASE, KEYSTORE_FILES, BOTH_KEY_AND_RECOVERY]
|
||||||
|
|
||||||
const RecoveryMechanism = ({
|
const RecoveryMechanism = ({ recoveryMechanism }: RecoveryMechanismProps) => {
|
||||||
recoveryMechanism,
|
|
||||||
handleRecMechanismChange,
|
|
||||||
}: RecoveryMechanismProps) => {
|
|
||||||
return (
|
return (
|
||||||
<YStack style={{ width: '100%' }}>
|
<YStack style={{ width: '100%' }}>
|
||||||
<Text size={19} weight={'semibold'}>
|
<Text size={19} weight={'semibold'}>
|
||||||
|
@ -22,12 +18,7 @@ const RecoveryMechanism = ({
|
||||||
</Text>
|
</Text>
|
||||||
<XStack space={'$4'} style={{ justifyContent: 'space-between', marginTop: '40px' }}>
|
<XStack space={'$4'} style={{ justifyContent: 'space-between', marginTop: '40px' }}>
|
||||||
{cards.map(value => (
|
{cards.map(value => (
|
||||||
<RecoveryMechanismCard
|
<RecoveryMechanismCard key={value} value={value} recoveryMechanism={recoveryMechanism} />
|
||||||
key={value}
|
|
||||||
value={value}
|
|
||||||
recoveryMechanism={recoveryMechanism}
|
|
||||||
handleRecMechanismChange={handleRecMechanismChange}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</XStack>
|
</XStack>
|
||||||
</YStack>
|
</YStack>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Meta, StoryObj } from '@storybook/react'
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
import RecoveryMechanismCard from './RecoveryMechanismCard'
|
import RecoveryMechanismCard from './RecoveryMechanismCard'
|
||||||
import { KEYSTORE_FILES } from '../../../../constants'
|
import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../../constants'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'ValidatorOnboarding/RecoveryMechanismCard',
|
title: 'ValidatorOnboarding/RecoveryMechanismCard',
|
||||||
|
@ -15,11 +15,24 @@ const meta = {
|
||||||
export default meta
|
export default meta
|
||||||
type Story = StoryObj<typeof meta>
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
export const Selected: Story = {
|
export const KeystoreFiles: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: KEYSTORE_FILES,
|
value: KEYSTORE_FILES,
|
||||||
recoveryMechanism: KEYSTORE_FILES,
|
recoveryMechanism: KEYSTORE_FILES,
|
||||||
handleRecMechanismChange: () => {},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RecoveryPhrase: Story = {
|
||||||
|
args: {
|
||||||
|
value: RECOVERY_PHRASE,
|
||||||
|
recoveryMechanism: RECOVERY_PHRASE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BothKeyAndRecovery: Story = {
|
||||||
|
args: {
|
||||||
|
value: BOTH_KEY_AND_RECOVERY,
|
||||||
|
recoveryMechanism: BOTH_KEY_AND_RECOVERY,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +40,6 @@ export const NotSelected: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: KEYSTORE_FILES,
|
value: KEYSTORE_FILES,
|
||||||
recoveryMechanism: '',
|
recoveryMechanism: '',
|
||||||
handleRecMechanismChange: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +47,5 @@ export const WithoutValue: Story = {
|
||||||
args: {
|
args: {
|
||||||
value: '',
|
value: '',
|
||||||
recoveryMechanism: KEYSTORE_FILES,
|
recoveryMechanism: KEYSTORE_FILES,
|
||||||
handleRecMechanismChange: () => {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import { Text } from '@status-im/components'
|
import { Text } from '@status-im/components'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
|
||||||
|
import { setRecoveryMechanism } from '../../../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||||
|
|
||||||
type RecoveryMechanismProps = {
|
type RecoveryMechanismProps = {
|
||||||
value: string
|
value: string
|
||||||
recoveryMechanism: string
|
recoveryMechanism: string
|
||||||
handleRecMechanismChange: (value: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RecoveryMechanismCard = ({
|
const RecoveryMechanismCard = ({ value, recoveryMechanism }: RecoveryMechanismProps) => {
|
||||||
value,
|
const dispatch = useDispatch()
|
||||||
recoveryMechanism,
|
|
||||||
handleRecMechanismChange,
|
const handleRecMechanismChange = () => {
|
||||||
}: RecoveryMechanismProps) => {
|
dispatch(setRecoveryMechanism(value))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -22,7 +26,7 @@ const RecoveryMechanismCard = ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '140px',
|
height: '140px',
|
||||||
}}
|
}}
|
||||||
onClick={() => handleRecMechanismChange(value)}
|
onClick={handleRecMechanismChange}
|
||||||
>
|
>
|
||||||
<Text size={15} weight={'semibold'}>
|
<Text size={15} weight={'semibold'}>
|
||||||
{value}
|
{value}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import { Stack, XStack, YStack } from 'tamagui'
|
import { Stack, XStack, YStack } from 'tamagui'
|
||||||
import { Button, InformationBox, Text } from '@status-im/components'
|
import { Button, InformationBox, Text } from '@status-im/components'
|
||||||
import { CloseCircleIcon } from '@status-im/icons'
|
import { CloseCircleIcon } from '@status-im/icons'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { generateMnemonic } from 'web-bip39'
|
||||||
|
import wordlist from 'web-bip39/wordlists/english'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
import { RootState } from '../../../redux/store'
|
||||||
|
import { setGeneratedMnemonic } from '../../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||||
|
|
||||||
type RecoveryPhraseProps = {
|
type RecoveryPhraseProps = {
|
||||||
isKeystoreFiles: boolean
|
isKeystoreFiles: boolean
|
||||||
|
@ -9,11 +15,31 @@ type RecoveryPhraseProps = {
|
||||||
|
|
||||||
const RecoveryPhrase = ({ isKeystoreFiles }: RecoveryPhraseProps) => {
|
const RecoveryPhrase = ({ isKeystoreFiles }: RecoveryPhraseProps) => {
|
||||||
const [isReveal, setIsReveal] = useState(false)
|
const [isReveal, setIsReveal] = useState(false)
|
||||||
|
const { generatedMnemonic } = useSelector((state: RootState) => state.keyGeneration)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getMnemonic()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getMnemonic = async () => {
|
||||||
|
const mnemonic = await generateMnemonic(wordlist, 256)
|
||||||
|
dispatch(setGeneratedMnemonic(mnemonic.split(' ')))
|
||||||
|
}
|
||||||
|
|
||||||
const revealHandler = () => {
|
const revealHandler = () => {
|
||||||
setIsReveal(state => !state)
|
setIsReveal(state => !state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const copyRecoveryPhraseHandler = () => {
|
||||||
|
if (isKeystoreFiles) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = generatedMnemonic.join(' ')
|
||||||
|
navigator.clipboard.writeText(text)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<YStack space={'$4'} style={{ width: '100%', marginTop: isKeystoreFiles ? '20px' : '0px' }}>
|
<YStack space={'$4'} style={{ width: '100%', marginTop: isKeystoreFiles ? '20px' : '0px' }}>
|
||||||
<Stack
|
<Stack
|
||||||
|
@ -23,30 +49,32 @@ const RecoveryPhrase = ({ isKeystoreFiles }: RecoveryPhraseProps) => {
|
||||||
padding: '28px 18px',
|
padding: '28px 18px',
|
||||||
backgroundColor: '#f4f6fe',
|
backgroundColor: '#f4f6fe',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '176px',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<YStack space={'$2'} style={{ filter: `blur(${isReveal ? '0px' : '4px'})` }}>
|
<YStack
|
||||||
<XStack space={'$6'}>
|
space={'$2'}
|
||||||
<Text size={19} weight={'semibold'}>
|
style={{ filter: `blur(${isReveal ? '0px' : '4px'})`, cursor: 'pointer' }}
|
||||||
this is your secret recovery phrase for the validator
|
onClick={copyRecoveryPhraseHandler}
|
||||||
</Text>
|
>
|
||||||
<Text size={19} weight={'semibold'}>
|
<XStack
|
||||||
this is your secret recovery phrase for the validator
|
style={{
|
||||||
</Text>
|
display: 'grid',
|
||||||
</XStack>
|
gridTemplateColumns: 'repeat(6, 1fr)',
|
||||||
<XStack space={'$6'}>
|
gap: '5px 0px',
|
||||||
<Text size={19} weight={'semibold'}>
|
}}
|
||||||
this is your secret recovery phrase for the validator
|
>
|
||||||
</Text>
|
{generatedMnemonic.map((word, index) => (
|
||||||
<Text size={19} weight={'semibold'}>
|
<Text key={index} size={19} weight={'semibold'}>
|
||||||
this is your secret recovery phrase for the validator
|
{word}
|
||||||
</Text>
|
</Text>
|
||||||
|
))}
|
||||||
</XStack>
|
</XStack>
|
||||||
</YStack>
|
</YStack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack style={{ width: 'fit-content', marginBottom: '12px' }}>
|
<Stack style={{ width: 'fit-content', marginBottom: '12px' }}>
|
||||||
<Button onPress={revealHandler}>Reveal Recovery Phrase</Button>
|
<Button onPress={revealHandler}>
|
||||||
|
{isReveal ? 'Hide Recovery Phrase' : 'Reveal Recovery Phrase'}
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
<InformationBox
|
<InformationBox
|
||||||
message="Write down and keep your Secret Recovery Phrase in a secure place. Make sure no one is looking at your screen."
|
message="Write down and keep your Secret Recovery Phrase in a secure place. Make sure no one is looking at your screen."
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import wordlist from 'web-bip39/wordlists/english'
|
import wordlist from 'web-bip39/wordlists/english'
|
||||||
|
ц
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setIsCopyPastedPhrase,
|
setIsCopyPastedPhrase,
|
||||||
|
@ -21,15 +22,24 @@ import Advisories from './Advisories/Advisories'
|
||||||
import ValidatorSetup from './ValidatorSetup/ValidatorSetup/ValidatorSetup'
|
import ValidatorSetup from './ValidatorSetup/ValidatorSetup/ValidatorSetup'
|
||||||
import ValidatorSetupInstall from './ValidatorSetup/ValidatorInstalling/ValidatorInstall'
|
import ValidatorSetupInstall from './ValidatorSetup/ValidatorInstalling/ValidatorInstall'
|
||||||
import ContinueButton from './ContinueButton'
|
import ContinueButton from './ContinueButton'
|
||||||
|
|
||||||
|
|
||||||
import ActivationValidatorSetup from './ValidatorSetup/ValidatorActivation/ActivationValidatorSetup'
|
import ActivationValidatorSetup from './ValidatorSetup/ValidatorActivation/ActivationValidatorSetup'
|
||||||
|
import './layoutGradient.css'
|
||||||
|
|
||||||
|
|
||||||
import Deposit from './Deposit/Deposit'
|
import Deposit from './Deposit/Deposit'
|
||||||
import { setIsTransactionConfirmation } from '../../redux/ValidatorOnboarding/Deposit/slice'
|
import { setIsTransactionConfirmation } from '../../redux/ValidatorOnboarding/Deposit/slice'
|
||||||
import './layoutGradient.css'
|
import './layoutGradient.css'
|
||||||
|
|
||||||
const ValidatorOnboarding = () => {
|
const ValidatorOnboarding = () => {
|
||||||
const [activeStep, setActiveStep] = useState(0)
|
const { activeStep, subStepValidatorSetup } = useSelector(
|
||||||
|
(state: RootState) => state.validatorOnboarding,
|
||||||
|
)
|
||||||
|
const { isConfirmPhraseStage } = useSelector((state: RootState) => state.keyGeneration)
|
||||||
|
|
||||||
const [isConfirmPhraseStage, setIsConfirmPhraseStage] = useState(false)
|
const [isConfirmPhraseStage, setIsConfirmPhraseStage] = useState(false)
|
||||||
const [subStepValidatorSetup, setSubStepValidatorSetup] = useState(0)
|
|
||||||
|
|
||||||
const [subStepAdvisories, setSubStepAdvisories] = useState(0)
|
const [subStepAdvisories, setSubStepAdvisories] = useState(0)
|
||||||
|
|
||||||
|
@ -101,7 +111,7 @@ const ValidatorOnboarding = () => {
|
||||||
setIsConfirmPhraseStage(false)
|
setIsConfirmPhraseStage(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gradient-wrapper">
|
<div className="gradient-wrapper">
|
||||||
<YStack
|
<YStack
|
||||||
|
@ -118,7 +128,7 @@ const ValidatorOnboarding = () => {
|
||||||
titleSize={19}
|
titleSize={19}
|
||||||
subtitle="Earn Rewards for securing the Ethereum Network"
|
subtitle="Earn Rewards for securing the Ethereum Network"
|
||||||
/>
|
/>
|
||||||
<FormStepper activeStep={activeStep} changeActiveStep={changeActiveStep} />
|
<FormStepper activeStep={activeStep} />
|
||||||
<ValidatorBoxWrapper>
|
<ValidatorBoxWrapper>
|
||||||
{activeStep === 0 && <Overview />}
|
{activeStep === 0 && <Overview />}
|
||||||
{activeStep === 1 && (
|
{activeStep === 1 && (
|
||||||
|
@ -156,13 +166,7 @@ const ValidatorOnboarding = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ValidatorBoxWrapper>
|
</ValidatorBoxWrapper>
|
||||||
<ContinueButton
|
<ContinueButton />
|
||||||
activeStep={activeStep}
|
|
||||||
continueHandler={continueHandler}
|
|
||||||
isConfirmPhraseStage={isConfirmPhraseStage}
|
|
||||||
subStepValidatorSetup={subStepValidatorSetup}
|
|
||||||
isValidatorSet={isValidatorSet}
|
|
||||||
/>
|
|
||||||
</YStack>
|
</YStack>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
import { KEYSTORE_FILES } from '../../../constants'
|
||||||
|
|
||||||
type KeyGenerationState = {
|
type KeyGenerationState = {
|
||||||
words: string[]
|
mnemonic: string[]
|
||||||
isCopyPastedPhrase: boolean
|
isCopyPastedPhrase: boolean
|
||||||
validWords: boolean[]
|
validWords: boolean[]
|
||||||
|
generatedMnemonic: string[]
|
||||||
|
isConfirmPhraseStage: boolean
|
||||||
|
recoveryMechanism: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type wordProps = {
|
type wordProps = {
|
||||||
|
@ -12,9 +17,12 @@ type wordProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: KeyGenerationState = {
|
const initialState: KeyGenerationState = {
|
||||||
words: Array(24).fill(''),
|
mnemonic: Array(24).fill(''),
|
||||||
isCopyPastedPhrase: false,
|
isCopyPastedPhrase: false,
|
||||||
validWords: Array(24).fill(true),
|
validWords: Array(24).fill(true),
|
||||||
|
generatedMnemonic: Array(24).fill(''),
|
||||||
|
isConfirmPhraseStage: false,
|
||||||
|
recoveryMechanism: KEYSTORE_FILES,
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyGenerationSlice = createSlice({
|
const keyGenerationSlice = createSlice({
|
||||||
|
@ -22,12 +30,9 @@ const keyGenerationSlice = createSlice({
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setWord: (state, action: PayloadAction<wordProps>) => {
|
setWord: (state, action: PayloadAction<wordProps>) => {
|
||||||
const newWords = [...state.words]
|
const newMnemonic = [...state.mnemonic]
|
||||||
newWords[action.payload.index] = action.payload.word
|
newMnemonic[action.payload.index] = action.payload.word
|
||||||
return { ...state, words: newWords }
|
state.mnemonic = newMnemonic
|
||||||
},
|
|
||||||
setMnemonic: (state, action: PayloadAction<string[]>) => {
|
|
||||||
state.words = action.payload
|
|
||||||
},
|
},
|
||||||
setIsCopyPastedPhrase: (state, action: PayloadAction<boolean>) => {
|
setIsCopyPastedPhrase: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isCopyPastedPhrase = action.payload
|
state.isCopyPastedPhrase = action.payload
|
||||||
|
@ -35,10 +40,25 @@ const keyGenerationSlice = createSlice({
|
||||||
setValidWords: (state, action: PayloadAction<boolean[]>) => {
|
setValidWords: (state, action: PayloadAction<boolean[]>) => {
|
||||||
state.validWords = action.payload
|
state.validWords = action.payload
|
||||||
},
|
},
|
||||||
|
setGeneratedMnemonic: (state, action: PayloadAction<string[]>) => {
|
||||||
|
state.generatedMnemonic = action.payload
|
||||||
|
},
|
||||||
|
setIsConfirmPhraseStage: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isConfirmPhraseStage = action.payload
|
||||||
|
},
|
||||||
|
setRecoveryMechanism: (state, action: PayloadAction<string>) => {
|
||||||
|
state.recoveryMechanism = action.payload
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setWord, setMnemonic, setIsCopyPastedPhrase, setValidWords } =
|
export const {
|
||||||
keyGenerationSlice.actions
|
setWord,
|
||||||
|
setIsCopyPastedPhrase,
|
||||||
|
setValidWords,
|
||||||
|
setGeneratedMnemonic,
|
||||||
|
setIsConfirmPhraseStage,
|
||||||
|
setRecoveryMechanism,
|
||||||
|
} = keyGenerationSlice.actions
|
||||||
|
|
||||||
export default keyGenerationSlice.reducer
|
export default keyGenerationSlice.reducer
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
type ValidatorOnboardingState = {
|
||||||
|
activeStep: number
|
||||||
|
subStepValidatorSetup: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: ValidatorOnboardingState = {
|
||||||
|
activeStep: 0,
|
||||||
|
subStepValidatorSetup: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatorOnboardingSlice = createSlice({
|
||||||
|
name: 'validatorOnboarding',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setActiveStep(state, action: PayloadAction<number>) {
|
||||||
|
state.activeStep = action.payload
|
||||||
|
},
|
||||||
|
setSubStepValidatorSetup(state, action: PayloadAction<number>) {
|
||||||
|
state.subStepValidatorSetup = action.payload
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { setActiveStep, setSubStepValidatorSetup } = validatorOnboardingSlice.actions
|
||||||
|
|
||||||
|
export default validatorOnboardingSlice.reducer
|
|
@ -6,6 +6,7 @@ import pinnedMessageReducer from './PinnedMessage/slice'
|
||||||
import execClientReducer from './ValidatorOnboarding/ValidatorSetup/slice'
|
import execClientReducer from './ValidatorOnboarding/ValidatorSetup/slice'
|
||||||
import keyGenerationReducer from './ValidatorOnboarding/KeyGeneration/slice'
|
import keyGenerationReducer from './ValidatorOnboarding/KeyGeneration/slice'
|
||||||
import depositReducer from './ValidatorOnboarding/Deposit/slice'
|
import depositReducer from './ValidatorOnboarding/Deposit/slice'
|
||||||
|
import validatorOnboardingReducer from './ValidatorOnboarding/slice'
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
|
@ -15,6 +16,7 @@ const store = configureStore({
|
||||||
theme: themeReducer,
|
theme: themeReducer,
|
||||||
keyGeneration: keyGenerationReducer,
|
keyGeneration: keyGenerationReducer,
|
||||||
deposit: depositReducer,
|
deposit: depositReducer,
|
||||||
|
validatorOnboarding: validatorOnboardingReducer,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue