diff --git a/src/pages/ValidatorOnboarding/ContinueButton.stories.ts b/src/pages/ValidatorOnboarding/ContinueButton.stories.ts index 549e8c04..d809d2c9 100644 --- a/src/pages/ValidatorOnboarding/ContinueButton.stories.ts +++ b/src/pages/ValidatorOnboarding/ContinueButton.stories.ts @@ -17,19 +17,5 @@ export default meta type Story = StoryObj export const Default: Story = { - args: { - continueHandler: () => {}, - activeStep: 0, - isConfirmPhraseStage: false, - subStepValidatorSetup: 0, - }, -} - -export const Disabled: Story = { - args: { - continueHandler: () => {}, - activeStep: 0, - isConfirmPhraseStage: true, - subStepValidatorSetup: 0, - }, + args: {}, } diff --git a/src/pages/ValidatorOnboarding/ContinueButton.tsx b/src/pages/ValidatorOnboarding/ContinueButton.tsx index 024f88ef..22c88f29 100644 --- a/src/pages/ValidatorOnboarding/ContinueButton.tsx +++ b/src/pages/ValidatorOnboarding/ContinueButton.tsx @@ -1,84 +1,125 @@ -import { Stack, YStack } from 'tamagui' +import { Stack, XStack } from 'tamagui' import { Button, InformationBox } from '@status-im/components' import { CloseCircleIcon } from '@status-im/icons' 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 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 = { - continueHandler: () => void - activeStep: number - isConfirmPhraseStage: boolean - subStepValidatorSetup: number - isValidatorSet?: boolean -} - -const ContinueButton = ({ - continueHandler, - activeStep, - isConfirmPhraseStage, - subStepValidatorSetup, - isValidatorSet, -}: ContinueButton) => { - const { isCopyPastedPhrase, words, validWords } = useSelector( - (state: RootState) => state.keyGeneration, +const ContinueButton = () => { + const [isDisabled, setIsDisabled] = useState(false) + const { + isCopyPastedPhrase, + mnemonic, + validWords, + isConfirmPhraseStage, + recoveryMechanism, + generatedMnemonic, + } = useSelector((state: RootState) => state.keyGeneration) + const { activeStep, subStepValidatorSetup } = useSelector( + (state: RootState) => state.validatorOnboarding, ) - 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 = () => { - const isDepositWalletConnected = isWalletConnected === false && activeStep === 5 - let isEmptyPhrase = false - let isNotValidWords = false + setIsDisabled(getDisabledButton()) + }, [activeStep, subStepValidatorSetup, isConfirmPhraseStage, mnemonic, validWords]) + + 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) { - isEmptyPhrase = words.some(word => word === '') - isNotValidWords = validWords.every(word => word === false) - } + const newValidWords = mnemonic.map((w, index) => generatedMnemonic[index] === w) + dispatch(setValidWords(newValidWords)) - if (isEmptyPhrase || isNotValidWords || isDepositWalletConnected) { - return true + if (!newValidWords.includes(false)) { + 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 ( - - - {isCopyPastedPhrase && ( + + {isCopyPastedPhrase && ( + } /> - )} - {isActivationValidatorScreen && ( - - )} - - - - - + + )} + {isActivationValScreen && ( + + )} + + ) } diff --git a/src/pages/ValidatorOnboarding/FormStepper/FormStepper.stories.ts b/src/pages/ValidatorOnboarding/FormStepper/FormStepper.stories.ts index 1db421b1..a096e8b5 100644 --- a/src/pages/ValidatorOnboarding/FormStepper/FormStepper.stories.ts +++ b/src/pages/ValidatorOnboarding/FormStepper/FormStepper.stories.ts @@ -9,6 +9,13 @@ const meta = { layout: 'centered', }, tags: ['autodocs'], + argTypes: { + activeStep: { + options: [0, 1, 2, 3, 4, 5], + control: { type: 'radio' }, + defaultValue: 0, + }, + }, } satisfies Meta export default meta @@ -17,48 +24,41 @@ type Story = StoryObj export const OverviewActive: Story = { args: { activeStep: 0, - changeActiveStep: () => {}, }, } export const AdvisoriesActive: Story = { args: { activeStep: 1, - changeActiveStep: () => {}, }, } export const ClientSetupActive: Story = { args: { activeStep: 2, - changeActiveStep: () => {}, }, } export const ValidatorSetupActive: Story = { args: { activeStep: 3, - changeActiveStep: () => {}, }, } export const KeyGenerationActive: Story = { args: { activeStep: 4, - changeActiveStep: () => {}, }, } export const ActivationActive: Story = { args: { activeStep: 5, - changeActiveStep: () => {}, }, } export const NoActiveStep: Story = { args: { activeStep: -1, - changeActiveStep: () => {}, }, } diff --git a/src/pages/ValidatorOnboarding/FormStepper/FormStepper.tsx b/src/pages/ValidatorOnboarding/FormStepper/FormStepper.tsx index 3c4e9608..ed09ab8e 100644 --- a/src/pages/ValidatorOnboarding/FormStepper/FormStepper.tsx +++ b/src/pages/ValidatorOnboarding/FormStepper/FormStepper.tsx @@ -1,11 +1,10 @@ import { Stepper, Step } from 'react-form-stepper' import './FormStepper.css' +import { useDispatch } from 'react-redux' -type FormStepperProps = { - activeStep: number - changeActiveStep: (step: number) => void -} +import { setActiveStep } from '../../../redux/ValidatorOnboarding/slice' +import './FormStepper.css' const steps = [ { label: 'Overview', subtitle: 'Get Started' }, @@ -17,7 +16,13 @@ const steps = [ { label: 'Activation', subtitle: 'Complete Setup' }, ] -const FormStepper = ({ activeStep, changeActiveStep }: FormStepperProps) => { +type FormStepperProps = { + activeStep: number +} + +const FormStepper = ({ activeStep }: FormStepperProps) => { + const dispatch = useDispatch() + return ( { key={index} label={step.label} className="custom-step" - onClick={() => changeActiveStep(index)} + onClick={() => dispatch(setActiveStep(index))} completed={activeStep > index - 1} data-subtitle={step.subtitle} data-step={step.label} diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/ConfirmRecoveryPhrase/AutocompleteInput.tsx b/src/pages/ValidatorOnboarding/KeyGeneration/ConfirmRecoveryPhrase/AutocompleteInput.tsx index d655b6dc..b1595c07 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/ConfirmRecoveryPhrase/AutocompleteInput.tsx +++ b/src/pages/ValidatorOnboarding/KeyGeneration/ConfirmRecoveryPhrase/AutocompleteInput.tsx @@ -5,7 +5,6 @@ import wordlist from 'web-bip39/wordlists/english' import { RootState } from '../../../../redux/store' import { setIsCopyPastedPhrase, - setMnemonic, setValidWords, setWord, } from '../../../../redux/ValidatorOnboarding/KeyGeneration/slice' @@ -18,9 +17,9 @@ type AutocompleteInputProps = { const AutocompleteInput = ({ index }: AutocompleteInputProps) => { const [suggestions, setSuggestions] = useState([]) 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 validWords = useSelector((state: RootState) => state.keyGeneration.validWords) + const { validWords, generatedMnemonic } = useSelector((state: RootState) => state.keyGeneration) const dispatch = useDispatch() useEffect(() => { @@ -32,39 +31,47 @@ const AutocompleteInput = ({ index }: AutocompleteInputProps) => { } const handleInputChange = (e: React.ChangeEvent) => { + const { value } = e.target + const newMnemonic = value.trim().split(' ') + const newMnemonicLength = newMnemonic.length + let newValidWords = [...validWords] + if (!isFocused) { handleInputFocus() } - const value = e.target.value - const mnemonic = value.trim().split(' ').slice(0, 24) - const mnemonicLength = mnemonic.length - let newValidWords = [...validWords] - - if (mnemonicLength === 1) { - dispatch(setWord({ index, word: value })) - - newValidWords[index] = wordlist.includes(value) || getNewSuggestions(value).length > 0 - } else if (mnemonicLength === 24) { - dispatch(setMnemonic(mnemonic)) - dispatch(setIsCopyPastedPhrase(true)) - - mnemonic.forEach((m, i) => { - 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)) + switch (newMnemonicLength) { + case 1: + updateWord(index, value, newValidWords) + break + case 24: + dispatch(setIsCopyPastedPhrase(true)) + updateMultipleWords(newMnemonic, newValidWords) + break + default: + const endIndex = Math.min(newMnemonicLength + index, 24) + const partialMnemonic = newMnemonic.slice(0, endIndex - index) + dispatch(setIsCopyPastedPhrase(true)) + updateMultipleWords(partialMnemonic, newValidWords, index) + break } 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) => { e.preventDefault() @@ -72,7 +79,7 @@ const AutocompleteInput = ({ index }: AutocompleteInputProps) => { dispatch(setWord({ index, word: suggestion })) let newValidWords = [...validWords] - newValidWords[index] = wordlist.includes(suggestion) + newValidWords[index] = generatedMnemonic[index] === suggestion dispatch(setValidWords(newValidWords)) } @@ -84,7 +91,7 @@ const AutocompleteInput = ({ index }: AutocompleteInputProps) => { setIsFocused(false) let newValidWords = [...validWords] - newValidWords[index] = wordlist.includes(word) + newValidWords[index] = generatedMnemonic[index] === word dispatch(setValidWords(newValidWords)) } diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/KeyGeneration.tsx b/src/pages/ValidatorOnboarding/KeyGeneration/KeyGeneration.tsx index 8c672ccf..fe7e0776 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/KeyGeneration.tsx +++ b/src/pages/ValidatorOnboarding/KeyGeneration/KeyGeneration.tsx @@ -1,20 +1,21 @@ import { Stack, YStack } from 'tamagui' import { Text } from '@status-im/components' -import { useState } from 'react' +import { useSelector } from 'react-redux' import KeyGenerationHeader from './KeyGenerationHeader/KeyGenerationHeader' import RecoveryMechanism from './RecoveryMechanism/RecoveryMechanism' import KeystoreFiles from './KeystoreFiles' import RecoveryPhrase from './RecoveryPhrase' -import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../constants' import ConfirmRecoveryPhrase from './ConfirmRecoveryPhrase/ConfirmRecoveryPhrase' +import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../constants' +import { RootState } from '../../../redux/store' type KeyGenerationProps = { isConfirmPhraseStage: boolean } const KeyGeneration = ({ isConfirmPhraseStage }: KeyGenerationProps) => { - const [recoveryMechanism, setRecoveryMechanism] = useState(KEYSTORE_FILES) + const { recoveryMechanism } = useSelector((state: RootState) => state.keyGeneration) const isKeystoreFiles = recoveryMechanism === KEYSTORE_FILES || recoveryMechanism === BOTH_KEY_AND_RECOVERY @@ -22,20 +23,13 @@ const KeyGeneration = ({ isConfirmPhraseStage }: KeyGenerationProps) => { const isRecoveryPhrase = recoveryMechanism === RECOVERY_PHRASE || recoveryMechanism === BOTH_KEY_AND_RECOVERY - const handleRecMechanismChange = (value: string) => { - setRecoveryMechanism(value) - } - return ( {isConfirmPhraseStage && } {isConfirmPhraseStage === false && ( <> - + 4 Validators diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/KeyGenerationHeader/KeyGenerationHeader.tsx b/src/pages/ValidatorOnboarding/KeyGeneration/KeyGenerationHeader/KeyGenerationHeader.tsx index da062939..9d10ad84 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/KeyGenerationHeader/KeyGenerationHeader.tsx +++ b/src/pages/ValidatorOnboarding/KeyGeneration/KeyGenerationHeader/KeyGenerationHeader.tsx @@ -5,7 +5,7 @@ import KeyGenerationTitle from '../KeyGenerationTitle' const KeyGenerationHeader = () => { return ( - + { + const [isOpen, setIsOpen] = useState(true) + + const closeCardHanlder = () => { + setIsOpen(false) + } + + if (isOpen === false) { + return null + } + return ( @@ -48,7 +59,12 @@ const KeyGenerationSyncCard = ({ synced, total, title, color }: KeyGenerationSyn {formatNumberForGauge(synced)} / {formatNumberForGauge(total)} - + ) diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/KeystoreFiles.tsx b/src/pages/ValidatorOnboarding/KeyGeneration/KeystoreFiles.tsx index 0ca60c66..1fb44a9d 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/KeystoreFiles.tsx +++ b/src/pages/ValidatorOnboarding/KeyGeneration/KeystoreFiles.tsx @@ -6,26 +6,49 @@ import { useState } from 'react' const KeystoreFiles = () => { const [encryptedPassword, setEncryptedPassword] = 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) => { - setEncryptedPassword(value) + setEncryptedPasswordError(false) + setConfirmEncryptedPasswordError(false) } - const changeConfirmEncryptedPasswordHandler = (value: string) => { - setConfirmEncryptedPassword(value) + const changeEncryptedPasswordHandler = (e: any) => { + const password = e.target.value + setEncryptedPassword(password) + setDisplayEncryptedPassword(getHidedPassword(password.length)) } - const clearEncryptedPasswordHandler = () => { - setEncryptedPassword('') + const changeConfirmEncryptedPasswordHandler = (e: any) => { + const password = e.target.value + setConfirmEncryptedPassword(password) + setDisplayConfirmEncryptedPassword(getHidedPassword(password.length)) } - const clearConfirmEncryptedPasswordHandler = () => { - setConfirmEncryptedPassword('') + const downloadKeyFilesHandler = () => { + 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 ( @@ -42,11 +65,12 @@ const KeystoreFiles = () => { size={16} color="#A1ABBD" style={{ cursor: 'pointer' }} - onClick={clearEncryptedPasswordHandler} + onClick={() => setEncryptedPassword('')} /> } - value={encryptedPassword} - onChangeText={changeEncryptedPasswordHandler} + error={encryptedPasswordError} + value={displayEncryptedPassword} + onChange={changeEncryptedPasswordHandler} /> @@ -60,11 +84,12 @@ const KeystoreFiles = () => { size={16} color="#A1ABBD" style={{ cursor: 'pointer' }} - onClick={clearConfirmEncryptedPasswordHandler} + onClick={() => setConfirmEncryptedPassword('')} /> } - value={confirmEncryptedPassword} - onChangeText={changeConfirmEncryptedPasswordHandler} + error={confirmEncryptedPasswordError} + value={displayConfirmEncryptedPassword} + onChange={changeConfirmEncryptedPasswordHandler} /> diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.stories.ts b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.stories.tsx similarity index 81% rename from src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.stories.ts rename to src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.stories.tsx index 5b2fbb60..faf7173d 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.stories.ts +++ b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.stories.tsx @@ -10,6 +10,13 @@ const meta = { layout: 'centered', }, tags: ['autodocs'], + argTypes: { + recoveryMechanism: { + options: [RECOVERY_PHRASE, KEYSTORE_FILES, BOTH_KEY_AND_RECOVERY], + control: { type: 'radio' }, + defaultValue: KEYSTORE_FILES, + }, + }, } satisfies Meta export default meta @@ -18,27 +25,23 @@ type Story = StoryObj export const KeystoreFiles: Story = { args: { recoveryMechanism: KEYSTORE_FILES, - handleRecMechanismChange: () => {}, }, } export const RecoveryPhrase: Story = { args: { recoveryMechanism: RECOVERY_PHRASE, - handleRecMechanismChange: () => {}, }, } export const BothKeystoreAndRecovery: Story = { args: { recoveryMechanism: BOTH_KEY_AND_RECOVERY, - handleRecMechanismChange: () => {}, }, } export const WithoutRecMechanism: Story = { args: { recoveryMechanism: '', - handleRecMechanismChange: () => {}, }, } diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.tsx b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.tsx index fd768bb4..84a49253 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.tsx +++ b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanism.tsx @@ -6,15 +6,11 @@ import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../.. type RecoveryMechanismProps = { recoveryMechanism: string - handleRecMechanismChange: (value: string) => void } const cards = [RECOVERY_PHRASE, KEYSTORE_FILES, BOTH_KEY_AND_RECOVERY] -const RecoveryMechanism = ({ - recoveryMechanism, - handleRecMechanismChange, -}: RecoveryMechanismProps) => { +const RecoveryMechanism = ({ recoveryMechanism }: RecoveryMechanismProps) => { return ( @@ -22,12 +18,7 @@ const RecoveryMechanism = ({ {cards.map(value => ( - + ))} diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.stories.ts b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.stories.ts index 06f87741..85c512c5 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.stories.ts +++ b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.stories.ts @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react' import RecoveryMechanismCard from './RecoveryMechanismCard' -import { KEYSTORE_FILES } from '../../../../constants' +import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../../constants' const meta = { title: 'ValidatorOnboarding/RecoveryMechanismCard', @@ -15,11 +15,24 @@ const meta = { export default meta type Story = StoryObj -export const Selected: Story = { +export const KeystoreFiles: Story = { args: { value: 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: { value: KEYSTORE_FILES, recoveryMechanism: '', - handleRecMechanismChange: () => {}, }, } @@ -35,6 +47,5 @@ export const WithoutValue: Story = { args: { value: '', recoveryMechanism: KEYSTORE_FILES, - handleRecMechanismChange: () => {}, }, } diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.tsx b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.tsx index 4b5098bd..62235436 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.tsx +++ b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryMechanism/RecoveryMechanismCard.tsx @@ -1,16 +1,20 @@ import { Text } from '@status-im/components' +import { useDispatch } from 'react-redux' + +import { setRecoveryMechanism } from '../../../../redux/ValidatorOnboarding/KeyGeneration/slice' type RecoveryMechanismProps = { value: string recoveryMechanism: string - handleRecMechanismChange: (value: string) => void } -const RecoveryMechanismCard = ({ - value, - recoveryMechanism, - handleRecMechanismChange, -}: RecoveryMechanismProps) => { +const RecoveryMechanismCard = ({ value, recoveryMechanism }: RecoveryMechanismProps) => { + const dispatch = useDispatch() + + const handleRecMechanismChange = () => { + dispatch(setRecoveryMechanism(value)) + } + return (
handleRecMechanismChange(value)} + onClick={handleRecMechanismChange} > {value} diff --git a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryPhrase.tsx b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryPhrase.tsx index 10e0a84f..a80b74da 100644 --- a/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryPhrase.tsx +++ b/src/pages/ValidatorOnboarding/KeyGeneration/RecoveryPhrase.tsx @@ -1,7 +1,13 @@ import { Stack, XStack, YStack } from 'tamagui' import { Button, InformationBox, Text } from '@status-im/components' 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 = { isKeystoreFiles: boolean @@ -9,11 +15,31 @@ type RecoveryPhraseProps = { const RecoveryPhrase = ({ isKeystoreFiles }: RecoveryPhraseProps) => { 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 = () => { setIsReveal(state => !state) } + const copyRecoveryPhraseHandler = () => { + if (isKeystoreFiles) { + return + } + + const text = generatedMnemonic.join(' ') + navigator.clipboard.writeText(text) + } + return ( { padding: '28px 18px', backgroundColor: '#f4f6fe', width: '100%', - height: '176px', }} > - - - - this is your secret recovery phrase for the validator - - - this is your secret recovery phrase for the validator - - - - - this is your secret recovery phrase for the validator - - - this is your secret recovery phrase for the validator - + + + {generatedMnemonic.map((word, index) => ( + + {word} + + ))} - + { - 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 [subStepValidatorSetup, setSubStepValidatorSetup] = useState(0) + const [subStepAdvisories, setSubStepAdvisories] = useState(0) @@ -101,7 +111,7 @@ const ValidatorOnboarding = () => { setIsConfirmPhraseStage(false) } } - + return (
{ titleSize={19} subtitle="Earn Rewards for securing the Ethereum Network" /> - + {activeStep === 0 && } {activeStep === 1 && ( @@ -156,13 +166,7 @@ const ValidatorOnboarding = () => { /> )} - +
) diff --git a/src/redux/ValidatorOnboarding/KeyGeneration/slice.ts b/src/redux/ValidatorOnboarding/KeyGeneration/slice.ts index 158dc368..94e1c5d3 100644 --- a/src/redux/ValidatorOnboarding/KeyGeneration/slice.ts +++ b/src/redux/ValidatorOnboarding/KeyGeneration/slice.ts @@ -1,9 +1,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { KEYSTORE_FILES } from '../../../constants' + type KeyGenerationState = { - words: string[] + mnemonic: string[] isCopyPastedPhrase: boolean validWords: boolean[] + generatedMnemonic: string[] + isConfirmPhraseStage: boolean + recoveryMechanism: string } type wordProps = { @@ -12,9 +17,12 @@ type wordProps = { } const initialState: KeyGenerationState = { - words: Array(24).fill(''), + mnemonic: Array(24).fill(''), isCopyPastedPhrase: false, validWords: Array(24).fill(true), + generatedMnemonic: Array(24).fill(''), + isConfirmPhraseStage: false, + recoveryMechanism: KEYSTORE_FILES, } const keyGenerationSlice = createSlice({ @@ -22,12 +30,9 @@ const keyGenerationSlice = createSlice({ initialState, reducers: { setWord: (state, action: PayloadAction) => { - const newWords = [...state.words] - newWords[action.payload.index] = action.payload.word - return { ...state, words: newWords } - }, - setMnemonic: (state, action: PayloadAction) => { - state.words = action.payload + const newMnemonic = [...state.mnemonic] + newMnemonic[action.payload.index] = action.payload.word + state.mnemonic = newMnemonic }, setIsCopyPastedPhrase: (state, action: PayloadAction) => { state.isCopyPastedPhrase = action.payload @@ -35,10 +40,25 @@ const keyGenerationSlice = createSlice({ setValidWords: (state, action: PayloadAction) => { state.validWords = action.payload }, + setGeneratedMnemonic: (state, action: PayloadAction) => { + state.generatedMnemonic = action.payload + }, + setIsConfirmPhraseStage: (state, action: PayloadAction) => { + state.isConfirmPhraseStage = action.payload + }, + setRecoveryMechanism: (state, action: PayloadAction) => { + state.recoveryMechanism = action.payload + }, }, }) -export const { setWord, setMnemonic, setIsCopyPastedPhrase, setValidWords } = - keyGenerationSlice.actions +export const { + setWord, + setIsCopyPastedPhrase, + setValidWords, + setGeneratedMnemonic, + setIsConfirmPhraseStage, + setRecoveryMechanism, +} = keyGenerationSlice.actions export default keyGenerationSlice.reducer diff --git a/src/redux/ValidatorOnboarding/slice.ts b/src/redux/ValidatorOnboarding/slice.ts new file mode 100644 index 00000000..fd2d0b52 --- /dev/null +++ b/src/redux/ValidatorOnboarding/slice.ts @@ -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) { + state.activeStep = action.payload + }, + setSubStepValidatorSetup(state, action: PayloadAction) { + state.subStepValidatorSetup = action.payload + }, + }, +}) + +export const { setActiveStep, setSubStepValidatorSetup } = validatorOnboardingSlice.actions + +export default validatorOnboardingSlice.reducer diff --git a/src/redux/store.ts b/src/redux/store.ts index 06218390..fad839dc 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -6,6 +6,7 @@ import pinnedMessageReducer from './PinnedMessage/slice' import execClientReducer from './ValidatorOnboarding/ValidatorSetup/slice' import keyGenerationReducer from './ValidatorOnboarding/KeyGeneration/slice' import depositReducer from './ValidatorOnboarding/Deposit/slice' +import validatorOnboardingReducer from './ValidatorOnboarding/slice' const store = configureStore({ reducer: { @@ -15,6 +16,7 @@ const store = configureStore({ theme: themeReducer, keyGeneration: keyGenerationReducer, deposit: depositReducer, + validatorOnboarding: validatorOnboardingReducer, }, })