Merge pull request #41 from nimbus-gui/hn.fix-validator-onboarding

Hn.fix validator onboarding
This commit is contained in:
Hristo Nedelkov 2024-01-30 15:24:58 +02:00 committed by GitHub
commit 9d28bf19c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 340 additions and 281 deletions

View File

@ -21,6 +21,18 @@ import LogsPage from './pages/LogsPage/LogsPage'
import { ethereumRopsten, wcV2InitOptions, apiKey } from './constants' import { ethereumRopsten, wcV2InitOptions, apiKey } from './constants'
import './App.css' import './App.css'
//ValidatorOnboarding sub-routes
import Overview from './pages/ValidatorOnboarding/Overview/Overview'
import Advisories from './pages/ValidatorOnboarding/Advisories/Advisories'
import ValidatorSetup from './pages/ValidatorOnboarding/ValidatorSetup/ValidatorSetup/ValidatorSetup'
import ValidatorSetupInstall from './pages/ValidatorOnboarding/ValidatorSetup/ValidatorInstalling/ValidatorInstall'
import ConsensusSelection from './pages/ValidatorOnboarding/ValidatorSetup/ConsensusClient/ConsensusSelection'
import ActivationValidatorSetup from './pages/ValidatorOnboarding/ValidatorSetup/ValidatorActivation/ActivationValidatorSetup'
import ClientSetup from './pages/ValidatorOnboarding/ClientSetup/ClientSetup'
import KeyGeneration from './pages/ValidatorOnboarding/KeyGeneration/KeyGeneration'
import Deposit from './pages/ValidatorOnboarding/Deposit/Deposit'
import Activation from './pages/ValidatorOnboarding/Activation/Activation'
const injected = injectedModule() const injected = injectedModule()
const walletConnect = walletConnectModule(wcV2InitOptions) const walletConnect = walletConnectModule(wcV2InitOptions)
@ -55,10 +67,35 @@ const router = createBrowserRouter([
element: <PairDevice />, element: <PairDevice />,
}, },
{ path: '/create-local-node', element: <CreateLocalNode /> }, { path: '/create-local-node', element: <CreateLocalNode /> },
{ path: '/validator-onboarding', element: <ValidatorOnboarding /> }, {
path: '/validator-onboarding',
children: [
{ path: '', element: <Overview /> },
{ path: 'advisories', element: <Advisories /> },
{ path: 'validator-setup', element: <ValidatorSetup /> },
{ path: 'validator-setup-install', element: <ValidatorSetupInstall /> },
{ path: 'consensus-selection', element: <ConsensusSelection /> },
{
path: 'activation-validator-setup',
element: <ActivationValidatorSetup />,
},
{ path: 'client-setup', element: <ClientSetup /> },
{ path: 'key-generation', element: <KeyGeneration /> },
{ path: 'deposit', element: <Deposit /> },
{
path: 'activation',
element: <Activation />,
},
],
element: <ValidatorOnboarding />,
},
{ path: '/dashboard', element: <Dashboard /> }, { path: '/dashboard', element: <Dashboard /> },
{ path: '/logs', element: <LogsPage /> }, { path: '/logs', element: <LogsPage /> },
{ path: '/validator-management', element: <ValidatorManagement /> }, {
path: '/validator-management',
element: <ValidatorManagement />,
},
]) ])
function App() { function App() {

View File

@ -1,5 +1,5 @@
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import styles from './../../../pages/ValidatorOnboarding/ValidatorSetup/ValidatorInstalling/InstallLayout.module.css'
import MarkdownLink from './MarkdownLink' import MarkdownLink from './MarkdownLink'
type MarkdownProps = { type MarkdownProps = {
@ -7,7 +7,13 @@ type MarkdownProps = {
} }
const Markdown = ({ children }: MarkdownProps) => { const Markdown = ({ children }: MarkdownProps) => {
return <ReactMarkdown children={children} components={{ a: MarkdownLink }} /> return (
<ReactMarkdown
children={children}
components={{ a: MarkdownLink }}
className={styles['markdown-text']}
/>
)
} }
export default Markdown export default Markdown

View File

@ -5,38 +5,11 @@ import Confetti from 'react-confetti'
import ActivationCard from './ActivationCard' import ActivationCard from './ActivationCard'
import LinkWithArrow from '../../../components/General/LinkWithArrow' import LinkWithArrow from '../../../components/General/LinkWithArrow'
import { useWindowSize } from '../../../hooks/useWindowSize'
type ActivationProps = { const Activation = () => {
validatorsValue: string
executionSyncStatus1: {
text: string
isGaugeIncluded: boolean
gaugeColor: string
gaugeSynced: number
gaugeTotal: number
}
executionSyncStatus2: {
text: string
isGaugeIncluded: boolean
gaugeColor: string
gaugeSynced: number
gaugeTotal: number
}
currentAPRValue: string
estimatedActivationTimeValue: string
validatorQueueValue: string
}
const Activation = ({
validatorsValue,
executionSyncStatus1,
executionSyncStatus2,
currentAPRValue,
estimatedActivationTimeValue,
validatorQueueValue,
}: ActivationProps) => {
const [showConfetti, setShowConfetti] = useState(true) const [showConfetti, setShowConfetti] = useState(true)
const windowSize = useWindowSize()
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
setShowConfetti(false) setShowConfetti(false)
@ -61,22 +34,36 @@ const Activation = ({
Validators and are currently syncing your nodes. Validators and are currently syncing your nodes.
</Text> </Text>
</Stack> </Stack>
<YStack space={'$3'} marginTop={'25px'} width={'33%'}> <YStack marginTop={'25px'}>
<XStack space={'$3'} justifyContent={'space-between'}> <XStack
<ActivationCard text="Validators" value={validatorsValue} /> width="100%"
<ActivationCard {...executionSyncStatus1} /> flexWrap={windowSize.width < 780 ? 'wrap' : 'nowrap'}
<ActivationCard {...executionSyncStatus2} /> >
<ActivationCard
text="Execution Sync Status"
isGaugeIncluded={true}
gaugeColor={'#2a4af5'}
gaugeSynced={123.524}
gaugeTotal={172.503}
/>
<ActivationCard
text="Execution Sync Status"
isGaugeIncluded={true}
gaugeColor={'#EB5757'}
gaugeSynced={123.524}
gaugeTotal={172.503}
/>
</XStack> </XStack>
<XStack space={'$3'}> <XStack
<ActivationCard text="Current APR" value={currentAPRValue} /> flexWrap={windowSize.width < 780 ? 'wrap' : 'nowrap'}
width="100%"
>
<ActivationCard text="Validator Queue" value="92603" />
<ActivationCard <ActivationCard
text="Estimated Activation Time" text="Estimated Activation Time"
value={estimatedActivationTimeValue} value="32 Days"
/>
<ActivationCard
text="Validator Queue"
value={validatorQueueValue}
/> />
<ActivationCard text="Current APR" value="4.40%" />
</XStack> </XStack>
</YStack> </YStack>
</YStack> </YStack>

View File

@ -26,7 +26,8 @@ const ActivationCard = ({
border: '1px solid rgba(0, 0, 0, 0.15)', border: '1px solid rgba(0, 0, 0, 0.15)',
padding: '12px 16px', padding: '12px 16px',
backgroundColor: '#FFF', backgroundColor: '#FFF',
width: '100%', flex: 1,
margin: '8px',
}} }}
> >
{!isGaugeIncluded && ( {!isGaugeIncluded && (

View File

@ -1,6 +1,7 @@
import { Text } from '@status-im/components' import { Text } from '@status-im/components'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Stack, YStack } from 'tamagui' import { Stack, YStack } from 'tamagui'
import { useWindowSize } from '../../../hooks/useWindowSize'
type AdvisoriesContentProps = { type AdvisoriesContentProps = {
title: string title: string
@ -8,8 +9,9 @@ type AdvisoriesContentProps = {
} }
const AdvisoriesContent = ({ title, content }: AdvisoriesContentProps) => { const AdvisoriesContent = ({ title, content }: AdvisoriesContentProps) => {
const windowSize = useWindowSize()
return ( return (
<YStack space={'$1'} style={{ width: '70%' }}> <YStack space={'$1'} width={windowSize.width < 780 ? '100%' : '70%'}>
<Stack style={{ marginBottom: '5%' }}> <Stack style={{ marginBottom: '5%' }}>
<Text size={27} weight={400}> <Text size={27} weight={400}>
{title} {title}

View File

@ -2,7 +2,6 @@
display: flex; display: flex;
padding: 30px; padding: 30px;
justify-content: space-between; justify-content: space-between;
width: auto;
} }
.advisories-nav { .advisories-nav {
display: flex; display: flex;

View File

@ -8,18 +8,17 @@ 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 { import { setActiveStep } from '../../redux/ValidatorOnboarding/slice'
setActiveStep,
setSubStepValidatorSetup,
} from '../../redux/ValidatorOnboarding/slice'
import { KEYSTORE_FILES } from '../../constants' import { KEYSTORE_FILES } from '../../constants'
import { import {
setIsConfirmPhraseStage, setIsConfirmPhraseStage,
setIsCopyPastedPhrase, setIsCopyPastedPhrase,
setValidWords, setValidWords,
} from '../../redux/ValidatorOnboarding/KeyGeneration/slice' } from '../../redux/ValidatorOnboarding/KeyGeneration/slice'
import { useWindowSize } from '../../hooks/useWindowSize'
const ContinueButton = () => { const ContinueButton = () => {
const windowSize = useWindowSize()
const [isDisabled, setIsDisabled] = useState(false) const [isDisabled, setIsDisabled] = useState(false)
const { const {
isCopyPastedPhrase, isCopyPastedPhrase,
@ -33,24 +32,44 @@ const ContinueButton = () => {
const { activeStep, subStepValidatorSetup } = useSelector( const { activeStep, subStepValidatorSetup } = useSelector(
(state: RootState) => state.validatorOnboarding, (state: RootState) => state.validatorOnboarding,
) )
const dispatch = useDispatch()
const pathToStepMap = {
'/validator-onboarding/': 0,
'/validator-onboarding/advisories': 1,
'/validator-onboarding/validator-setup': 2,
'/validator-onboarding/validator-setup-install': 3,
'/validator-onboarding/consensus-selection': 4,
'/validator-onboarding/activation-validator-setup': 5,
'/validator-onboarding/client-setup': 6,
'/validator-onboarding/key-generation': 7,
'/validator-onboarding/deposit': 8,
'/validator-onboarding/activation': 9,
}
dispatch(
setActiveStep(
pathToStepMap[location.pathname as keyof typeof pathToStepMap] || 0,
),
)
const { isValidatorSet } = useSelector( const { isValidatorSet } = useSelector(
(state: RootState) => state.validatorSetup, (state: RootState) => state.validatorSetup,
) )
const dispatch = useDispatch()
const navigate = useNavigate() const navigate = useNavigate()
const isActivationValScreen = activeStep === 3 && subStepValidatorSetup === 3
const isActivationValScreen = activeStep === 5
useEffect(() => { useEffect(() => {
const getDisabledButton = () => { const getDisabledButton = () => {
if (activeStep === 4 && isConfirmPhraseStage) { if (activeStep === 7 && isConfirmPhraseStage) {
if ( if (
validWords.some(w => w === false) || validWords.some(w => w === false) ||
generatedMnemonic.some((w, i) => w !== mnemonic[i]) generatedMnemonic.some((w, i) => w !== mnemonic[i])
) { ) {
return true return true
} }
} else if (activeStep === 3 && !isValidatorSet) { } else if (activeStep === 6 && !isValidatorSet) {
return true return true
} }
return false return false
@ -66,22 +85,10 @@ const ContinueButton = () => {
isValidatorSet, isValidatorSet,
]) ])
const handleStep1 = () => { const handleRecoveryMechanism = () => {
dispatch(setActiveStep(activeStep + 1))
}
const handleStep2 = () => {
if (subStepValidatorSetup === 3) {
return dispatch(setActiveStep(activeStep + 1))
}
dispatch(setSubStepValidatorSetup(subStepValidatorSetup + 1))
}
const handleStep4 = () => {
if (!isConfirmPhraseStage && recoveryMechanism === KEYSTORE_FILES) { if (!isConfirmPhraseStage && recoveryMechanism === KEYSTORE_FILES) {
return dispatch(setActiveStep(activeStep + 1)) return dispatch(setActiveStep(activeStep + 1))
} }
if (!isConfirmPhraseStage) { if (!isConfirmPhraseStage) {
return dispatch(setIsConfirmPhraseStage(true)) return dispatch(setIsConfirmPhraseStage(true))
} }
@ -102,26 +109,43 @@ const ContinueButton = () => {
} }
const continueHandler = () => { const continueHandler = () => {
if (activeStep === 1) { let nextPath
handleStep1() if (activeStep === 0) nextPath = '/validator-onboarding/advisories'
} else if (activeStep === 2) { else if (activeStep === 1)
handleStep2() nextPath = '/validator-onboarding/validator-setup'
} else if (activeStep === 4) { else if (activeStep === 2)
handleStep4() nextPath = '/validator-onboarding/validator-setup-install'
} else { else if (activeStep === 3)
if (activeStep < 6) { nextPath = '/validator-onboarding/consensus-selection'
dispatch(setActiveStep(activeStep + 1)) else if (activeStep === 4)
nextPath = '/validator-onboarding/activation-validator-setup'
else if (activeStep === 5) {
nextPath = '/validator-onboarding/client-setup'
} else if (activeStep === 6) {
nextPath = '/validator-onboarding/key-generation'
} else if (activeStep === 7) {
if (isConfirmPhraseStage) {
nextPath = '/validator-onboarding/deposit'
} else { } else {
navigate('/dashboard') nextPath = '/validator-onboarding/key-generation'
} }
} handleRecoveryMechanism()
} else if (activeStep === 8) nextPath = '/validator-onboarding/activation'
else if (activeStep === 9) nextPath = '/dashboard'
else nextPath = '/validator-onboarding/'
navigate(nextPath)
} }
return ( return (
<XStack <XStack
style={{ style={{
width: '100%', width: '100%',
justifyContent: isActivationValScreen ? 'space-between' : 'end', justifyContent: isActivationValScreen
? 'space-between'
: windowSize.width < 560
? 'start'
: 'end',
alignItems: 'center', alignItems: 'center',
zIndex: 1000, zIndex: 1000,
marginTop: '21px', marginTop: '21px',
@ -145,7 +169,7 @@ const ContinueButton = () => {
/> />
)} )}
<Button onPress={continueHandler} size={40} disabled={isDisabled}> <Button onPress={continueHandler} size={40} disabled={isDisabled}>
{activeStep < 6 ? 'Continue' : 'Continue to Dashboard'} {activeStep < 9 ? 'Continue' : 'Continue to Dashboard'}
</Button> </Button>
</XStack> </XStack>
) )

View File

@ -1,64 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react'
import FormStepper from './FormStepper'
const meta = {
title: 'ValidatorOnboarding/FormStepper',
component: FormStepper,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
activeStep: {
options: [0, 1, 2, 3, 4, 5],
control: { type: 'radio' },
defaultValue: 0,
},
},
} satisfies Meta<typeof FormStepper>
export default meta
type Story = StoryObj<typeof meta>
export const OverviewActive: Story = {
args: {
activeStep: 0,
},
}
export const AdvisoriesActive: Story = {
args: {
activeStep: 1,
},
}
export const ClientSetupActive: Story = {
args: {
activeStep: 2,
},
}
export const ValidatorSetupActive: Story = {
args: {
activeStep: 3,
},
}
export const KeyGenerationActive: Story = {
args: {
activeStep: 4,
},
}
export const ActivationActive: Story = {
args: {
activeStep: 5,
},
}
export const NoActiveStep: Story = {
args: {
activeStep: -1,
},
}

View File

@ -1,7 +1,5 @@
import { Stepper, Step } from 'react-form-stepper' import { Stepper, Step } from 'react-form-stepper'
import { useDispatch } from 'react-redux' import { useNavigate } from 'react-router-dom'
import { setActiveStep } from '../../../redux/ValidatorOnboarding/slice'
import { FORM_STEPS } from '../../../constants' import { FORM_STEPS } from '../../../constants'
import { useWindowSize } from '../../../hooks/useWindowSize' import { useWindowSize } from '../../../hooks/useWindowSize'
import './FormStepper.css' import './FormStepper.css'
@ -11,9 +9,22 @@ type FormStepperProps = {
} }
const FormStepper = ({ activeStep }: FormStepperProps) => { const FormStepper = ({ activeStep }: FormStepperProps) => {
const dispatch = useDispatch() const navigate = useNavigate()
const windowSize = useWindowSize() const windowSize = useWindowSize()
const stepToUrlMap = [
'/validator-onboarding/',
'/validator-onboarding/advisories',
'/validator-onboarding/validator-setup',
'/validator-onboarding/validator-setup-install',
'/validator-onboarding/consensus-selection',
'/validator-onboarding/activation-validator-setup',
'/validator-onboarding/client-setup',
'/validator-onboarding/key-generation',
'/validator-onboarding/deposit',
'/validator-onboarding/activation',
]
const getIsStepVisible = ( const getIsStepVisible = (
index: number, index: number,
stepsBefore: number, stepsBefore: number,
@ -23,7 +34,6 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
let start = activeStep - stepsBefore let start = activeStep - stepsBefore
let end = activeStep + stepsAfter let end = activeStep + stepsAfter
// active step is near the start or end
if (start < 0) { if (start < 0) {
end -= start end -= start
start = 0 start = 0
@ -41,23 +51,27 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
const isStepVisible = (index: number) => { const isStepVisible = (index: number) => {
if (windowSize.width < 774) { if (windowSize.width < 774) {
return getIsStepVisible(index, 1, 1) // 3 steps (1 before, 1 after) return getIsStepVisible(index, 1, 1)
} else if (windowSize.width < 963) { } else if (windowSize.width < 963) {
return getIsStepVisible(index, 1, 2) // 4 steps return getIsStepVisible(index, 1, 2)
} else if (windowSize.width < 1183) { } else if (windowSize.width < 1183) {
return getIsStepVisible(index, 1, 3) // 5 steps return getIsStepVisible(index, 1, 3)
} else if (windowSize.width < 1300) { } else if (windowSize.width < 1300) {
return getIsStepVisible(index, 2, 3) // 6 steps return getIsStepVisible(index, 2, 3)
} else { } else {
return true return true
} }
} }
const changeStepOnClickHandler = (index: number) => { const changeStepOnClickHandler = (index: number) => {
if (activeStep > index) { const path = stepToUrlMap[index]
dispatch(setActiveStep(index)) if (path && index < activeStep) {
navigate(path)
} }
} }
if (activeStep > 1) {
activeStep = activeStep < 6 ? 2 : activeStep - 3
}
return ( return (
<Stepper <Stepper
@ -85,12 +99,18 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
completed={activeStep > originalIndex - 1} completed={activeStep > originalIndex - 1}
data-subtitle={step.subtitle} data-subtitle={step.subtitle}
data-step={step.label} data-step={step.label}
style={
originalIndex === activeStep
? { backgroundColor: stepStyle.currentBgColor }
: {}
}
/> />
) )
})} })}
</Stepper> </Stepper>
) )
} }
const stepStyle = { const stepStyle = {
// For default dots: // For default dots:
inactiveBgColor: '#FFFFFF', inactiveBgColor: '#FFFFFF',
@ -107,9 +127,10 @@ const stepStyle = {
inactiveTextColor: '#000000', inactiveTextColor: '#000000',
size: '28px', size: '28px',
circleFontSize: '0px', circleFontSize: '0px',
labelFontSize: '14px', labelFontSize: '13px',
borderRadius: '50%', borderRadius: '50%',
fontWeight: 700, fontWeight: 700,
currentBgColor: '#808080',
} }
const customConnectorStyle = { const customConnectorStyle = {

View File

@ -1,4 +1,4 @@
import { YStack } from 'tamagui' import { YStack, Stack } from 'tamagui'
import { Text } from '@status-im/components' import { Text } from '@status-im/components'
import OverviewCard from './OverviewCard' import OverviewCard from './OverviewCard'
@ -12,10 +12,17 @@ const Overview = () => {
imgHeight="250%" imgHeight="250%"
rightImageSrc="./background-images/sync-status-background.png" rightImageSrc="./background-images/sync-status-background.png"
> >
<YStack space={'$5'} marginTop={'2rem'} width="100%"> <YStack
<Text size={27} weight={'semibold'}> space={'$5'}
Overview marginTop={'2rem'}
</Text> marginBottom={'2rem'}
width="100%"
>
<Stack marginBottom={'$2'}>
<Text size={27} weight={'semibold'}>
Overview
</Text>
</Stack>
<Text size={19}> <Text size={19}>
Becoming a validator is a big responsibility with important Becoming a validator is a big responsibility with important
preparation steps. Only start the deposit process when you're ready. preparation steps. Only start the deposit process when you're ready.

View File

@ -5,6 +5,7 @@
flex-wrap: wrap; flex-wrap: wrap;
width: 250%; width: 250%;
} }
.overviewCard { .overviewCard {
border-radius: 16px; border-radius: 16px;
border: 1px solid rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.15);
@ -19,6 +20,5 @@
} }
.overviewCard { .overviewCard {
width: 35%; width: 35%;
margin-bottom: 16px;
} }
} }

View File

@ -4,23 +4,15 @@ import { useSelector } from 'react-redux'
import { RootState } from '../../redux/store' import { RootState } from '../../redux/store'
import FormStepper from './FormStepper/FormStepper' import FormStepper from './FormStepper/FormStepper'
import Titles from '../../components/General/Titles' import Titles from '../../components/General/Titles'
import Overview from './Overview/Overview'
import KeyGeneration from './KeyGeneration/KeyGeneration'
import Activation from './Activation/Activation'
import ValidatorBoxWrapper from './ValidatorBoxWrapper/ValidatorBoxWrapper' import ValidatorBoxWrapper from './ValidatorBoxWrapper/ValidatorBoxWrapper'
import ClientSetup from './ClientSetup/ClientSetup'
import ConsensusSelection from './ValidatorSetup/ConsensusClient/ConsensusSelection'
import Advisories from './Advisories/Advisories'
import ValidatorSetup from './ValidatorSetup/ValidatorSetup/ValidatorSetup'
import ValidatorSetupInstall from './ValidatorSetup/ValidatorInstalling/ValidatorInstall'
import ContinueButton from './ContinueButton' import ContinueButton from './ContinueButton'
import ActivationValidatorSetup from './ValidatorSetup/ValidatorActivation/ActivationValidatorSetup'
import Deposit from './Deposit/Deposit'
import { useWindowSize } from '../../hooks/useWindowSize' import { useWindowSize } from '../../hooks/useWindowSize'
import styles from './layoutGradient.module.css' import styles from './layoutGradient.module.css'
import { Outlet } from 'react-router-dom'
const ValidatorOnboarding = () => { const ValidatorOnboarding = () => {
const { activeStep, subStepValidatorSetup } = useSelector( const { activeStep } = useSelector(
(state: RootState) => state.validatorOnboarding, (state: RootState) => state.validatorOnboarding,
) )
const windowSize = useWindowSize() const windowSize = useWindowSize()
@ -42,45 +34,7 @@ const ValidatorOnboarding = () => {
/> />
<FormStepper activeStep={activeStep} /> <FormStepper activeStep={activeStep} />
<ValidatorBoxWrapper> <ValidatorBoxWrapper>
{activeStep === 0 && <Overview />} <Outlet />
{activeStep === 1 && <Advisories />}
{activeStep === 2 && subStepValidatorSetup === 0 && (
<ValidatorSetup />
)}
{activeStep === 2 && subStepValidatorSetup === 1 && (
<ValidatorSetupInstall />
)}
{activeStep === 2 && subStepValidatorSetup === 2 && (
<ConsensusSelection />
)}
{activeStep === 2 && subStepValidatorSetup === 3 && (
<ActivationValidatorSetup />
)}
{activeStep === 3 && <ClientSetup />}
{activeStep === 4 && <KeyGeneration />}
{activeStep === 5 && <Deposit />}
{activeStep === 6 && (
<Activation
validatorsValue="4"
executionSyncStatus1={{
text: 'Execution Sync Status',
isGaugeIncluded: true,
gaugeColor: '$blue',
gaugeSynced: 123.524,
gaugeTotal: 172.503,
}}
executionSyncStatus2={{
text: 'Execution Sync Status',
isGaugeIncluded: true,
gaugeColor: '$red',
gaugeSynced: 123.524,
gaugeTotal: 172.503,
}}
currentAPRValue="4.40%"
estimatedActivationTimeValue="32 Days"
validatorQueueValue="92603"
/>
)}
</ValidatorBoxWrapper> </ValidatorBoxWrapper>
<ContinueButton /> <ContinueButton />
</YStack> </YStack>

View File

@ -2,6 +2,7 @@ import { Stack, YStack } from 'tamagui'
import { Text } from '@status-im/components' import { Text } from '@status-im/components'
import Icon from '../../../../components/General/Icon' import Icon from '../../../../components/General/Icon'
import { useWindowSize } from '../../../../hooks/useWindowSize'
type ConsensusClientCardProps = { type ConsensusClientCardProps = {
name: string name: string
@ -9,6 +10,7 @@ type ConsensusClientCardProps = {
} }
const ConsensusClientCard = ({ name, icon }: ConsensusClientCardProps) => { const ConsensusClientCard = ({ name, icon }: ConsensusClientCardProps) => {
const windowSize = useWindowSize()
return ( return (
<YStack <YStack
style={{ style={{
@ -16,8 +18,9 @@ const ConsensusClientCard = ({ name, icon }: ConsensusClientCardProps) => {
border: '1px solid #2A4AF5', border: '1px solid #2A4AF5',
borderRadius: '16px', borderRadius: '16px',
padding: '12px 16px', padding: '12px 16px',
width: '29%',
cursor: 'pointer', cursor: 'pointer',
minWidth: '250px',
width: windowSize.width < 780 ? '100%' : 'auto',
}} }}
space={'$8'} space={'$8'}
> >

View File

@ -3,8 +3,8 @@ import { InfoBadgeIcon } from '@status-im/icons'
import { Text } from '@status-im/components' import { Text } from '@status-im/components'
import StandardGauge from '../../../../components/Charts/StandardGauge' import StandardGauge from '../../../../components/Charts/StandardGauge'
import BorderBox from '../../../../components/General/BorderBox'
import { formatNumbersWithComa } from '../../../../utilities' import { formatNumbersWithComa } from '../../../../utilities'
import { useWindowSize } from '../../../../hooks/useWindowSize'
type ConsensusGaugeCardProps = { type ConsensusGaugeCardProps = {
synced: number synced: number
@ -19,43 +19,56 @@ const ConsensusGaugeCard = ({
title, title,
color, color,
}: ConsensusGaugeCardProps) => { }: ConsensusGaugeCardProps) => {
const windowSize = useWindowSize()
return ( return (
<BorderBox style={{ borderRadius: '10.1px', borderWidth: '0.5px' }}> <Stack
<XStack space={'$2'} alignItems="center"> style={{
<Stack border: '1px solid #DCE0E5',
style={{ borderRadius: '10px',
height: '35px', padding: '6px 12px',
width: '35px', borderWidth: '0.5px',
}} width: windowSize.width < 580 ? '100%' : 'auto',
> height: '100%',
<StandardGauge }}
data={[ >
{ <XStack space={'$2'} alignItems="center" justifyContent="space-between">
id: title, <XStack space={'$3'}>
label: title, <Stack
value: synced, style={{
color: color, height: '35px',
}, width: '35px',
{ }}
id: 'free', space={'$2'}
label: 'free', >
value: total - synced || 1, <StandardGauge
color: '#E7EAEE', data={[
}, {
]} id: title,
/> label: title,
</Stack> value: synced,
<YStack> color: color,
<Text size={11} color="#84888e" weight={'semibold'}> },
{title} {
</Text> id: 'free',
<Text size={15} weight={'semibold'}> label: 'free',
{formatNumbersWithComa(synced)} / {formatNumbersWithComa(total)} value: total - synced || 1,
</Text> color: '#E7EAEE',
</YStack> },
]}
/>
</Stack>
<YStack>
<Text size={11} color="#84888e" weight={'semibold'}>
{title}
</Text>
<Text size={15} weight={'semibold'}>
{formatNumbersWithComa(synced)} / {formatNumbersWithComa(total)}
</Text>
</YStack>
</XStack>
<InfoBadgeIcon size={20} color="#A1ABBD" /> <InfoBadgeIcon size={20} color="#A1ABBD" />
</XStack> </XStack>
</BorderBox> </Stack>
) )
} }

View File

@ -7,6 +7,7 @@ import ConsensusGaugeCard from './ConsensusGaugeCard'
import ConsensusClientCard from './ConsensusClientCard' import ConsensusClientCard from './ConsensusClientCard'
import LinkWithArrow from '../../../../components/General/LinkWithArrow' import LinkWithArrow from '../../../../components/General/LinkWithArrow'
import { RootState } from '../../../../redux/store' import { RootState } from '../../../../redux/store'
import { useWindowSize } from '../../../../hooks/useWindowSize'
const clientIcons = { const clientIcons = {
Nethermind: '/icons/nethermind-circle.png', Nethermind: '/icons/nethermind-circle.png',
@ -17,6 +18,7 @@ const clientIcons = {
} }
const ConsensusSelection = () => { const ConsensusSelection = () => {
const windowSize = useWindowSize()
const selectedClient = useSelector( const selectedClient = useSelector(
(state: RootState) => state.execClient.selectedClient, (state: RootState) => state.execClient.selectedClient,
) as 'Nethermind' | 'Besu' | 'Geth' | 'Erigon' | 'Nimbus' ) as 'Nethermind' | 'Besu' | 'Geth' | 'Erigon' | 'Nimbus'
@ -37,9 +39,14 @@ const ConsensusSelection = () => {
flexWrap="wrap" flexWrap="wrap"
> >
<Text size={27} weight={'semibold'}> <Text size={27} weight={'semibold'}>
Validator Setup Client Setup
</Text> </Text>
<XStack space={'$2'} flexWrap="wrap"> <XStack
space={'$2'}
flexWrap={windowSize.width < 735 ? 'wrap' : 'nowrap'}
marginTop={windowSize.width < 735 ? '20px' : 0}
width={windowSize.width < 580 ? '100%' : 'auto'}
>
<PairedDeviceCard /> <PairedDeviceCard />
<ConsensusGaugeCard <ConsensusGaugeCard
@ -76,7 +83,7 @@ const ConsensusSelection = () => {
<XStack space={'$8'} flexWrap="wrap"> <XStack space={'$8'} flexWrap="wrap">
<ConsensusClientCard name={clients[0].name} icon={clients[0].icon} /> <ConsensusClientCard name={clients[0].name} icon={clients[0].icon} />
<YStack width={'67%'} maxWidth="550px" space={'$4'}> <YStack width={windowSize.width < 780 ? '100%' : '70%'} space={'$4'}>
<Text size={19}>The resource efficient Ethereum Clients.</Text> <Text size={19}>The resource efficient Ethereum Clients.</Text>
<Text size={15}> <Text size={15}>
{selectedClient} is a client implementation for both execution and {selectedClient} is a client implementation for both execution and

View File

@ -1,8 +1,10 @@
import { XStack, YStack } from 'tamagui' import { XStack, YStack } from 'tamagui'
import { InfoBadgeIcon } from '@status-im/icons' import { InfoBadgeIcon } from '@status-im/icons'
import { Avatar, Text } from '@status-im/components' import { Avatar, Text } from '@status-im/components'
import { useWindowSize } from '../../../../hooks/useWindowSize'
const PairedDeviceCard = () => { const PairedDeviceCard = () => {
const windowSize = useWindowSize()
return ( return (
<XStack <XStack
space={'$7'} space={'$7'}
@ -10,10 +12,13 @@ const PairedDeviceCard = () => {
padding: '6px 12px', padding: '6px 12px',
border: '1px solid #DCE0E5', border: '1px solid #DCE0E5',
borderRadius: '10px', borderRadius: '10px',
marginBottom: '20px',
width: windowSize.width < 580 ? '100%' : 'auto',
}} }}
justifyContent="space-between"
alignItems={'center'} alignItems={'center'}
> >
<XStack space={'$3'} alignItems={'center'}> <XStack space={'$3'}>
<Avatar <Avatar
backgroundColor="pink" backgroundColor="pink"
type="icon" type="icon"

View File

@ -4,10 +4,11 @@ import { Text } from '@status-im/components'
import Confetti from 'react-confetti' import Confetti from 'react-confetti'
import ActivationCard from '../../Activation/ActivationCard' import ActivationCard from '../../Activation/ActivationCard'
import { useWindowSize } from '../../../../hooks/useWindowSize'
const ActivationValidatorSetup = () => { const ActivationValidatorSetup = () => {
const [showConfetti, setShowConfetti] = useState(true) const [showConfetti, setShowConfetti] = useState(true)
const windowSize = useWindowSize()
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
setShowConfetti(false) setShowConfetti(false)
@ -19,7 +20,7 @@ const ActivationValidatorSetup = () => {
}, []) }, [])
return ( return (
<Stack style={styles.confettiContainer} width={'100%'} minHeight={'65vh'}> <Stack style={styles.confettiContainer} minHeight={'65vh'}>
{showConfetti && <Confetti style={styles.confettiCanvas} />} {showConfetti && <Confetti style={styles.confettiCanvas} />}
<YStack style={{ padding: '26px 32px' }}> <YStack style={{ padding: '26px 32px' }}>
<YStack space={'$5'}> <YStack space={'$5'}>
@ -34,8 +35,11 @@ const ActivationValidatorSetup = () => {
making a deposit. making a deposit.
</Text> </Text>
</Stack> </Stack>
<YStack space={'$3'} marginTop={'25px'} width={'33%'}> <YStack marginTop={'25px'}>
<XStack width={'151%'} space={'$3'}> <XStack
width="100%"
flexWrap={windowSize.width < 780 ? 'wrap' : 'nowrap'}
>
<ActivationCard <ActivationCard
text="Execution Sync Status" text="Execution Sync Status"
isGaugeIncluded={true} isGaugeIncluded={true}
@ -51,7 +55,10 @@ const ActivationValidatorSetup = () => {
gaugeTotal={172.503} gaugeTotal={172.503}
/> />
</XStack> </XStack>
<XStack space={'$3'}> <XStack
flexWrap={windowSize.width < 780 ? 'wrap' : 'nowrap'}
width="100%"
>
<ActivationCard text="Validator Queue" value="92603" /> <ActivationCard text="Validator Queue" value="92603" />
<ActivationCard <ActivationCard
text="Estimated Activation Time" text="Estimated Activation Time"

View File

@ -1,4 +1,4 @@
.osCardsContainer { .os-cards-container {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
display: grid; display: grid;
@ -7,36 +7,45 @@
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
} }
.osCard { .os-card {
border: 1px solid rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 16px; border-radius: 16px;
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;
box-sizing: border-box; box-sizing: border-box;
} }
.markdown-text {
.osCardSelected { overflow-x: auto;
width: 100%;
-ms-overflow-style: none;
}
.os-card-selected {
background-color: #2a4af50d; background-color: #2a4af50d;
border: 1px solid #2a4af566; border: 1px solid #2a4af566;
border-radius: 16px;
padding: 12px 16px;
cursor: pointer;
box-sizing: border-box;
} }
@media (max-width: 1000px) { @media (max-width: 1000px) {
.osCardsContainer { .os-cards-container {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
.osCard:nth-child(3) { .os-card:nth-child(3) {
width: 205%; width: 205%;
} }
} }
@media (max-width: 750px) { @media (max-width: 850px) {
.osCardsContainer { .os-cards-container {
grid-template-columns: repeat(1, 1fr); grid-template-columns: repeat(1, 1fr);
} }
.osCard { .os-card {
width: 100%; width: 100%;
} }
.osCard:nth-child(3) { .os-card:nth-child(3) {
width: 100%; width: 100%;
} }
} }

View File

@ -14,13 +14,15 @@ type OSCardsProps = {
} }
const OSCards = ({ selectedOS, handleOSCardClick }: OSCardsProps) => { const OSCards = ({ selectedOS, handleOSCardClick }: OSCardsProps) => {
return ( return (
<div className={styles.osCardsContainer}> <div className={styles['os-cards-container']}>
{cards.map(card => ( {cards.map(card => (
<div <div
key={card.name} key={card.name}
className={`${styles.osCard} ${ className={
selectedOS === card.name ? styles.osCardSelected : '' selectedOS === card.name
}`} ? styles['os-card-selected']
: styles['os-card']
}
onClick={() => handleOSCardClick(card.name)} onClick={() => handleOSCardClick(card.name)}
> >
<OSCard key={card.name} icon={card.icon} name={card.name} /> <OSCard key={card.name} icon={card.icon} name={card.name} />

View File

@ -1,6 +1,6 @@
import { Stack, YStack } from 'tamagui' import { Stack, YStack } from 'tamagui'
import { Text } from '@status-im/components' import { Text } from '@status-im/components'
import { useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { useState } from 'react' import { useState } from 'react'
import { RootState } from '../../../../redux/store' import { RootState } from '../../../../redux/store'
@ -8,21 +8,56 @@ import { DOCUMENTATIONS } from './documentations'
import { MAC } from '../../../../constants' import { MAC } from '../../../../constants'
import OSCards from './OSCards' import OSCards from './OSCards'
import Markdown from '../../../../components/General/Markdown/Markdown' import Markdown from '../../../../components/General/Markdown/Markdown'
import { setPinnedMessage } from '../../../../redux/PinnedMessage/slice'
function extractBashCommands(documentation: any) {
const bashCommandRegex = /```bash\n([\s\S]*?)\n```/g
const matches = []
let match
while ((match = bashCommandRegex.exec(documentation)) !== null) {
matches.push(match[1])
}
return matches.join('\n\n')
}
const ValidatorSetupInstall = () => { const ValidatorSetupInstall = () => {
const dispatch = useDispatch()
const [selectedOS, setSelectedOS] = useState(MAC) const [selectedOS, setSelectedOS] = useState(MAC)
const selectedClient = useSelector( const selectedClient = useSelector(
(state: RootState) => state.execClient.selectedClient, (state: RootState) => state.execClient.selectedClient,
) )
const docText = DOCUMENTATIONS[selectedClient].documentation[selectedOS]
const bashCommands = extractBashCommands(docText)
const handleOSCardClick = (os: string) => { const handleOSCardClick = (os: string) => {
setSelectedOS(os) setSelectedOS(os)
} }
const copyCommands = () => {
navigator.clipboard.writeText(bashCommands)
dispatch(
setPinnedMessage({
id: '123',
text: 'You have successfully copied the commands to your clipboard.',
pinned: true,
}),
)
setTimeout(() => {
dispatch(
setPinnedMessage({
id: '123',
text: 'You have successfully copied the commands to your clipboard.',
pinned: false,
}),
)
}, 3000)
}
return ( return (
<YStack style={{ padding: '26px 32px', width: 'fit-content' }}> <YStack style={{ padding: '26px 32px', width: '100%' }}>
<Text size={27} weight={'semibold'}> <Text size={27} weight={'semibold'}>
Validator Setup Client Setup
</Text> </Text>
<YStack <YStack
style={{ style={{
@ -43,9 +78,13 @@ const ValidatorSetupInstall = () => {
selectedOS={selectedOS} selectedOS={selectedOS}
handleOSCardClick={handleOSCardClick} handleOSCardClick={handleOSCardClick}
/> />
<Markdown <Stack onPress={() => copyCommands()}>
children={DOCUMENTATIONS[selectedClient].documentation[selectedOS]} <Markdown
/> children={
DOCUMENTATIONS[selectedClient].documentation[selectedOS]
}
/>
</Stack>
</Stack> </Stack>
</YStack> </YStack>
</YStack> </YStack>

View File

@ -19,7 +19,7 @@ const ValidatorSetup = () => {
space={'$8'} space={'$8'}
> >
<Text size={27} weight={'semibold'}> <Text size={27} weight={'semibold'}>
Validator Setup Client Setup
</Text> </Text>
<PairedDeviceCard /> <PairedDeviceCard />
</XStack> </XStack>