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 './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 walletConnect = walletConnectModule(wcV2InitOptions)
@ -55,10 +67,35 @@ const router = createBrowserRouter([
element: <PairDevice />,
},
{ 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: '/logs', element: <LogsPage /> },
{ path: '/validator-management', element: <ValidatorManagement /> },
{
path: '/validator-management',
element: <ValidatorManagement />,
},
])
function App() {

View File

@ -1,5 +1,5 @@
import ReactMarkdown from 'react-markdown'
import styles from './../../../pages/ValidatorOnboarding/ValidatorSetup/ValidatorInstalling/InstallLayout.module.css'
import MarkdownLink from './MarkdownLink'
type MarkdownProps = {
@ -7,7 +7,13 @@ type 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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,18 +8,17 @@ 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 { setActiveStep } from '../../redux/ValidatorOnboarding/slice'
import { KEYSTORE_FILES } from '../../constants'
import {
setIsConfirmPhraseStage,
setIsCopyPastedPhrase,
setValidWords,
} from '../../redux/ValidatorOnboarding/KeyGeneration/slice'
import { useWindowSize } from '../../hooks/useWindowSize'
const ContinueButton = () => {
const windowSize = useWindowSize()
const [isDisabled, setIsDisabled] = useState(false)
const {
isCopyPastedPhrase,
@ -33,24 +32,44 @@ const ContinueButton = () => {
const { activeStep, subStepValidatorSetup } = useSelector(
(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(
(state: RootState) => state.validatorSetup,
)
const dispatch = useDispatch()
const navigate = useNavigate()
const isActivationValScreen = activeStep === 3 && subStepValidatorSetup === 3
const isActivationValScreen = activeStep === 5
useEffect(() => {
const getDisabledButton = () => {
if (activeStep === 4 && isConfirmPhraseStage) {
if (activeStep === 7 && isConfirmPhraseStage) {
if (
validWords.some(w => w === false) ||
generatedMnemonic.some((w, i) => w !== mnemonic[i])
) {
return true
}
} else if (activeStep === 3 && !isValidatorSet) {
} else if (activeStep === 6 && !isValidatorSet) {
return true
}
return false
@ -66,22 +85,10 @@ const ContinueButton = () => {
isValidatorSet,
])
const handleStep1 = () => {
dispatch(setActiveStep(activeStep + 1))
}
const handleStep2 = () => {
if (subStepValidatorSetup === 3) {
return dispatch(setActiveStep(activeStep + 1))
}
dispatch(setSubStepValidatorSetup(subStepValidatorSetup + 1))
}
const handleStep4 = () => {
const handleRecoveryMechanism = () => {
if (!isConfirmPhraseStage && recoveryMechanism === KEYSTORE_FILES) {
return dispatch(setActiveStep(activeStep + 1))
}
if (!isConfirmPhraseStage) {
return dispatch(setIsConfirmPhraseStage(true))
}
@ -102,26 +109,43 @@ const ContinueButton = () => {
}
const continueHandler = () => {
if (activeStep === 1) {
handleStep1()
} else if (activeStep === 2) {
handleStep2()
} else if (activeStep === 4) {
handleStep4()
} else {
if (activeStep < 6) {
dispatch(setActiveStep(activeStep + 1))
let nextPath
if (activeStep === 0) nextPath = '/validator-onboarding/advisories'
else if (activeStep === 1)
nextPath = '/validator-onboarding/validator-setup'
else if (activeStep === 2)
nextPath = '/validator-onboarding/validator-setup-install'
else if (activeStep === 3)
nextPath = '/validator-onboarding/consensus-selection'
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 {
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 (
<XStack
style={{
width: '100%',
justifyContent: isActivationValScreen ? 'space-between' : 'end',
justifyContent: isActivationValScreen
? 'space-between'
: windowSize.width < 560
? 'start'
: 'end',
alignItems: 'center',
zIndex: 1000,
marginTop: '21px',
@ -145,7 +169,7 @@ const ContinueButton = () => {
/>
)}
<Button onPress={continueHandler} size={40} disabled={isDisabled}>
{activeStep < 6 ? 'Continue' : 'Continue to Dashboard'}
{activeStep < 9 ? 'Continue' : 'Continue to Dashboard'}
</Button>
</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 { useDispatch } from 'react-redux'
import { setActiveStep } from '../../../redux/ValidatorOnboarding/slice'
import { useNavigate } from 'react-router-dom'
import { FORM_STEPS } from '../../../constants'
import { useWindowSize } from '../../../hooks/useWindowSize'
import './FormStepper.css'
@ -11,9 +9,22 @@ type FormStepperProps = {
}
const FormStepper = ({ activeStep }: FormStepperProps) => {
const dispatch = useDispatch()
const navigate = useNavigate()
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 = (
index: number,
stepsBefore: number,
@ -23,7 +34,6 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
let start = activeStep - stepsBefore
let end = activeStep + stepsAfter
// active step is near the start or end
if (start < 0) {
end -= start
start = 0
@ -41,23 +51,27 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
const isStepVisible = (index: number) => {
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) {
return getIsStepVisible(index, 1, 2) // 4 steps
return getIsStepVisible(index, 1, 2)
} else if (windowSize.width < 1183) {
return getIsStepVisible(index, 1, 3) // 5 steps
return getIsStepVisible(index, 1, 3)
} else if (windowSize.width < 1300) {
return getIsStepVisible(index, 2, 3) // 6 steps
return getIsStepVisible(index, 2, 3)
} else {
return true
}
}
const changeStepOnClickHandler = (index: number) => {
if (activeStep > index) {
dispatch(setActiveStep(index))
const path = stepToUrlMap[index]
if (path && index < activeStep) {
navigate(path)
}
}
if (activeStep > 1) {
activeStep = activeStep < 6 ? 2 : activeStep - 3
}
return (
<Stepper
@ -85,12 +99,18 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
completed={activeStep > originalIndex - 1}
data-subtitle={step.subtitle}
data-step={step.label}
style={
originalIndex === activeStep
? { backgroundColor: stepStyle.currentBgColor }
: {}
}
/>
)
})}
</Stepper>
)
}
const stepStyle = {
// For default dots:
inactiveBgColor: '#FFFFFF',
@ -107,9 +127,10 @@ const stepStyle = {
inactiveTextColor: '#000000',
size: '28px',
circleFontSize: '0px',
labelFontSize: '14px',
labelFontSize: '13px',
borderRadius: '50%',
fontWeight: 700,
currentBgColor: '#808080',
}
const customConnectorStyle = {

View File

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

View File

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

View File

@ -4,23 +4,15 @@ import { useSelector } from 'react-redux'
import { RootState } from '../../redux/store'
import FormStepper from './FormStepper/FormStepper'
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 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 ActivationValidatorSetup from './ValidatorSetup/ValidatorActivation/ActivationValidatorSetup'
import Deposit from './Deposit/Deposit'
import { useWindowSize } from '../../hooks/useWindowSize'
import styles from './layoutGradient.module.css'
import { Outlet } from 'react-router-dom'
const ValidatorOnboarding = () => {
const { activeStep, subStepValidatorSetup } = useSelector(
const { activeStep } = useSelector(
(state: RootState) => state.validatorOnboarding,
)
const windowSize = useWindowSize()
@ -42,45 +34,7 @@ const ValidatorOnboarding = () => {
/>
<FormStepper activeStep={activeStep} />
<ValidatorBoxWrapper>
{activeStep === 0 && <Overview />}
{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"
/>
)}
<Outlet />
</ValidatorBoxWrapper>
<ContinueButton />
</YStack>

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import ConsensusGaugeCard from './ConsensusGaugeCard'
import ConsensusClientCard from './ConsensusClientCard'
import LinkWithArrow from '../../../../components/General/LinkWithArrow'
import { RootState } from '../../../../redux/store'
import { useWindowSize } from '../../../../hooks/useWindowSize'
const clientIcons = {
Nethermind: '/icons/nethermind-circle.png',
@ -17,6 +18,7 @@ const clientIcons = {
}
const ConsensusSelection = () => {
const windowSize = useWindowSize()
const selectedClient = useSelector(
(state: RootState) => state.execClient.selectedClient,
) as 'Nethermind' | 'Besu' | 'Geth' | 'Erigon' | 'Nimbus'
@ -37,9 +39,14 @@ const ConsensusSelection = () => {
flexWrap="wrap"
>
<Text size={27} weight={'semibold'}>
Validator Setup
Client Setup
</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 />
<ConsensusGaugeCard
@ -76,7 +83,7 @@ const ConsensusSelection = () => {
<XStack space={'$8'} flexWrap="wrap">
<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={15}>
{selectedClient} is a client implementation for both execution and

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { Stack, YStack } from 'tamagui'
import { Text } from '@status-im/components'
import { useSelector } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import { useState } from 'react'
import { RootState } from '../../../../redux/store'
@ -8,21 +8,56 @@ import { DOCUMENTATIONS } from './documentations'
import { MAC } from '../../../../constants'
import OSCards from './OSCards'
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 dispatch = useDispatch()
const [selectedOS, setSelectedOS] = useState(MAC)
const selectedClient = useSelector(
(state: RootState) => state.execClient.selectedClient,
)
const docText = DOCUMENTATIONS[selectedClient].documentation[selectedOS]
const bashCommands = extractBashCommands(docText)
const handleOSCardClick = (os: string) => {
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 (
<YStack style={{ padding: '26px 32px', width: 'fit-content' }}>
<YStack style={{ padding: '26px 32px', width: '100%' }}>
<Text size={27} weight={'semibold'}>
Validator Setup
Client Setup
</Text>
<YStack
style={{
@ -43,9 +78,13 @@ const ValidatorSetupInstall = () => {
selectedOS={selectedOS}
handleOSCardClick={handleOSCardClick}
/>
<Markdown
children={DOCUMENTATIONS[selectedClient].documentation[selectedOS]}
/>
<Stack onPress={() => copyCommands()}>
<Markdown
children={
DOCUMENTATIONS[selectedClient].documentation[selectedOS]
}
/>
</Stack>
</Stack>
</YStack>
</YStack>

View File

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