Merge pull request #36 from nimbus-gui/hn.validator-onboarding-responsive
Validator onboarding responsive
This commit is contained in:
commit
4d145760ec
|
@ -1,4 +1,4 @@
|
|||
import { Image } from '@status-im/components'
|
||||
import { Image } from 'tamagui'
|
||||
|
||||
export type IconProps = {
|
||||
src: string
|
||||
|
@ -6,16 +6,8 @@ export type IconProps = {
|
|||
height?: number
|
||||
}
|
||||
|
||||
const Icon = ({ src, width = 16, height = 16 }: IconProps) => {
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
source={{ uri: src }}
|
||||
width={width}
|
||||
height={height}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
/>
|
||||
)
|
||||
const Icon = ({ src, height = 100, width = 100 }: IconProps) => {
|
||||
return <Image src={src} source={{ uri: src }} height={height} width={width} />
|
||||
}
|
||||
|
||||
export default Icon
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { CSSProperties, ReactNode } from 'react'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
|
||||
type ResponsiveStackProps = {
|
||||
isVerticalAligned?: boolean
|
||||
children: ReactNode
|
||||
space?: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
const ResponsiveStack = ({ isVerticalAligned, children, space, style }: ResponsiveStackProps) => {
|
||||
if (isVerticalAligned) {
|
||||
return (
|
||||
<YStack space={space} style={style}>
|
||||
{children}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<XStack space={space} style={style}>
|
||||
{children}
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResponsiveStack
|
|
@ -1,5 +1,6 @@
|
|||
import { Avatar, Text } from '@status-im/components'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { CopyIcon } from '@status-im/icons'
|
||||
|
||||
import AddCard from '../AddCards/AddCard'
|
||||
import AlertsList from './AlertsList'
|
||||
|
@ -7,9 +8,13 @@ import LogsList from './LogsList'
|
|||
import DiamondCard from './DiamondCard'
|
||||
import ValidatorsCount from './ValidatorsCount'
|
||||
import ValidatorsTabs from './ValidatorsTabs/ValidatorsTabs'
|
||||
import { getFormattedWalletAddress } from '../../../utilities'
|
||||
import { copyFunction, getFormattedWalletAddress } from '../../../utilities'
|
||||
|
||||
const RightSidebar = () => {
|
||||
const onCopyWalletAddress = () => {
|
||||
copyFunction('0xb9dc35')
|
||||
}
|
||||
|
||||
return (
|
||||
<YStack
|
||||
width={'320px'}
|
||||
|
@ -23,13 +28,21 @@ const RightSidebar = () => {
|
|||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
<XStack alignItems="center">
|
||||
<XStack alignItems="center" space={'$2'}>
|
||||
<Avatar type="user" size={32} name="Ethereum Mainnet" />
|
||||
<YStack pl="8px">
|
||||
<YStack>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
Ethereum Mainnet
|
||||
</Text>
|
||||
<Text size={13}>{getFormattedWalletAddress('0xb9dc35')}</Text>
|
||||
<XStack space={'$1'} alignItems="center">
|
||||
<Text size={13}>{getFormattedWalletAddress('0xb9dc35')}</Text>
|
||||
<CopyIcon
|
||||
size={16}
|
||||
color="#647084"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={onCopyWalletAddress}
|
||||
/>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack space={'$2'} alignItems="center" justifyContent="space-between">
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { useState } from 'react'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { Avatar, Checkbox, Text } from '@status-im/components'
|
||||
import { VerifiedIcon, ContactIcon } from '@status-im/icons'
|
||||
import { XStack } from 'tamagui'
|
||||
import { Avatar, Checkbox } from '@status-im/components'
|
||||
|
||||
import { getFormattedValidatorAddress } from '../../../../utilities'
|
||||
import ValidatorNameAddress from '../../ValidatorNameAddress'
|
||||
|
||||
type ValidatorListItemProps = {
|
||||
name: string
|
||||
|
@ -41,7 +40,7 @@ const ValidatorListItem = ({
|
|||
}}
|
||||
width="92%"
|
||||
>
|
||||
<XStack alignItems="center">
|
||||
<XStack alignItems="center" space={'$2'}>
|
||||
<Avatar
|
||||
type="user"
|
||||
size={32}
|
||||
|
@ -54,16 +53,12 @@ const ValidatorListItem = ({
|
|||
[11, 20],
|
||||
]}
|
||||
/>
|
||||
<YStack pl="8px">
|
||||
<XStack space={'$1'} alignItems="center">
|
||||
<Text size={13} weight={'semibold'}>
|
||||
Validator {name}
|
||||
</Text>
|
||||
{isVerified && <VerifiedIcon size={20} />}
|
||||
{isAvatarChipIncluded && <ContactIcon size={20} />}
|
||||
</XStack>
|
||||
<Text size={13}>{getFormattedValidatorAddress(validatorAddress)}</Text>
|
||||
</YStack>
|
||||
<ValidatorNameAddress
|
||||
name={name}
|
||||
address={validatorAddress}
|
||||
isVerified={isVerified}
|
||||
isAvatarChipIncluded={isAvatarChipIncluded}
|
||||
/>
|
||||
</XStack>
|
||||
{isSelected && <Checkbox id={name} variant="outline" size={20} selected={isSelected} />}
|
||||
</XStack>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import ValidatorNameAddress from './ValidatorNameAddress'
|
||||
|
||||
const meta = {
|
||||
title: 'General/ValidatorNameAddress',
|
||||
component: ValidatorNameAddress,
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof ValidatorNameAddress>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
name: '1',
|
||||
address: 'zQ3asdf9d4Gs0',
|
||||
},
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { CopyIcon, VerifiedIcon, ContactIcon, CheckIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { copyFunction, getFormattedValidatorAddress } from '../../utilities'
|
||||
|
||||
type ValidatorNameAddressProps = {
|
||||
name: string
|
||||
address: string
|
||||
isVerified?: boolean
|
||||
isAvatarChipIncluded?: boolean
|
||||
}
|
||||
|
||||
const ValidatorNameAddress = ({
|
||||
name,
|
||||
address,
|
||||
isVerified,
|
||||
isAvatarChipIncluded,
|
||||
}: ValidatorNameAddressProps) => {
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
|
||||
const onCopyAddress = () => {
|
||||
copyFunction(address)
|
||||
|
||||
if (isCopied === false) {
|
||||
setIsCopied(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<YStack alignItems={'start'}>
|
||||
<XStack space={'$1'} alignItems="center">
|
||||
<Text size={13} weight={'semibold'}>
|
||||
Validator {name}
|
||||
</Text>
|
||||
{isVerified && <VerifiedIcon size={20} />}
|
||||
{isAvatarChipIncluded && <ContactIcon size={20} />}
|
||||
</XStack>
|
||||
<XStack space={'$1'} alignItems="center">
|
||||
<Text size={13} color="#647084">
|
||||
{getFormattedValidatorAddress(address)}
|
||||
</Text>
|
||||
{isCopied ? (
|
||||
<CheckIcon size={16} color="#647084" />
|
||||
) : (
|
||||
<CopyIcon
|
||||
size={16}
|
||||
color="#647084"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={onCopyAddress}
|
||||
/>
|
||||
)}
|
||||
</XStack>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ValidatorNameAddress
|
|
@ -13,7 +13,7 @@ type Story = StoryObj<typeof meta>
|
|||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
number: 1,
|
||||
name: '1',
|
||||
address: 'zQ3asdf9d4Gs0',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,31 +1,24 @@
|
|||
import { Avatar, Text } from '@status-im/components'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { Avatar } from '@status-im/components'
|
||||
import { XStack } from 'tamagui'
|
||||
|
||||
import { getFormattedValidatorAddress } from '../../utilities'
|
||||
import ValidatorNameAddress from './ValidatorNameAddress'
|
||||
|
||||
type ValidatorProfileProps = {
|
||||
number: number
|
||||
name: string
|
||||
address: string
|
||||
}
|
||||
|
||||
const ValidatorProfile = ({ number, address }: ValidatorProfileProps) => {
|
||||
const ValidatorProfile = ({ name, address }: ValidatorProfileProps) => {
|
||||
return (
|
||||
<XStack space={'$2'}>
|
||||
<Avatar
|
||||
type="user"
|
||||
size={32}
|
||||
src="/icons/validator-request.svg"
|
||||
name={number.toString()}
|
||||
name={name}
|
||||
indicator="online"
|
||||
/>
|
||||
<YStack>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
Validator {number}
|
||||
</Text>
|
||||
<Text size={13} color="#647084">
|
||||
{getFormattedValidatorAddress(address)}
|
||||
</Text>
|
||||
</YStack>
|
||||
<ValidatorNameAddress name={name} address={address} />
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useState } from 'react'
|
||||
import { Input, Text } from '@status-im/components'
|
||||
import { AddIcon } from '@status-im/icons'
|
||||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
import { Stack, XStack, YStack, useMedia } from 'tamagui'
|
||||
|
||||
import { CURRENCIES, ETH_PER_VALIDATOR } from '../../constants'
|
||||
import CurrencyDropdown from './CurrencyDropdown'
|
||||
import ResponsiveStack from './ResponsiveStack'
|
||||
|
||||
type ValidatorsMenuWithPriceProps = {
|
||||
validatorCount: number
|
||||
|
@ -20,6 +21,7 @@ const ValidatorsMenuWithPrice = ({
|
|||
label,
|
||||
}: ValidatorsMenuWithPriceProps) => {
|
||||
const [currency, setCurrency] = useState(Object.keys(CURRENCIES)[0] as CurrencyType)
|
||||
const media = useMedia()
|
||||
|
||||
const changeCurrency = (currency: CurrencyType) => {
|
||||
if (CURRENCIES[currency]) {
|
||||
|
@ -31,7 +33,11 @@ const ValidatorsMenuWithPrice = ({
|
|||
const totalPrice = totalETH * CURRENCIES[currency as keyof typeof CURRENCIES]
|
||||
|
||||
return (
|
||||
<XStack justifyContent={'space-between'} width={'80%'}>
|
||||
<ResponsiveStack
|
||||
isVerticalAligned={media.sm}
|
||||
style={{ justifyContent: 'space-between', width: media.lg ? '100%' : '80%' }}
|
||||
space={'$2'}
|
||||
>
|
||||
<Stack space={'$2'}>
|
||||
<Text size={15} weight="regular" color={'#647084'}>
|
||||
{label}
|
||||
|
@ -49,26 +55,30 @@ const ValidatorsMenuWithPrice = ({
|
|||
onChangeText={changeValidatorCountHandler}
|
||||
/>
|
||||
</Stack>
|
||||
<YStack space={'$2'}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
ETH
|
||||
</Text>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
{totalETH}
|
||||
</Text>
|
||||
</YStack>
|
||||
<YStack space={'$2'}>
|
||||
<XStack style={{ justifyContent: 'space-between', width: '115%' }}>
|
||||
<XStack space={'$10'} style={{ justifyContent: 'space-between' }}>
|
||||
<YStack space={'$2'}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
{currency}
|
||||
ETH
|
||||
</Text>
|
||||
<CurrencyDropdown changeCurrency={changeCurrency} />
|
||||
</XStack>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
{totalPrice.toFixed(2)} {currency}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<Stack style={{ marginTop: '2px' }}>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
{totalETH}
|
||||
</Text>
|
||||
</Stack>
|
||||
</YStack>
|
||||
<YStack space={'$2'}>
|
||||
<XStack style={{ justifyContent: 'space-between' }}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
{currency}
|
||||
</Text>
|
||||
<CurrencyDropdown changeCurrency={changeCurrency} />
|
||||
</XStack>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
{totalPrice.toFixed(2)} {currency}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
</ResponsiveStack>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ReactNode } from 'react'
|
|||
import { useTheme } from 'tamagui'
|
||||
|
||||
import NimbusLogoMark from '../Logos/NimbusLogoMark'
|
||||
import './layout.css'
|
||||
import styles from './index.module.css'
|
||||
|
||||
type PageWrapperShadowProps = {
|
||||
breadcrumbBar?: ReactNode
|
||||
|
@ -22,19 +22,19 @@ const PageWrapperShadow = ({
|
|||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div className="layout" style={{ backgroundColor: theme.background.val }}>
|
||||
<section className="layout-left">
|
||||
<div className={styles['layout']} style={{ backgroundColor: theme.background.val }}>
|
||||
<section className={styles['layout-left']}>
|
||||
{breadcrumbBar}
|
||||
<div className="container">
|
||||
<div className="container-inner">{children}</div>
|
||||
<div className={styles['container']}>
|
||||
<div className={styles['container-inner']}>{children}</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="layout-right">
|
||||
<div className="image-container">
|
||||
<section className={styles['layout-right']}>
|
||||
<div className={styles['image-container']}>
|
||||
<img
|
||||
src={rightImageSrc}
|
||||
alt="background"
|
||||
className="background-img"
|
||||
className={styles['background-img']}
|
||||
style={{ height: imgHeight }}
|
||||
/>
|
||||
{rightImageLogo ? <NimbusLogoMark /> : null}
|
||||
|
|
|
@ -31,6 +31,49 @@ export const KEYSTORE_FILES = 'KeystoreFiles'
|
|||
export const RECOVERY_PHRASE = 'Recovery Phrase'
|
||||
export const BOTH_KEY_AND_RECOVERY = 'Both KeystoreFiles & Recovery Phrase'
|
||||
export const ETH_PER_VALIDATOR = 32
|
||||
export const FORM_STEPS = [
|
||||
{ label: 'Overview', subtitle: 'Get Started' },
|
||||
{ label: 'Advisories', subtitle: 'Understand your Duties' },
|
||||
{ label: 'Client Setup', subtitle: 'Execution & Consensus' },
|
||||
{ label: 'Validator Setup', subtitle: 'Validators & Withdrawal' },
|
||||
{ label: 'Key Generation', subtitle: 'Secure your Keypairs' },
|
||||
{ label: 'Deposit', subtitle: 'Stake your ETH' },
|
||||
{ label: 'Activation', subtitle: 'Complete Setup' },
|
||||
]
|
||||
export const ADVISORY_TOPICS: {
|
||||
[key: string]: string[]
|
||||
} = {
|
||||
'Proof of Stake': [
|
||||
'Proof of Stake systems require validators to hold and lock up a certain amount of cryptocurrency to participate.',
|
||||
'In Proof of Stake, the chances of creating a block is proportional to the amount of cryptocurrency held.',
|
||||
'Unlike Proof of Work, Proof of Stake aims to achieve consensus without intensive computational work.',
|
||||
],
|
||||
Deposit: [
|
||||
'Deposits are often irreversible, so ensure to double-check transaction details before confirming.',
|
||||
'Delay in deposit acknowledgment might be due to network congestion or node synchronization.',
|
||||
'Always keep transaction IDs or hashes for records and future references in case of disputes.',
|
||||
],
|
||||
'Key Management': [
|
||||
'Storing your private keys on a device connected to the internet is susceptible to hacks and malware.',
|
||||
'Hardware wallets provide an added layer of security by keeping private keys isolated from online systems.',
|
||||
'Regularly back up and encrypt your key management solutions to prevent potential losses.',
|
||||
],
|
||||
'Bad Behaviour': [
|
||||
'If you try to cheat the system, or act contrary to the specification, you will be liable to incur a penalty known as slashing.',
|
||||
'Running your validator keys simultaneously on two or more machines will result in slashing.*',
|
||||
'Simply being offline with an otherwise healthy network does not result in slashing, but will result in small inactivity penalties.',
|
||||
],
|
||||
Requirements: [
|
||||
'Ensure your system meets the minimum software and hardware requirements before initiating any operations.',
|
||||
'Staying updated with the latest versions is vital to maintain system integrity and performance.',
|
||||
'Failure to meet requirements might result in operational inefficiencies or security vulnerabilities.',
|
||||
],
|
||||
Risks: [
|
||||
'Cryptocurrency investments are subject to high volatility and can result in both significant gains and losses.',
|
||||
'Always do thorough research before making investment decisions or engaging in transactions.',
|
||||
'Be wary of phishing scams, malicious software, and too-good-to-be-true offers.',
|
||||
],
|
||||
}
|
||||
|
||||
export const MAC = 'MacOS'
|
||||
export const WINDOWS = 'Windows'
|
||||
|
@ -73,7 +116,7 @@ export const VALIDATOR_TABS_MANAGEMENT = [
|
|||
|
||||
export const VALIDATORS_DATA = [
|
||||
{
|
||||
number: 1,
|
||||
name: '1',
|
||||
address: 'zQ3asdf9d4Gs0',
|
||||
balance: 32.0786,
|
||||
income: 0.0786,
|
||||
|
@ -83,7 +126,7 @@ export const VALIDATORS_DATA = [
|
|||
status: 'Active',
|
||||
},
|
||||
{
|
||||
number: 1,
|
||||
name: '1',
|
||||
address: 'zQ3asdf9d4Gs0',
|
||||
balance: 32.0786,
|
||||
income: 0.0786,
|
||||
|
@ -93,7 +136,7 @@ export const VALIDATORS_DATA = [
|
|||
status: 'Active',
|
||||
},
|
||||
{
|
||||
number: 1,
|
||||
name: '1',
|
||||
address: 'zQ3asdf9d4Gs0',
|
||||
balance: 32.0786,
|
||||
income: 0.0786,
|
||||
|
@ -103,7 +146,7 @@ export const VALIDATORS_DATA = [
|
|||
status: 'Active',
|
||||
},
|
||||
{
|
||||
number: 1,
|
||||
name: '1',
|
||||
address: 'zQ3asdf9d4Gs0',
|
||||
balance: 32.0786,
|
||||
income: 0.0786,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
|
||||
export const useWindowSize = () => {
|
||||
const [size, setSize] = useState({ width: 0, height: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
handleResize()
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
|
||||
return size
|
||||
}
|
|
@ -7,6 +7,7 @@ import { Separator, YStack } from 'tamagui'
|
|||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import styles from './pairDevice.module.css'
|
||||
import { copyFunction } from '../../utilities'
|
||||
|
||||
type GenerateIdProps = {
|
||||
isAwaitingPairing: boolean
|
||||
|
@ -24,7 +25,7 @@ const GenerateId = ({ isAwaitingPairing }: GenerateIdProps) => {
|
|||
}
|
||||
|
||||
const copyGeneratedIdHandler = () => {
|
||||
navigator.clipboard.writeText(generatedId)
|
||||
copyFunction(generatedId)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -15,7 +15,7 @@ type ManagementTableProps = {
|
|||
}
|
||||
|
||||
export type Validator = {
|
||||
number: number
|
||||
name: string
|
||||
address: string
|
||||
balance: number
|
||||
income: number
|
||||
|
@ -35,12 +35,12 @@ const isValidStatus = (validatorStatus: string, tabStatus: string) => {
|
|||
return false
|
||||
}
|
||||
|
||||
const isValidNumberOrAddress = (
|
||||
validatorNumber: number,
|
||||
const isValidNameOrAddress = (
|
||||
validatorName: string,
|
||||
validatorAddress: string,
|
||||
searchValue: string,
|
||||
) => {
|
||||
if (validatorNumber.toString().includes(searchValue) || validatorAddress.includes(searchValue)) {
|
||||
if (validatorName.includes(searchValue) || validatorAddress.includes(searchValue)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -61,7 +61,7 @@ const ManagementTable = ({ tab, searchValue, changeSearchValue }: ManagementTabl
|
|||
const filteredValidators = useMemo(() => {
|
||||
return validators
|
||||
.filter(validator => isValidStatus(validator.status, tab))
|
||||
.filter(validator => isValidNumberOrAddress(validator.number, validator.address, searchValue))
|
||||
.filter(validator => isValidNameOrAddress(validator.name, validator.address, searchValue))
|
||||
}, [validators, tab, searchValue])
|
||||
|
||||
const handleSelectAll = () => {
|
||||
|
|
|
@ -32,7 +32,7 @@ const ManagementTableRow = ({ validator, isAllSelected }: ManagementTableRowProp
|
|||
/>
|
||||
</td>
|
||||
<td>
|
||||
<ValidatorProfile number={validator.number} address={validator.address} />
|
||||
<ValidatorProfile name={validator.name} address={validator.address} />
|
||||
</td>
|
||||
<td>
|
||||
<Text size={15} color={'#647084'} weight={'semibold'}>
|
||||
|
|
|
@ -6,109 +6,87 @@ import AdvisoriesContent from './AdvisoriesContent'
|
|||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from '../../../redux/store'
|
||||
import { setSubStepAdvisories } from '../../../redux/ValidatorOnboarding/Advisories/slice'
|
||||
|
||||
type AdvisoryTopicsType = {
|
||||
[key: string]: string[]
|
||||
}
|
||||
import { ADVISORY_TOPICS } from '../../../constants'
|
||||
import styles from './advisoriesLayout.module.css'
|
||||
|
||||
const Advisories = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { subStepAdvisories } = useSelector((state: RootState) => state.advisories)
|
||||
const [selectedTitle, setSelectedTitle] = useState(Object.keys(advisoryTopics)[0])
|
||||
const [completedSteps, setCompletedSteps] = useState<number[]>([])
|
||||
const [selectedTitle, setSelectedTitle] = useState<string>(Object.keys(ADVISORY_TOPICS)[0])
|
||||
|
||||
const unicodeNumbers = ['➀', '➁', '➂', '➃', '➄', '➅']
|
||||
const advisoriesIcons = unicodeNumbers.map((number, index) =>
|
||||
index <= subStepAdvisories ? '✓' : number,
|
||||
)
|
||||
|
||||
const isCompleted = (index: number): boolean => completedSteps.includes(index)
|
||||
|
||||
const advisoriesIcons = unicodeNumbers.map((number, index) => (isCompleted(index) ? '✓' : number))
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTitle(Object.keys(advisoryTopics)[subStepAdvisories])
|
||||
setSelectedTitle(Object.keys(ADVISORY_TOPICS)[subStepAdvisories])
|
||||
|
||||
setCompletedSteps(prevSteps => {
|
||||
if (!prevSteps.includes(subStepAdvisories)) {
|
||||
return [...prevSteps, subStepAdvisories]
|
||||
}
|
||||
return prevSteps
|
||||
})
|
||||
}, [subStepAdvisories])
|
||||
|
||||
const handleStepClick = (title: string): void => {
|
||||
const index = getIndexTitle(title)
|
||||
dispatch(setSubStepAdvisories(index))
|
||||
}
|
||||
|
||||
const isCurrent = (currentTitle: string): boolean => {
|
||||
const topics = Object.keys(advisoryTopics)
|
||||
const topics = Object.keys(ADVISORY_TOPICS)
|
||||
const index = topics.indexOf(currentTitle)
|
||||
return index <= subStepAdvisories ? true : false
|
||||
return index === subStepAdvisories
|
||||
}
|
||||
|
||||
const getIndexTitle = (title: string): number => {
|
||||
const topics = Object.keys(advisoryTopics)
|
||||
const topics = Object.keys(ADVISORY_TOPICS)
|
||||
const index = topics.indexOf(title)
|
||||
return index
|
||||
}
|
||||
|
||||
return (
|
||||
<XStack
|
||||
style={{ padding: '30px 33px', justifyContent: 'space-between' }}
|
||||
minHeight={'65vh'}
|
||||
width={'100%'}
|
||||
>
|
||||
<YStack space={'$2'}>
|
||||
<div className={styles['advisories-container']}>
|
||||
<YStack space={'$2'} marginBottom={'30px'}>
|
||||
<Stack marginBottom="$6">
|
||||
<Text size={27} weight={'semibold'}>
|
||||
Advisories
|
||||
</Text>
|
||||
</Stack>
|
||||
{Object.keys(advisoryTopics).map((title, index) => (
|
||||
<XStack
|
||||
key={title}
|
||||
onPress={() => dispatch(setSubStepAdvisories(getIndexTitle(title)))}
|
||||
style={{ cursor: 'pointer', alignItems: 'center' }}
|
||||
space={'$2'}
|
||||
>
|
||||
<Text
|
||||
size={19}
|
||||
weight={isCurrent(title) && 'semibold'}
|
||||
color={isCurrent(title) ? 'blue' : ''}
|
||||
<div className={styles['advisories-nav']}>
|
||||
{Object.keys(ADVISORY_TOPICS).map((title, index) => (
|
||||
<XStack
|
||||
key={title}
|
||||
onPress={() => handleStepClick(title)}
|
||||
style={{ cursor: 'pointer', alignItems: 'center' }}
|
||||
space={'$2'}
|
||||
>
|
||||
{advisoriesIcons[index]}
|
||||
</Text>
|
||||
<Text
|
||||
size={19}
|
||||
weight={isCurrent(title) ? 'semibold' : ''}
|
||||
color={isCurrent(title) ? 'blue' : ''}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</XStack>
|
||||
))}
|
||||
<Text
|
||||
size={27}
|
||||
weight={isCompleted(index) || isCurrent(title) ? 'semibold' : 'normal'}
|
||||
color={isCompleted(index) || isCurrent(title) ? 'blue' : 'default'}
|
||||
>
|
||||
{advisoriesIcons[index]}
|
||||
</Text>
|
||||
<Text
|
||||
size={19}
|
||||
weight={isCompleted(index) || isCurrent(title) ? 'semibold' : 'normal'}
|
||||
color={isCompleted(index) || isCurrent(title) ? 'blue' : 'default'}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</XStack>
|
||||
))}
|
||||
</div>
|
||||
</YStack>
|
||||
<AdvisoriesContent title={selectedTitle} content={advisoryTopics[selectedTitle]} />
|
||||
</XStack>
|
||||
|
||||
<AdvisoriesContent title={selectedTitle} content={ADVISORY_TOPICS[selectedTitle]} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Advisories
|
||||
|
||||
export const advisoryTopics: AdvisoryTopicsType = {
|
||||
'Proof of Stake': [
|
||||
'Proof of Stake systems require validators to hold and lock up a certain amount of cryptocurrency to participate.',
|
||||
'In Proof of Stake, the chances of creating a block is proportional to the amount of cryptocurrency held.',
|
||||
'Unlike Proof of Work, Proof of Stake aims to achieve consensus without intensive computational work.',
|
||||
],
|
||||
Deposit: [
|
||||
'Deposits are often irreversible, so ensure to double-check transaction details before confirming.',
|
||||
'Delay in deposit acknowledgment might be due to network congestion or node synchronization.',
|
||||
'Always keep transaction IDs or hashes for records and future references in case of disputes.',
|
||||
],
|
||||
'Key Management': [
|
||||
'Storing your private keys on a device connected to the internet is susceptible to hacks and malware.',
|
||||
'Hardware wallets provide an added layer of security by keeping private keys isolated from online systems.',
|
||||
'Regularly back up and encrypt your key management solutions to prevent potential losses.',
|
||||
],
|
||||
'Bad Behaviour': [
|
||||
'If you try to cheat the system, or act contrary to the specification, you will be liable to incur a penalty known as slashing.',
|
||||
'Running your validator keys simultaneously on two or more machines will result in slashing.*',
|
||||
'Simply being offline with an otherwise healthy network does not result in slashing, but will result in small inactivity penalties.',
|
||||
],
|
||||
Requirements: [
|
||||
'Ensure your system meets the minimum software and hardware requirements before initiating any operations.',
|
||||
'Staying updated with the latest versions is vital to maintain system integrity and performance.',
|
||||
'Failure to meet requirements might result in operational inefficiencies or security vulnerabilities.',
|
||||
],
|
||||
Risks: [
|
||||
'Cryptocurrency investments are subject to high volatility and can result in both significant gains and losses.',
|
||||
'Always do thorough research before making investment decisions or engaging in transactions.',
|
||||
'Be wary of phishing scams, malicious software, and too-good-to-be-true offers.',
|
||||
],
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|||
import { withRouter } from 'storybook-addon-react-router-v6'
|
||||
|
||||
import AdvisoriesContent from './AdvisoriesContent'
|
||||
import { advisoryTopics } from './Advisories'
|
||||
import { ADVISORY_TOPICS } from '../../../constants'
|
||||
|
||||
const meta = {
|
||||
title: 'ValidatorOnboarding/AdvisoriesContent',
|
||||
|
@ -17,8 +17,8 @@ const meta = {
|
|||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const advisoryTopicsKeys = Object.keys(advisoryTopics)
|
||||
const advisoryTopicsValues = Object.values(advisoryTopics)
|
||||
const advisoryTopicsKeys = Object.keys(ADVISORY_TOPICS)
|
||||
const advisoryTopicsValues = Object.values(ADVISORY_TOPICS)
|
||||
|
||||
export const ProofOfStake: Story = {
|
||||
args: {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
.advisories-container {
|
||||
display: flex;
|
||||
padding: 30px;
|
||||
justify-content: space-between;
|
||||
width: auto;
|
||||
}
|
||||
.advisories-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
@media screen and (max-width: 780px) {
|
||||
.advisories-container {
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 20px;
|
||||
}
|
||||
.advisories-nav {
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ import {
|
|||
setIsCopyPastedPhrase,
|
||||
setValidWords,
|
||||
} from '../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||
import { setSubStepAdvisories } from '../../redux/ValidatorOnboarding/Advisories/slice'
|
||||
|
||||
const ContinueButton = () => {
|
||||
const [isDisabled, setIsDisabled] = useState(false)
|
||||
|
@ -32,7 +31,6 @@ const ContinueButton = () => {
|
|||
(state: RootState) => state.validatorOnboarding,
|
||||
)
|
||||
const { isValidatorSet } = useSelector((state: RootState) => state.validatorSetup)
|
||||
const { subStepAdvisories } = useSelector((state: RootState) => state.advisories)
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
|
@ -64,12 +62,7 @@ const ContinueButton = () => {
|
|||
])
|
||||
|
||||
const handleStep1 = () => {
|
||||
if (subStepAdvisories < 5) {
|
||||
dispatch(setSubStepAdvisories(subStepAdvisories + 1))
|
||||
} else {
|
||||
dispatch(setSubStepAdvisories(0))
|
||||
dispatch(setActiveStep(activeStep + 1))
|
||||
}
|
||||
dispatch(setActiveStep(activeStep + 1))
|
||||
}
|
||||
|
||||
const handleStep2 = () => {
|
||||
|
@ -124,7 +117,7 @@ const ContinueButton = () => {
|
|||
justifyContent: isActivationValScreen ? 'space-between' : 'end',
|
||||
alignItems: 'center',
|
||||
zIndex: 1000,
|
||||
marginTop: '10px',
|
||||
marginTop: '21px',
|
||||
}}
|
||||
>
|
||||
{isCopyPastedPhrase && (
|
||||
|
|
|
@ -52,7 +52,7 @@ const Deposit = () => {
|
|||
{Array.from({ length: validatorCount }).map((_, index) => (
|
||||
<ValidatorRequest
|
||||
key={index}
|
||||
number={index + 1}
|
||||
name={(index + 1).toString()}
|
||||
isTransactionConfirmation={isTransactionConfirmation}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -16,21 +16,21 @@ type Story = StoryObj<typeof meta>
|
|||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
number: 1,
|
||||
name: '1',
|
||||
isTransactionConfirmation: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const TransactionConfirmation: Story = {
|
||||
args: {
|
||||
number: 1,
|
||||
name: '1',
|
||||
isTransactionConfirmation: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const BigNumber: Story = {
|
||||
args: {
|
||||
number: 123456789,
|
||||
name: '123456789',
|
||||
isTransactionConfirmation: false,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ import TransactionStatus from './TransactionStatus'
|
|||
import ValidatorProfile from '../../../../components/General/ValidatorProfile'
|
||||
|
||||
type ValidatorRequestProps = {
|
||||
number: number
|
||||
name: string
|
||||
isTransactionConfirmation?: boolean
|
||||
}
|
||||
|
||||
const ValidatorRequest = ({ number, isTransactionConfirmation }: ValidatorRequestProps) => {
|
||||
const ValidatorRequest = ({ name, isTransactionConfirmation }: ValidatorRequestProps) => {
|
||||
let transactionStatus = 'Complete'
|
||||
const isTransactionCompleted = transactionStatus === 'Complete'
|
||||
|
||||
|
@ -17,7 +17,7 @@ const ValidatorRequest = ({ number, isTransactionConfirmation }: ValidatorReques
|
|||
<YStack space={'$3'} style={{ width: '100%' }}>
|
||||
<XStack style={{ justifyContent: 'space-between', width: '100%', alignItems: 'center' }}>
|
||||
<XStack style={{ justifyContent: 'space-between', width: '44%', alignItems: 'center' }}>
|
||||
<ValidatorProfile number={number} address={'zQ3asdf9d4Gs0'} />
|
||||
<ValidatorProfile name={name} address={'zQ3asdf9d4Gs0'} />
|
||||
<Text size={13} color="#647084" weight={'semibold'}>
|
||||
Keys Generated
|
||||
</Text>
|
||||
|
|
|
@ -53,9 +53,15 @@ span[class*='Connector-'] {
|
|||
content: attr(data-subtitle);
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 8px;
|
||||
left: 9px;
|
||||
font-size: 12px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: #a2a9b0;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
@media (max-width: 410px) {
|
||||
.custom-step::after {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,24 +2,52 @@ import { Stepper, Step } from 'react-form-stepper'
|
|||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import { setActiveStep } from '../../../redux/ValidatorOnboarding/slice'
|
||||
import { FORM_STEPS } from '../../../constants'
|
||||
import { useWindowSize } from '../../../hooks/useWindowSize'
|
||||
import './FormStepper.css'
|
||||
|
||||
const steps = [
|
||||
{ label: 'Overview', subtitle: 'Get Started' },
|
||||
{ label: 'Advisories', subtitle: 'Understand your Duties' },
|
||||
{ label: 'Client Setup', subtitle: 'Execution & Consensus' },
|
||||
{ label: 'Validator Setup', subtitle: 'Validators & Withdrawal' },
|
||||
{ label: 'Key Generation', subtitle: 'Secure your Keypairs' },
|
||||
{ label: 'Deposit', subtitle: 'Stake your ETH' },
|
||||
{ label: 'Activation', subtitle: 'Complete Setup' },
|
||||
]
|
||||
|
||||
type FormStepperProps = {
|
||||
activeStep: number
|
||||
}
|
||||
|
||||
const FormStepper = ({ activeStep }: FormStepperProps) => {
|
||||
const dispatch = useDispatch()
|
||||
const windowSize = useWindowSize()
|
||||
|
||||
const getIsStepVisible = (index: number, stepsBefore: number, stepsAfter: number) => {
|
||||
const totalSteps = FORM_STEPS.length
|
||||
let start = activeStep - stepsBefore
|
||||
let end = activeStep + stepsAfter
|
||||
|
||||
// active step is near the start or end
|
||||
if (start < 0) {
|
||||
end -= start
|
||||
start = 0
|
||||
}
|
||||
if (end >= totalSteps) {
|
||||
start -= end - totalSteps + 1
|
||||
end = totalSteps - 1
|
||||
}
|
||||
|
||||
start = Math.max(0, start)
|
||||
end = Math.min(end, totalSteps - 1)
|
||||
|
||||
return index >= start && index <= end
|
||||
}
|
||||
|
||||
const isStepVisible = (index: number) => {
|
||||
if (windowSize.width < 774) {
|
||||
return getIsStepVisible(index, 1, 1) // 3 steps (1 before, 1 after)
|
||||
} else if (windowSize.width < 963) {
|
||||
return getIsStepVisible(index, 1, 2) // 4 steps
|
||||
} else if (windowSize.width < 1183) {
|
||||
return getIsStepVisible(index, 1, 3) // 5 steps
|
||||
} else if (windowSize.width < 1300) {
|
||||
return getIsStepVisible(index, 2, 3) // 6 steps
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const changeStepOnClickHandler = (index: number) => {
|
||||
if (activeStep > index) {
|
||||
|
@ -37,21 +65,25 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
|
|||
fontSize: '14px',
|
||||
zIndex: 1,
|
||||
width: '100%',
|
||||
height: 'fit-content',
|
||||
padding: 0,
|
||||
marginBottom: '3rem',
|
||||
}}
|
||||
>
|
||||
{steps.map((step, index) => (
|
||||
<Step
|
||||
key={index}
|
||||
label={step.label}
|
||||
className="custom-step"
|
||||
onClick={() => changeStepOnClickHandler(index)}
|
||||
completed={activeStep > index - 1}
|
||||
data-subtitle={step.subtitle}
|
||||
data-step={step.label}
|
||||
/>
|
||||
))}
|
||||
{FORM_STEPS.filter((_, index) => isStepVisible(index)).map(step => {
|
||||
const originalIndex = FORM_STEPS.indexOf(step)
|
||||
return (
|
||||
<Step
|
||||
key={originalIndex}
|
||||
label={`${step.label}`}
|
||||
className="custom-step"
|
||||
onClick={() => changeStepOnClickHandler(originalIndex)}
|
||||
completed={activeStep > originalIndex - 1}
|
||||
data-subtitle={step.subtitle}
|
||||
data-step={step.label}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Stepper>
|
||||
)
|
||||
}
|
||||
|
@ -69,8 +101,8 @@ const stepStyle = {
|
|||
activeTextColor: '#ffffff',
|
||||
completedTextColor: '#ffffff',
|
||||
inactiveTextColor: '#000000',
|
||||
size: '20px',
|
||||
circleFontSize: '10px',
|
||||
size: '28px',
|
||||
circleFontSize: '0px',
|
||||
labelFontSize: '14px',
|
||||
borderRadius: '50%',
|
||||
fontWeight: 700,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Stack, YStack } from 'tamagui'
|
||||
import { YStack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import AutocompleteInput from './AutocompleteInput'
|
||||
import KeyGenerationTitle from '../KeyGenerationTitle'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from '../../../../redux/store'
|
||||
import styles from '../index.module.css'
|
||||
|
||||
const ConfirmRecoveryPhrase = () => {
|
||||
const { validWords } = useSelector((state: RootState) => state.keyGeneration)
|
||||
|
@ -13,19 +14,11 @@ const ConfirmRecoveryPhrase = () => {
|
|||
<YStack space={'$3'} style={{ width: '100%', marginTop: '20px' }}>
|
||||
<KeyGenerationTitle />
|
||||
<Text size={19}>Confirm Recovery Phrase</Text>
|
||||
<Stack
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
gap: '20px 40px',
|
||||
width: '72%',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
<div className={styles['confirm-recovery-phrase']}>
|
||||
{validWords.map((_, index) => (
|
||||
<AutocompleteInput key={index} index={index} />
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { useEffect } from 'react'
|
||||
import { Stack, YStack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { generateMnemonic } from 'web-bip39'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import wordlist from 'web-bip39/wordlists/english'
|
||||
|
||||
import KeyGenerationHeader from './KeyGenerationHeader/KeyGenerationHeader'
|
||||
import RecoveryMechanism from './RecoveryMechanism/RecoveryMechanism'
|
||||
|
@ -9,11 +12,22 @@ import RecoveryPhrase from './RecoveryPhrase'
|
|||
import ConfirmRecoveryPhrase from './ConfirmRecoveryPhrase/ConfirmRecoveryPhrase'
|
||||
import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../constants'
|
||||
import { RootState } from '../../../redux/store'
|
||||
import { setGeneratedMnemonic } from '../../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||
|
||||
const KeyGeneration = () => {
|
||||
const { recoveryMechanism, isConfirmPhraseStage } = useSelector(
|
||||
(state: RootState) => state.keyGeneration,
|
||||
)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
getMnemonic()
|
||||
}, [])
|
||||
|
||||
const getMnemonic = async () => {
|
||||
const mnemonic = await generateMnemonic(wordlist, 256)
|
||||
dispatch(setGeneratedMnemonic(mnemonic.split(' ')))
|
||||
}
|
||||
|
||||
const isKeystoreFiles =
|
||||
recoveryMechanism === KEYSTORE_FILES || recoveryMechanism === BOTH_KEY_AND_RECOVERY
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { XStack } from 'tamagui'
|
||||
|
||||
import SyncStatusCard from '../../../../components/General/SyncStatusCard'
|
||||
import KeyGenerationTitle from '../KeyGenerationTitle'
|
||||
import styles from '../index.module.css'
|
||||
|
||||
const KeyGenerationHeader = () => {
|
||||
return (
|
||||
<XStack style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<div className={styles['header']}>
|
||||
<KeyGenerationTitle />
|
||||
<XStack space={'$2'}>
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
<SyncStatusCard
|
||||
synced={123.524}
|
||||
total={172.503}
|
||||
|
@ -20,8 +19,8 @@ const KeyGenerationHeader = () => {
|
|||
title="Consensus Sync Status"
|
||||
color="#ff6161"
|
||||
/>
|
||||
</XStack>
|
||||
</XStack>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,7 @@ const KeystoreBackupsCard = () => {
|
|||
style={{
|
||||
border: '1px solid #DCE0E5',
|
||||
borderRadius: '16px',
|
||||
padding: '12px 16px',
|
||||
width: '32%',
|
||||
marginTop: '3.4%',
|
||||
padding: '9px 16px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={downloadKeyFilesHandler}
|
||||
|
@ -31,7 +29,6 @@ const KeystoreBackupsCard = () => {
|
|||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
<Text size={13} color="#647084">
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
import { Stack, YStack, useMedia } from 'tamagui'
|
||||
import { Button, InformationBox, Input, Text } from '@status-im/components'
|
||||
import { ClearIcon, CloseCircleIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
|
||||
import KeystoreBackupsCard from './KeystoreBackupsCard'
|
||||
import ResponsiveStack from '../../../../components/General/ResponsiveStack'
|
||||
|
||||
const KeystoreFiles = () => {
|
||||
const [encryptedPassword, setEncryptedPassword] = useState('')
|
||||
|
@ -12,6 +13,7 @@ const KeystoreFiles = () => {
|
|||
const [confirmEncryptedPasswordError, setConfirmEncryptedPasswordError] = useState(false)
|
||||
const [displayEncryptedPassword, setDisplayEncryptedPassword] = useState('')
|
||||
const [displayConfirmEncryptedPassword, setDisplayConfirmEncryptedPassword] = useState('')
|
||||
const media = useMedia()
|
||||
|
||||
const generateKeystoreFilesHandler = () => {
|
||||
if (
|
||||
|
@ -55,8 +57,12 @@ const KeystoreFiles = () => {
|
|||
|
||||
return (
|
||||
<YStack space={'$4'}>
|
||||
<XStack space={'$2'} style={{ justifyContent: 'space-between', width: '100%' }}>
|
||||
<YStack space={'$4'} style={{ width: '66%' }}>
|
||||
<ResponsiveStack
|
||||
isVerticalAligned={!!media.sm}
|
||||
space={'$2'}
|
||||
style={{ justifyContent: 'space-between', width: '100%' }}
|
||||
>
|
||||
<YStack space={'$4'} style={{ width: media.sm ? '100%' : '66%' }}>
|
||||
<YStack space={'$4'}>
|
||||
<Text size={15} color={'#647084'}>
|
||||
Encryption Password
|
||||
|
@ -96,8 +102,10 @@ const KeystoreFiles = () => {
|
|||
/>
|
||||
</YStack>
|
||||
</YStack>
|
||||
<KeystoreBackupsCard />
|
||||
</XStack>
|
||||
<div style={{ width: media.sm ? '100%' : '32%', paddingTop: '3.8%' }}>
|
||||
<KeystoreBackupsCard />
|
||||
</div>
|
||||
</ResponsiveStack>
|
||||
<Stack style={{ width: 'fit-content' }}>
|
||||
<Button onPress={generateKeystoreFilesHandler}>Generate Key files</Button>
|
||||
</Stack>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { YStack } from 'tamagui'
|
||||
|
||||
import RecoveryMechanismCard from './RecoveryMechanismCard'
|
||||
import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../../constants'
|
||||
import styles from '../index.module.css'
|
||||
|
||||
type RecoveryMechanismProps = {
|
||||
recoveryMechanism: string
|
||||
|
@ -20,7 +21,7 @@ const RecoveryMechanism = ({ recoveryMechanism }: RecoveryMechanismProps) => {
|
|||
<Text size={19} weight={'semibold'}>
|
||||
Select Recovery Mechanism
|
||||
</Text>
|
||||
<XStack space={'$4'} style={{ justifyContent: 'space-between', marginTop: '40px' }}>
|
||||
<div className={styles['recovery-mechanism-container']}>
|
||||
{Object.entries(cards).map(([value, icon]) => (
|
||||
<RecoveryMechanismCard
|
||||
key={value}
|
||||
|
@ -29,7 +30,7 @@ const RecoveryMechanism = ({ recoveryMechanism }: RecoveryMechanismProps) => {
|
|||
icon={icon}
|
||||
/>
|
||||
))}
|
||||
</XStack>
|
||||
</div>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@ import { Stack, XStack, YStack } from 'tamagui'
|
|||
import { Button, InformationBox, Text } from '@status-im/components'
|
||||
import { CloseCircleIcon, CopyIcon, CheckIcon } from '@status-im/icons'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { generateMnemonic } from 'web-bip39'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import wordlist from 'web-bip39/wordlists/english'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import { RootState } from '../../../redux/store'
|
||||
import { setGeneratedMnemonic } from '../../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||
import { copyFunction } from '../../../utilities'
|
||||
import styles from './index.module.css'
|
||||
|
||||
type RecoveryPhraseProps = {
|
||||
isKeystoreFiles: boolean
|
||||
|
@ -17,26 +16,25 @@ const RecoveryPhrase = ({ isKeystoreFiles }: RecoveryPhraseProps) => {
|
|||
const [isReveal, setIsReveal] = useState(false)
|
||||
const [isCopied, setIsCopied] = 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(' ')))
|
||||
}
|
||||
setIsCopied(false)
|
||||
}, [generatedMnemonic])
|
||||
|
||||
const revealHandler = () => {
|
||||
setIsReveal(state => !state)
|
||||
}
|
||||
|
||||
const copyRecoveryPhraseHandler = () => {
|
||||
const text = generatedMnemonic.join(' ')
|
||||
navigator.clipboard.writeText(text)
|
||||
copyFunction(generatedMnemonic.join(' '))
|
||||
|
||||
setIsCopied(true)
|
||||
if (isCopied === false) {
|
||||
setIsCopied(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -51,24 +49,21 @@ const RecoveryPhrase = ({ isKeystoreFiles }: RecoveryPhraseProps) => {
|
|||
cursor: 'pointer',
|
||||
paddingBottom: '8px',
|
||||
paddingRight: '18px',
|
||||
paddingLeft: '18px',
|
||||
}}
|
||||
onClick={copyRecoveryPhraseHandler}
|
||||
>
|
||||
<Stack
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(6, 1fr)',
|
||||
gap: '5px 3px',
|
||||
width: '100%',
|
||||
filter: `blur(${isReveal ? '0px' : '4px'})`,
|
||||
padding: '28px 0px 0px 18px',
|
||||
}}
|
||||
className={styles['recovery-phrase']}
|
||||
>
|
||||
{generatedMnemonic.map((word, index) => (
|
||||
<XStack style={{ width: '100%' }}>
|
||||
<Stack style={{ width: '25%' }}>
|
||||
<XStack style={{ width: '100%' }} key={word}>
|
||||
<Stack>
|
||||
<Text key={index} size={19} weight={'semibold'} color="#0d162566">
|
||||
{index + 1}.
|
||||
{index + 1}.
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text key={index} size={19} weight={'semibold'}>
|
||||
|
@ -76,7 +71,7 @@ const RecoveryPhrase = ({ isKeystoreFiles }: RecoveryPhraseProps) => {
|
|||
</Text>
|
||||
</XStack>
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
{isCopied ? <CheckIcon size={20} /> : <CopyIcon size={20} />}
|
||||
</YStack>
|
||||
<Stack style={{ width: 'fit-content', marginBottom: '12px' }}>
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
.recovery-mechanism-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
justify-content: space-between;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.recovery-mechanism-container > div {
|
||||
flex: 1;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.recovery-phrase {
|
||||
display: grid;
|
||||
gap: 5px 3px;
|
||||
width: 100%;
|
||||
padding: 28px 0px 0px 18px;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
|
||||
.confirm-recovery-phrase {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px 40px;
|
||||
width: 72%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 630px) {
|
||||
.recovery-mechanism-container > div {
|
||||
min-width: 40%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 740px) {
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 701px) and (max-width: 950px) {
|
||||
.recovery-phrase {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.recovery-phrase {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 351px) and (max-width: 520px) {
|
||||
.recovery-phrase {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.recovery-phrase {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.confirm-recovery-phrase {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
.confirm-recovery-phrase {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.confirm-recovery-phrase {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.confirm-recovery-phrase {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,60 +1,48 @@
|
|||
import { XStack, YStack } from 'tamagui'
|
||||
import { YStack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
import OverviewCard from './OverviewCard'
|
||||
import LinkWithArrow from '../../../components/General/LinkWithArrow'
|
||||
import OverviewWrapper from './OverviewWrapper'
|
||||
import styles from './overviewLayout.module.css'
|
||||
|
||||
const Overview = () => {
|
||||
return (
|
||||
<>
|
||||
<YStack
|
||||
className="layout-left"
|
||||
space={'$5'}
|
||||
style={{ padding: '26px 0 32px 32px' }}
|
||||
minHeight={'65vh'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<YStack space={'$5'}>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
Overview
|
||||
</Text>
|
||||
<Text size={19}>
|
||||
Becoming a validator is a big responsibility with important preparation steps. Only
|
||||
start the deposit process when you're ready.
|
||||
</Text>
|
||||
<Text size={15} color="#939BA1">
|
||||
By running a validator, you'll be responsible for securing the network and receive
|
||||
continuous payouts for actions that help the network reach consensus.
|
||||
</Text>
|
||||
<Text size={15} color="#939BA1">
|
||||
Since the successful transition to proof-of-stake via The Merge, Ethereum is fully
|
||||
secured by proof-of-stake validators. By running a validator, you'll be helping to
|
||||
secure the Ethereum network.
|
||||
</Text>
|
||||
<LinkWithArrow
|
||||
text="Learn More"
|
||||
to={'/'}
|
||||
arrowRight={true}
|
||||
style={{ marginBottom: '1%', fontSize: '13px' }}
|
||||
/>
|
||||
</YStack>
|
||||
<XStack space={'$3'}>
|
||||
<OverviewCard text={'Current APR'} value={'4.40%'} />
|
||||
<OverviewCard text={'Total ETH Staked'} value={'9,451,123'} />
|
||||
<OverviewCard text={'Estimated Activation Time'} value={'32 Days'} />
|
||||
<OverviewCard text={'Validator Queue'} value={'92603'} />
|
||||
</XStack>
|
||||
<OverviewWrapper
|
||||
imgHeight="250%"
|
||||
rightImageSrc="./background-images/sync-status-background.png"
|
||||
>
|
||||
<YStack space={'$5'} marginTop={'2rem'} width="100%">
|
||||
<Text size={27} weight={'semibold'}>
|
||||
Overview
|
||||
</Text>
|
||||
<Text size={19}>
|
||||
Becoming a validator is a big responsibility with important preparation steps. Only start
|
||||
the deposit process when you're ready.
|
||||
</Text>
|
||||
<Text size={15} color="#939BA1">
|
||||
By running a validator, you'll be responsible for securing the network and receive
|
||||
continuous payouts for actions that help the network reach consensus.
|
||||
</Text>
|
||||
<Text size={15} color="#939BA1">
|
||||
Since the successful transition to proof-of-stake via The Merge, Ethereum is fully secured
|
||||
by proof-of-stake validators. By running a validator, you'll be helping to secure the
|
||||
Ethereum network.
|
||||
</Text>
|
||||
<LinkWithArrow
|
||||
text="Learn More"
|
||||
to={'/'}
|
||||
arrowRight={true}
|
||||
style={{ marginBottom: '1%', fontSize: '13px' }}
|
||||
/>
|
||||
</YStack>
|
||||
<section className="layout-right">
|
||||
<div className="image-container">
|
||||
<img
|
||||
src="./background-images/sync-status-background.png"
|
||||
alt="background"
|
||||
className="background-img"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
<div className={styles.overviewCards}>
|
||||
<OverviewCard text={'Current APR'} value={'4.40%'} />
|
||||
<OverviewCard text={'Total ETH Staked'} value={'9,451,123'} />
|
||||
<OverviewCard text={'Estimated Activation Time'} value={'32 Days'} />
|
||||
<OverviewCard text={'Validator Queue'} value={'92603'} />
|
||||
</div>
|
||||
</OverviewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { YStack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
import styles from './overviewLayout.module.css'
|
||||
import { Text, YStack } from 'tamagui'
|
||||
type OverviewCardProps = {
|
||||
text: string
|
||||
value: string
|
||||
|
@ -8,21 +7,22 @@ type OverviewCardProps = {
|
|||
|
||||
const OverviewCard = ({ text, value }: OverviewCardProps) => {
|
||||
return (
|
||||
<YStack
|
||||
style={{
|
||||
borderRadius: '16px',
|
||||
border: '1px solid rgba(0, 0, 0, 0.15)',
|
||||
width: '44%',
|
||||
padding: '12px 16px',
|
||||
backgroundColor: '#FFF',
|
||||
}}
|
||||
>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
{text}
|
||||
</Text>
|
||||
<Text size={27} color="blue" weight={'semibold'}>
|
||||
{value}
|
||||
</Text>
|
||||
<YStack>
|
||||
<div className={styles.overviewCard}>
|
||||
<Text
|
||||
fontWeight={'500'}
|
||||
style={{ display: 'block', marginBottom: '8px', fontSize: '15px' }}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
<Text
|
||||
color="blue"
|
||||
fontWeight={'500'}
|
||||
style={{ display: 'block', marginBottom: '8px', fontSize: '27px' }}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
</div>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { ReactNode } from 'react'
|
||||
import { useTheme } from 'tamagui'
|
||||
|
||||
import styles from './validatorLayout.module.css'
|
||||
|
||||
type OverviewWrapperProps = {
|
||||
rightImageSrc?: string
|
||||
children: ReactNode
|
||||
imgHeight?: string
|
||||
}
|
||||
|
||||
const OverviewWrapper = ({ rightImageSrc, children, imgHeight }: OverviewWrapperProps) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div className={styles['layout']} style={{ backgroundColor: theme.background.val }}>
|
||||
<section className={styles['layout-left']}>
|
||||
<div className={styles['container']}>
|
||||
<div className={styles['container-inner']}>{children}</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className={styles['layout-right']}>
|
||||
<div className={styles['image-container']}>
|
||||
<img
|
||||
src={rightImageSrc}
|
||||
alt="background"
|
||||
className={styles['background-img']}
|
||||
style={{ height: imgHeight }}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OverviewWrapper
|
|
@ -0,0 +1,24 @@
|
|||
.overviewCards {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
width: 250%;
|
||||
}
|
||||
.overviewCard {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.overviewCards {
|
||||
flex-direction: column;
|
||||
}
|
||||
.overviewCard {
|
||||
width: 35%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
.layout {
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow: hidden;
|
||||
border-radius: 25px;
|
||||
}
|
||||
.layout::after {
|
||||
display: block;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.layout-left {
|
||||
flex: 0 0 55%;
|
||||
max-width: 55%;
|
||||
z-index: 2;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 100%;
|
||||
padding-left: 10%;
|
||||
}
|
||||
.container-inner {
|
||||
max-width: 70%;
|
||||
flex: 1 0 70%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* LAYOUT RIGHT ELEMENT WITH IMAGE TAKING UP THE WHOLE HIGHT OF THE VIEWPORT */
|
||||
.layout-right {
|
||||
flex: 0 0 45%;
|
||||
max-width: 45%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
}
|
||||
.image-container::before {
|
||||
display: block;
|
||||
content: '';
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
.image-container::after {
|
||||
display: block;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -1%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, rgba(255, 255, 255, 1) 20%, rgba(255, 255, 255, 0));
|
||||
}
|
||||
.image-container .background-img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 140%;
|
||||
width: auto;
|
||||
}
|
||||
.image-container .nimbus-logomark {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.image-container .nimbus-logomark svg {
|
||||
height: 73px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.layout {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.layout-left {
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.container {
|
||||
justify-content: start;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container-inner {
|
||||
max-width: 100%;
|
||||
flex: 1 0 100%;
|
||||
}
|
||||
|
||||
.layout-right {
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
order: 0;
|
||||
margin-top: -10%;
|
||||
margin-bottom: -72%;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
margin: 0;
|
||||
height: auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-container .background-img {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
/* transform: translateX(-50%) translateY(-5%); */
|
||||
clip-path: inset(0 0 85% 0);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.image-container .nimbus-logomark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content,
|
||||
.breadcrumbBar,
|
||||
.other-elements {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumbBar,
|
||||
.some-other-element {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.image-container .background-img {
|
||||
clip-path: polygon(0 0, 100% 0, 100% 40%, 0 40%);
|
||||
}
|
||||
|
||||
.image-container::after {
|
||||
width: 100%;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to top, rgba(255, 255, 255, 1) 62%, rgba(255, 255, 255, 0));
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ const ValidatorBoxWrapper = ({ children }: ValidatorBoxWrapperProps) => {
|
|||
backgroundColor: '#fff',
|
||||
zIndex: 1,
|
||||
width: '100%',
|
||||
minHeight: '60vh',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { YStack } from 'tamagui'
|
||||
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import { RootState } from '../../redux/store'
|
||||
|
@ -15,26 +14,23 @@ 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 './layoutGradient.css'
|
||||
|
||||
import Deposit from './Deposit/Deposit'
|
||||
|
||||
import './layoutGradient.css'
|
||||
import { useWindowSize } from '../../hooks/useWindowSize'
|
||||
import styles from './layoutGradient.module.css'
|
||||
|
||||
const ValidatorOnboarding = () => {
|
||||
const { activeStep, subStepValidatorSetup } = useSelector(
|
||||
(state: RootState) => state.validatorOnboarding,
|
||||
)
|
||||
const windowSize = useWindowSize()
|
||||
|
||||
return (
|
||||
<div className="gradient-wrapper">
|
||||
<div className={styles.gradientWrapper}>
|
||||
<YStack
|
||||
style={{
|
||||
width: '100%',
|
||||
maxWidth: '1100px',
|
||||
margin: '4rem auto 2rem',
|
||||
padding: windowSize.width < 1000 ? '5% 2% 3% 2%' : '3% 13% 1% 13%',
|
||||
justifyContent: 'start',
|
||||
alignItems: 'start',
|
||||
}}
|
||||
|
@ -48,13 +44,11 @@ const ValidatorOnboarding = () => {
|
|||
<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 && (
|
||||
|
|
|
@ -33,12 +33,13 @@ const ConsensusSelection = () => {
|
|||
|
||||
return (
|
||||
<YStack style={{ width: '100%', padding: '32px' }} minHeight={'65vh'}>
|
||||
<XStack justifyContent={'space-between'} alignItems={'center'} mb={'30px'}>
|
||||
<XStack justifyContent={'space-between'} alignItems={'center'} mb={'30px'} flexWrap="wrap">
|
||||
<Text size={27} weight={'semibold'}>
|
||||
Validator Setup
|
||||
</Text>
|
||||
<XStack space={'$2'}>
|
||||
<XStack space={'$2'} flexWrap="wrap">
|
||||
<PairedDeviceCard />
|
||||
|
||||
<ConsensusGaugeCard
|
||||
color="blue"
|
||||
synced={134879}
|
||||
|
@ -67,9 +68,9 @@ const ConsensusSelection = () => {
|
|||
Install Consensus client
|
||||
</TextTam>
|
||||
|
||||
<XStack space={'$8'}>
|
||||
<XStack space={'$8'} flexWrap="wrap">
|
||||
<ConsensusClientCard name={clients[0].name} icon={clients[0].icon} />
|
||||
<YStack width={'67%'} space={'$4'}>
|
||||
<YStack width={'67%'} maxWidth="550px" space={'$4'}>
|
||||
<Text size={19}>The resource efficient Ethereum Clients.</Text>
|
||||
<Text size={15}>
|
||||
{selectedClient} is a client implementation for both execution and consensus layers that
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
.osCardsContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
display: grid;
|
||||
grid-gap: 15px;
|
||||
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.osCard {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 16px;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.osCardSelected {
|
||||
background-color: #2a4af50d;
|
||||
border: 1px solid #2a4af566;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.osCardsContainer {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.osCard:nth-child(3) {
|
||||
width: 205%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
.osCardsContainer {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
.osCard {
|
||||
width: 100%;
|
||||
}
|
||||
.osCard:nth-child(3) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ export const MacOS: Story = {
|
|||
args: {
|
||||
icon: '/icons/apple-logo.svg',
|
||||
name: MAC,
|
||||
isSelected: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -24,7 +23,6 @@ export const Linux: Story = {
|
|||
args: {
|
||||
icon: '/icons/linux-logo.svg',
|
||||
name: LINUX,
|
||||
isSelected: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -32,7 +30,6 @@ export const Windows: Story = {
|
|||
args: {
|
||||
icon: '/icons/windows-logo.svg',
|
||||
name: WINDOWS,
|
||||
isSelected: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -40,6 +37,5 @@ export const NotSelectedMacOS = {
|
|||
args: {
|
||||
icon: '/icons/apple-logo.svg',
|
||||
name: MAC,
|
||||
isSelected: false,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,36 +1,19 @@
|
|||
import { Stack, YStack } from 'tamagui'
|
||||
import { Stack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
import Icon from '../../../../components/General/Icon'
|
||||
|
||||
type OSCardProps = {
|
||||
name: string
|
||||
icon: string
|
||||
onClick?: () => void
|
||||
isSelected?: boolean
|
||||
}
|
||||
|
||||
const OSCard = ({ name, icon, onClick, isSelected }: OSCardProps) => {
|
||||
const OSCard = ({ name, icon }: OSCardProps) => {
|
||||
return (
|
||||
<YStack
|
||||
style={{
|
||||
backgroundColor: isSelected ? '#2A4AF50D' : 'none',
|
||||
border: isSelected ? '1px solid #2A4AF566' : '1px solid rgba(0, 0, 0, 0.15);',
|
||||
borderRadius: '16px',
|
||||
padding: '12px 16px',
|
||||
width: '32%',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
space={'$8'}
|
||||
onPress={onClick}
|
||||
>
|
||||
<Stack>
|
||||
<Text size={19} weight={'semibold'}>
|
||||
{name}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Icon src={icon} width={42} height={52} />
|
||||
</YStack>
|
||||
<Stack>
|
||||
<Text size={19} weight={'semibold'}>
|
||||
{name}
|
||||
</Text>
|
||||
<Icon src={icon} width={90} height={110} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,41 +1,30 @@
|
|||
import { XStack } from 'tamagui'
|
||||
|
||||
import OSCard from './OSCard'
|
||||
import { LINUX, MAC, WINDOWS } from '../../../../constants'
|
||||
import styles from './InstallLayout.module.css'
|
||||
|
||||
const cards = [
|
||||
{
|
||||
name: MAC,
|
||||
icon: '/icons/apple-logo.svg',
|
||||
},
|
||||
{
|
||||
name: LINUX,
|
||||
icon: '/icons/linux-logo.svg',
|
||||
},
|
||||
{
|
||||
name: WINDOWS,
|
||||
icon: '/icons/windows-logo.svg',
|
||||
},
|
||||
{ name: MAC, icon: '/icons/apple-logo.svg' },
|
||||
{ name: LINUX, icon: '/icons/linux-logo.svg' },
|
||||
{ name: WINDOWS, icon: '/icons/windows-logo.svg' },
|
||||
]
|
||||
|
||||
type OSCardsProps = {
|
||||
selectedOS: string
|
||||
handleOSCardClick: (os: string) => void
|
||||
}
|
||||
|
||||
const OSCards = ({ selectedOS, handleOSCardClick }: OSCardsProps) => {
|
||||
return (
|
||||
<XStack justifyContent={'space-between'} my={'15px'}>
|
||||
<div className={styles.osCardsContainer}>
|
||||
{cards.map(card => (
|
||||
<OSCard
|
||||
<div
|
||||
key={card.name}
|
||||
icon={card.icon}
|
||||
name={card.name}
|
||||
isSelected={selectedOS === card.name}
|
||||
className={`${styles.osCard} ${selectedOS === card.name ? styles.osCardSelected : ''}`}
|
||||
onClick={() => handleOSCardClick(card.name)}
|
||||
/>
|
||||
>
|
||||
<OSCard key={card.name} icon={card.icon} name={card.name} />
|
||||
</div>
|
||||
))}
|
||||
</XStack>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { YStack } from 'tamagui'
|
||||
import { Stack, YStack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useState } from 'react'
|
||||
|
@ -18,7 +18,7 @@ const ValidatorSetupInstall = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<YStack style={{ width: '100%', padding: '26px 32px' }}>
|
||||
<YStack style={{ padding: '26px 32px', width: 'fit-content' }}>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
Validator Setup
|
||||
</Text>
|
||||
|
@ -35,9 +35,11 @@ const ValidatorSetupInstall = () => {
|
|||
<Text size={19} weight={'semibold'}>
|
||||
Installing {selectedClient}
|
||||
</Text>
|
||||
<Markdown children={DOCUMENTATIONS[selectedClient].general} />
|
||||
<OSCards selectedOS={selectedOS} handleOSCardClick={handleOSCardClick} />
|
||||
<Markdown children={DOCUMENTATIONS[selectedClient].documentation[selectedOS]} />
|
||||
<Stack>
|
||||
<Markdown children={DOCUMENTATIONS[selectedClient].general} />
|
||||
<OSCards selectedOS={selectedOS} handleOSCardClick={handleOSCardClick} />
|
||||
<Markdown children={DOCUMENTATIONS[selectedClient].documentation[selectedOS]} />
|
||||
</Stack>
|
||||
</YStack>
|
||||
</YStack>
|
||||
)
|
||||
|
|
|
@ -28,8 +28,9 @@ const ExecClientCard = ({ name, icon, isComingSoon }: ExecClientCardProps) => {
|
|||
: '1px solid #DCE0E5',
|
||||
borderRadius: '16px',
|
||||
padding: '12px 16px',
|
||||
width: '19%',
|
||||
cursor: 'pointer',
|
||||
width: '100%',
|
||||
minWidth: '150px',
|
||||
}}
|
||||
space={'$8'}
|
||||
onClick={() => {
|
||||
|
@ -49,7 +50,6 @@ const ExecClientCard = ({ name, icon, isComingSoon }: ExecClientCardProps) => {
|
|||
alignItems: 'center',
|
||||
padding: '3px 6px',
|
||||
borderRadius: '67px',
|
||||
width: 'fit-content',
|
||||
}}
|
||||
>
|
||||
<Text size={11} color="#fff">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
import { Stack, YStack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
import ExecClientCard from './ExecClientCard'
|
||||
|
@ -10,11 +10,17 @@ const ExecClientCards = () => {
|
|||
<Stack style={{ marginTop: '15px', marginLeft: 0, marginBottom: '15px' }}>
|
||||
<Text size={27}>Select Execution client</Text>
|
||||
</Stack>
|
||||
<XStack justifyContent={'space-between'}>
|
||||
<Stack
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
{Object.entries(DOCUMENTATIONS).map(([name, { icon }], index) => (
|
||||
<ExecClientCard key={index} name={name} icon={icon} />
|
||||
))}
|
||||
</XStack>
|
||||
</Stack>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ const ValidatorSetup = () => {
|
|||
minHeight={'65vh'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<XStack justifyContent={'space-between'} alignItems={'center'}>
|
||||
<XStack justifyContent={'space-between'} alignItems={'center'} flexWrap="wrap" space={'$8'}>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
Validator Setup
|
||||
</Text>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.gradient-wrapper .image-container .background-img {
|
||||
.gradientWrapper .image-container .background-img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 75%;
|
||||
|
@ -6,7 +6,7 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.gradient-wrapper:after {
|
||||
.gradientWrapper:after {
|
||||
display: block;
|
||||
content: '';
|
||||
position: absolute;
|
|
@ -68,3 +68,7 @@ export const getHeightPercentages = (amountOfElements: number) => {
|
|||
|
||||
return `${percentages}%`
|
||||
}
|
||||
|
||||
export const copyFunction = (text: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue