Merge pull request #18 from nimbus-gui/UI-feedback-fixes
hn.UI feedback fixes
|
@ -32,8 +32,14 @@
|
|||
"@types/react": "18",
|
||||
"@types/react-chartjs-2": "^2.5.7",
|
||||
"@types/react-dom": "18",
|
||||
"@web3-onboard/core": "^2.21.2",
|
||||
"@web3-onboard/injected-wallets": "^2.10.6",
|
||||
"@web3-onboard/ledger": "^2.5.2",
|
||||
"@web3-onboard/react": "^2.8.13",
|
||||
"@web3-onboard/walletconnect": "^2.4.6",
|
||||
"chart.js": "^4.4.0",
|
||||
"emoji-picker-react": "^4.4.11",
|
||||
"ethers": "^6.7.1",
|
||||
"expo-modules-core": "^1.5.9",
|
||||
"react": "18",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
|
@ -48,7 +54,8 @@
|
|||
"react-syntax-highlighter": "^15.5.0",
|
||||
"recharts": "^2.8.0",
|
||||
"tamagui": "1.36.4",
|
||||
"web-bip39": "^0.0.3"
|
||||
"web-bip39": "^0.0.3",
|
||||
"web3-validator": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fsouza/prettierd": "^0.24.2",
|
||||
|
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,16 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Token Logo/ETH" clip-path="url(#clip0_2167_1766)">
|
||||
<path id="Vector" d="M16 32C24.8365 32 32 24.8365 32 16C32 7.16344 24.8365 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8365 7.16344 32 16 32Z" fill="#2A4CF4"/>
|
||||
<path id="Vector_2" d="M9.35999 17.4629L16.015 26.8385V21.3947L9.35999 17.4629Z" fill="white"/>
|
||||
<path id="Vector_3" opacity="0.6" d="M16.0151 21.3947V26.8385L22.6727 17.4628L16.0151 21.3947Z" fill="white"/>
|
||||
<path id="Vector_4" opacity="0.3" d="M22.6671 16.2018L16.0149 13.1752L16.0148 20.1336L22.6671 16.2018Z" fill="white"/>
|
||||
<path id="Vector_5" opacity="0.6" d="M9.36008 16.202L16.0152 13.1754L16.0149 20.1336L9.36008 16.202Z" fill="white"/>
|
||||
<path id="Vector_6" opacity="0.6" d="M16.0149 5.16L22.6672 16.2018L16.0152 13.1753L16.0149 5.16Z" fill="white"/>
|
||||
<path id="Vector_7" d="M16.0152 5.16V13.1753L9.36008 16.202L16.0152 5.16Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2167_1766">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,16 @@
|
|||
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Token Logo/ETH" clip-path="url(#clip0_2167_1636)">
|
||||
<path id="Vector" d="M16 32.5C24.8365 32.5 32 25.3365 32 16.5C32 7.66344 24.8365 0.5 16 0.5C7.16344 0.5 0 7.66344 0 16.5C0 25.3365 7.16344 32.5 16 32.5Z" fill="black"/>
|
||||
<path id="Vector_2" d="M9.36011 17.9629L16.0151 27.3385V21.8947L9.36011 17.9629Z" fill="white"/>
|
||||
<path id="Vector_3" opacity="0.6" d="M16.0151 21.8947V27.3385L22.6728 17.9628L16.0151 21.8947Z" fill="white"/>
|
||||
<path id="Vector_4" opacity="0.3" d="M22.6672 16.7018L16.015 13.6752L16.0149 20.6336L22.6672 16.7018Z" fill="white"/>
|
||||
<path id="Vector_5" opacity="0.6" d="M9.36011 16.702L16.0152 13.6754L16.0149 20.6336L9.36011 16.702Z" fill="white"/>
|
||||
<path id="Vector_6" opacity="0.6" d="M16.0149 5.66L22.6672 16.7018L16.0152 13.6753L16.0149 5.66Z" fill="white"/>
|
||||
<path id="Vector_7" d="M16.0152 5.66V13.6753L9.36011 16.702L16.0152 5.66Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2167_1636">
|
||||
<rect width="32" height="32" fill="white" transform="translate(0 0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,8 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Ring">
|
||||
<path id="Shape" d="M0 16C-2.76649e-07 12.8355 0.938383 9.74206 2.69649 7.11088C4.45459 4.47969 6.95345 2.42893 9.87706 1.21793C12.8007 0.00692558 16.0177 -0.309929 19.1214 0.307436C22.2251 0.924799 25.0761 2.44865 27.3137 4.68629C29.5513 6.92393 31.0752 9.77486 31.6926 12.8786C32.3099 15.9823 31.9931 19.1993 30.7821 22.1229C29.5711 25.0466 27.5203 27.5454 24.8891 29.3035C22.2579 31.0616 19.1645 32 16 32L16 16L0 16Z" fill="#FFC413"/>
|
||||
<path id="Shape_2" d="M23.6395 30.0584C21.7934 31.0616 19.7677 31.6914 17.6781 31.9118C15.5886 32.1321 13.4761 31.9388 11.4612 31.3427C9.44638 30.7467 7.56868 29.7596 5.9353 28.4379C4.30193 27.1162 2.94489 25.4857 1.94165 23.6395C0.938407 21.7934 0.308621 19.7677 0.0882464 17.6781C-0.132128 15.5886 0.0612254 13.4761 0.657267 11.4612C1.25331 9.44638 2.24036 7.56867 3.56208 5.9353C4.88379 4.30193 6.51428 2.94489 8.36045 1.94165L16 16L23.6395 30.0584Z" fill="#3FAEF9"/>
|
||||
<path id="Shape_3" d="M30.3827 8.99013C29.4621 7.10137 28.1786 5.41242 26.6053 4.01971C25.032 2.627 23.1999 1.55781 21.2134 0.873177C19.2269 0.188547 17.125 -0.0981102 15.0277 0.0295677C12.9305 0.157248 10.8789 0.696764 8.99013 1.61731C7.10137 2.53786 5.41242 3.82142 4.01971 5.39469C2.627 6.96797 1.5578 8.80015 0.873176 10.7866C0.188547 12.7731 -0.0981116 14.875 0.0295678 16.9723C0.157247 19.0695 0.696764 21.1211 1.61731 23.0099L16 16L30.3827 8.99013Z" fill="#FF9D9D"/>
|
||||
<path id="Shape_4" d="M31.4884 11.9863C30.6322 8.68243 28.742 5.73893 26.0939 3.58579C23.4458 1.43265 20.1786 0.182688 16.7696 0.0185204C13.3606 -0.145649 9.9884 0.784578 7.14568 2.6733C4.30295 4.56202 2.13865 7.31027 0.968975 10.5165L16 16L31.4884 11.9863Z" fill="#9B81FF"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 767 B |
51
src/App.tsx
|
@ -1,16 +1,19 @@
|
|||
import { TamaguiProvider, Theme } from 'tamagui'
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
|
||||
import { Provider as StatusProvider } from '@status-im/components'
|
||||
import { Web3OnboardProvider, init } from '@web3-onboard/react'
|
||||
import injectedModule from '@web3-onboard/injected-wallets'
|
||||
import walletConnectModule from '@web3-onboard/walletconnect'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import config from '../tamagui.config'
|
||||
import LandingPage from './pages/LandingPage/LandingPage'
|
||||
import DeviceHealthCheck from './pages/DeviceHealthCheck/DeviceHealthCheck'
|
||||
import ConnectDevicePage from './pages/ConnectDevicePage/ConnectDevicePage'
|
||||
import { RootState } from './redux/store'
|
||||
import DeviceSyncStatus from './pages/DeviceSyncStatus/DeviceSyncStatus'
|
||||
import PairDevice from './pages/PairDevice/PairDevice'
|
||||
import PinnedNotification from './components/General/PinnedNottification'
|
||||
import { RootState } from './redux/store'
|
||||
import CreateLocalNodePage from './pages/CreateLocalNodePage/CreateLocalNodePage'
|
||||
import ValidatorOnboarding from './pages/ValidatorOnboarding/ValidatorOnboarding'
|
||||
import Dashboard from './pages/Dashboard/Dashboard'
|
||||
|
@ -18,6 +21,38 @@ import './App.css'
|
|||
import ConnectExistingInstance from './pages/ConnectExistingInstance/ConnectExistingInstance'
|
||||
import './App.css'
|
||||
|
||||
const apiKey = '1730eff0-9d50-4382-a3fe-89f0d34a2070'
|
||||
const INFURA_KEY = 'f25e905e25a545dcaad2c939530b91db'
|
||||
const rpcUrl = `https://mainnet.infura.io/v3/${INFURA_KEY}`
|
||||
|
||||
const wcV2InitOptions = {
|
||||
projectId: 'abc123...',
|
||||
requiredChains: [1, 56],
|
||||
dappUrl: 'http://YourAwesomeDapp.com',
|
||||
}
|
||||
|
||||
const injected = injectedModule()
|
||||
const walletConnect = walletConnectModule(wcV2InitOptions)
|
||||
|
||||
const ethereumRopsten = {
|
||||
id: '0x3',
|
||||
token: 'rETH',
|
||||
label: 'Ethereum Ropsten',
|
||||
rpcUrl,
|
||||
}
|
||||
const chains = [ethereumRopsten]
|
||||
const wallets = [injected, walletConnect]
|
||||
const web3Onboard = init({
|
||||
apiKey,
|
||||
wallets,
|
||||
chains,
|
||||
appMetadata: {
|
||||
name: 'Web3-Onboard Demo',
|
||||
icon: '<svg>App Icon</svg>',
|
||||
description: 'A demo of Web3-Onboard.',
|
||||
},
|
||||
})
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
|
@ -53,12 +88,14 @@ function App() {
|
|||
|
||||
return (
|
||||
<TamaguiProvider config={config}>
|
||||
<StatusProvider>
|
||||
<Theme name={theme}>
|
||||
<PinnedNotification />
|
||||
<RouterProvider router={router} />
|
||||
</Theme>
|
||||
</StatusProvider>
|
||||
<Web3OnboardProvider web3Onboard={web3Onboard}>
|
||||
<StatusProvider>
|
||||
<Theme name={theme}>
|
||||
<PinnedNotification />
|
||||
<RouterProvider router={router} />
|
||||
</Theme>
|
||||
</StatusProvider>
|
||||
</Web3OnboardProvider>
|
||||
</TamaguiProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import IconText from '../General/IconText'
|
|||
import { Separator, XStack, YStack } from 'tamagui'
|
||||
import { Shadow, Text } from '@status-im/components'
|
||||
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
|
||||
type DataPoint = {
|
||||
x: number
|
||||
|
@ -18,21 +19,24 @@ type ChartData = {
|
|||
type DeviceCPULoadProps = {
|
||||
load: number[]
|
||||
}
|
||||
const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
|
||||
const DeviceCPULoad = ({ load }: DeviceCPULoadProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
const dataObj = load.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
}))
|
||||
const currentLoad = dataObj.length > 0 ? dataObj[dataObj.length - 1].y : 0
|
||||
|
||||
const message = currentLoad < 80 ? 'Good' : 'Poor'
|
||||
|
||||
const chartData: ChartData[] = [
|
||||
{
|
||||
id: 'cpu',
|
||||
color: '#8DC6BC',
|
||||
data: load.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
})),
|
||||
color: message === 'Good' ? '#8DC6BC' : '#e95460',
|
||||
data: dataObj,
|
||||
},
|
||||
]
|
||||
const currentLoad =
|
||||
chartData[0].data.length > 0 ? chartData[0].data[chartData[0].data.length - 1].y : 0
|
||||
|
||||
const message = currentLoad < 80 ? 'Good' : 'Poor'
|
||||
|
||||
return (
|
||||
<Shadow
|
||||
|
@ -41,9 +45,11 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
|
|||
width: '50%',
|
||||
minHeight: '135px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : '1px solid #E0E0E0',
|
||||
backgroundColor: isHovered ? '#f8f6ff' : message === 'Poor' ? '#fefafa' : '#fff',
|
||||
}}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
|
@ -54,7 +60,7 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
|
|||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||
<StandartLineChart data={chartData} />
|
||||
<StandartLineChart data={chartData} isInteractive={false} />
|
||||
</div>
|
||||
<YStack space={'$3'}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
|
|
|
@ -4,6 +4,7 @@ import IconText from '../General/IconText'
|
|||
import { Separator, XStack, YStack } from 'tamagui'
|
||||
import { Shadow as ShadowBox, Text } from '@status-im/components'
|
||||
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
|
||||
type DataPoint = {
|
||||
x: number
|
||||
|
@ -22,21 +23,24 @@ type DeviceMemoryHealthProps = {
|
|||
maxMemory: number
|
||||
}
|
||||
const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
const dataObj = currentMemory.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
}))
|
||||
const currentLoad = dataObj.length > 0 ? dataObj[dataObj.length - 1].y : 0
|
||||
|
||||
const message = currentLoad < maxMemory ? 'Good' : 'Poor'
|
||||
|
||||
const chartData: ChartData[] = [
|
||||
{
|
||||
id: 'cpu',
|
||||
color: '#8DC6BC',
|
||||
data: currentMemory.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
})),
|
||||
color: message == 'Good' ? '#8DC6BC' : '#e95460',
|
||||
data: dataObj,
|
||||
maxValue: maxMemory,
|
||||
},
|
||||
]
|
||||
const currentLoad =
|
||||
chartData[0].data.length > 0 ? chartData[0].data[chartData[0].data.length - 1].y : 0
|
||||
|
||||
const message = currentLoad < maxMemory ? 'Good' : 'Poor'
|
||||
|
||||
return (
|
||||
<ShadowBox
|
||||
|
@ -45,9 +49,11 @@ const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProp
|
|||
width: '50%',
|
||||
minHeight: '135px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : '1px solid #E0E0E0',
|
||||
backgroundColor: isHovered ? '#f8f6ff' : message === 'Poor' ? '#fefafa' : '#fff',
|
||||
}}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
|
@ -58,7 +64,7 @@ const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProp
|
|||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||
<StandartLineChart data={chartData} />
|
||||
<StandartLineChart data={chartData} isInteractive={false} />
|
||||
</div>
|
||||
<YStack space={'$3'}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
|
|
|
@ -16,21 +16,18 @@ type Story = StoryObj<typeof meta>
|
|||
|
||||
export const GoodStats: Story = {
|
||||
args: {
|
||||
uploadRate: [1, 4, 23, 61, 34],
|
||||
downloadRate: [20, 3, 40, 56, 32],
|
||||
latency: [55, 31, 5, 14, 20, 81, 50, 34, 12, 50, 4, 90, 56, 35, 59],
|
||||
},
|
||||
}
|
||||
|
||||
export const BadStats: Story = {
|
||||
args: {
|
||||
uploadRate: [1, 4, 23, 55],
|
||||
downloadRate: [20, 3, 40, 56, 80],
|
||||
latency: [55, 31, 5, 14, 20, 81, 50, 34, 12, 50, 4, 90, 56, 35],
|
||||
},
|
||||
}
|
||||
|
||||
export const NoStats: Story = {
|
||||
args: {
|
||||
uploadRate: [],
|
||||
downloadRate: [],
|
||||
latency: [],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import IconText from '../General/IconText'
|
|||
import { Separator, XStack, YStack } from 'tamagui'
|
||||
import { Shadow as ShadowBox, Text } from '@status-im/components'
|
||||
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
|
||||
type DataPoint = {
|
||||
x: number
|
||||
|
@ -14,34 +15,35 @@ type ChartData = {
|
|||
color: string
|
||||
data: DataPoint[]
|
||||
}
|
||||
|
||||
type DeviceNetworkHealthProps = {
|
||||
uploadRate: number[]
|
||||
downloadRate: number[]
|
||||
latency: number[]
|
||||
}
|
||||
const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthProps) => {
|
||||
const chartData: ChartData[] = [
|
||||
{
|
||||
id: 'uploadRate',
|
||||
color: '#8DC6BC',
|
||||
data: uploadRate.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
})),
|
||||
},
|
||||
{
|
||||
id: 'downloadRate',
|
||||
color: '#D92344',
|
||||
data: downloadRate.map((yValue, index: number) => ({
|
||||
x: index + 1,
|
||||
y: yValue,
|
||||
})),
|
||||
},
|
||||
]
|
||||
const currentLoad =
|
||||
chartData[0].data.length > 0 ? chartData[0].data[chartData[0].data.length - 1].y : 0
|
||||
|
||||
const message = currentLoad > 60 ? 'Good' : 'Poor'
|
||||
const DeviceNetworkHealth = ({ latency }: DeviceNetworkHealthProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
const THRESHOLD = 60
|
||||
const GOOD_COLOR = '#8DC6BC'
|
||||
const POOR_COLOR_LATENCY = '#D92344'
|
||||
|
||||
const processLatency = (latency: number[], id: string) => {
|
||||
const dataObj = latency.map((yValue, index: number) => ({ x: index + 1, y: yValue }))
|
||||
const currentLatency = dataObj.length > 0 ? dataObj[dataObj.length - 1].y : 0
|
||||
const message = currentLatency < THRESHOLD ? 'Good' : 'Poor'
|
||||
const color = message === 'Good' ? GOOD_COLOR : POOR_COLOR_LATENCY
|
||||
|
||||
return {
|
||||
id,
|
||||
color,
|
||||
data: dataObj,
|
||||
currentLatency,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
const processedLatency = processLatency(latency, 'latency')
|
||||
|
||||
const chartData: ChartData[] = [processedLatency]
|
||||
|
||||
return (
|
||||
<ShadowBox
|
||||
|
@ -50,9 +52,15 @@ const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthPr
|
|||
width: '50%',
|
||||
minHeight: '135px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
border: processedLatency.message === 'Poor' ? '1px solid #D92344' : '1px solid #E0E0E0',
|
||||
backgroundColor: isHovered
|
||||
? '#f8f6ff'
|
||||
: processedLatency.message === 'Poor'
|
||||
? '#fefafa'
|
||||
: '#fff',
|
||||
}}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
|
@ -63,28 +71,34 @@ const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthPr
|
|||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||
<StandartLineChart data={chartData} />
|
||||
<StandartLineChart data={chartData} isInteractive={false} />
|
||||
</div>
|
||||
<YStack space={'$3'}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
Network
|
||||
</Text>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
{currentLoad} GB
|
||||
{processedLatency.currentLatency} ms
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<Separator borderColor={'#e3e3e3'} />
|
||||
<XStack space={'$4'} style={{ padding: '0.65rem 1rem' }}>
|
||||
<IconText
|
||||
icon={message === 'Good' ? <CheckCircleIcon size={16} /> : <IncorrectIcon size={16} />}
|
||||
icon={
|
||||
processedLatency.message === 'Good' ? (
|
||||
<CheckCircleIcon size={16} />
|
||||
) : (
|
||||
<IncorrectIcon size={16} />
|
||||
)
|
||||
}
|
||||
weight={'semibold'}
|
||||
>
|
||||
{message}
|
||||
{processedLatency.message}
|
||||
</IconText>
|
||||
{message === 'Poor' && (
|
||||
{processedLatency.message === 'Poor' && (
|
||||
<Text size={13} color={'#E95460'} weight={'semibold'}>
|
||||
{((currentLoad / 60) * 100).toFixed(0)}% Utilization
|
||||
{`High Latency: ${processedLatency.currentLatency}ms`}
|
||||
</Text>
|
||||
)}
|
||||
</XStack>
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
import IconText from '../General/IconText'
|
||||
import { Separator, XStack, YStack } from 'tamagui'
|
||||
import { Separator, Stack, XStack, YStack } from 'tamagui'
|
||||
import StandardGauge from './StandardGauge'
|
||||
import { Shadow, Text } from '@status-im/components'
|
||||
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
|
||||
interface DeviceStorageHealthProps {
|
||||
storage: number
|
||||
maxStorage: number
|
||||
}
|
||||
const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxStorage }) => {
|
||||
const GOOD_COLOR = '#8DC6BC'
|
||||
const POOR_COLOR = '#E95460'
|
||||
|
||||
const DeviceStorageHealth = ({ storage, maxStorage }: DeviceStorageHealthProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
const message = storage < maxStorage ? 'Good' : 'Poor'
|
||||
const free = maxStorage - storage
|
||||
const utilization = (storage / (maxStorage || 1)) * 100
|
||||
|
@ -18,7 +25,7 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
|
|||
id: 'storage',
|
||||
label: 'Used',
|
||||
value: storage,
|
||||
color: '#E95460',
|
||||
color: message === 'Good' ? GOOD_COLOR : POOR_COLOR,
|
||||
},
|
||||
{
|
||||
id: 'storage',
|
||||
|
@ -28,7 +35,6 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
|
|||
},
|
||||
]
|
||||
}
|
||||
|
||||
return (
|
||||
<Shadow
|
||||
variant="$2"
|
||||
|
@ -36,9 +42,11 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
|
|||
width: '50%',
|
||||
minHeight: '135px',
|
||||
borderRadius: '16px',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : 'none',
|
||||
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
|
||||
border: message === 'Poor' ? '1px solid #D92344' : '1px solid #E0E0E0',
|
||||
backgroundColor: isHovered ? '#f8f6ff' : message === 'Poor' ? '#fefafa' : '#fff',
|
||||
}}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<YStack>
|
||||
<XStack
|
||||
|
@ -48,7 +56,7 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
|
|||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
<Stack
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '33px',
|
||||
|
@ -56,8 +64,8 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
|
|||
height: '4.75rem',
|
||||
}}
|
||||
>
|
||||
<StandardGauge data={data(free)} />
|
||||
</div>
|
||||
<StandardGauge data={data(free)} isInteractive={false} />
|
||||
</Stack>
|
||||
<YStack space={'$3'}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
Storage
|
||||
|
|
|
@ -9,22 +9,22 @@ export interface GaugeDataPoint {
|
|||
|
||||
interface StandardGaugeProps {
|
||||
data: GaugeDataPoint[]
|
||||
isInteractive?: boolean
|
||||
}
|
||||
|
||||
const StandardGauge = ({ data }: StandardGaugeProps) => (
|
||||
const StandardGauge = ({ data, isInteractive = true }: StandardGaugeProps) => (
|
||||
<ResponsivePie
|
||||
data={data}
|
||||
margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
|
||||
innerRadius={0.65}
|
||||
colors={datum => datum.data.color}
|
||||
fit={false}
|
||||
activeOuterRadiusOffset={8}
|
||||
enableArcLinkLabels={false}
|
||||
arcLinkLabelsColor={{ from: 'color' }}
|
||||
enableArcLabels={false}
|
||||
legends={[]}
|
||||
motionConfig="gentle"
|
||||
animate={false}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
)
|
||||
|
||||
|
|
|
@ -13,8 +13,9 @@ interface ChartData {
|
|||
|
||||
interface StandartLineChartProps {
|
||||
data: ChartData[]
|
||||
isInteractive?: boolean
|
||||
}
|
||||
const StandartLineChart = ({ data }: StandartLineChartProps) => {
|
||||
const StandartLineChart = ({ data, isInteractive }: StandartLineChartProps) => {
|
||||
const maxMemory = data[0].maxValue || 'auto'
|
||||
const colors = data.map(dataset => dataset.color)
|
||||
|
||||
|
@ -42,6 +43,7 @@ const StandartLineChart = ({ data }: StandartLineChartProps) => {
|
|||
useMesh={true}
|
||||
legends={[]}
|
||||
colors={colors}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import CurrencyDropdown from './CurrencyDropdown'
|
||||
|
||||
const meta = {
|
||||
title: 'General/CurrencyDropdown',
|
||||
component: CurrencyDropdown,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof CurrencyDropdown>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
changeCurrency: () => {},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { Button, DropdownMenu } from '@status-im/components'
|
||||
import { ChevronDownIcon } from '@status-im/icons'
|
||||
|
||||
import { CURRENCIES } from '../../constants'
|
||||
import { CurrencyType } from './ValidatorsMenuWithPrice'
|
||||
|
||||
type CurrencyDropdownProps = {
|
||||
changeCurrency: (currency: CurrencyType) => void
|
||||
}
|
||||
|
||||
const CurrencyDropdown = ({ changeCurrency }: CurrencyDropdownProps) => {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<Button variant="ghost" size={24} icon={<ChevronDownIcon size={16} color={'#919191'} />} />
|
||||
<DropdownMenu.Content sideOffset={10} position="absolute" zIndex={999}>
|
||||
{Object.keys(CURRENCIES).map(currency => (
|
||||
<DropdownMenu.Item
|
||||
key={currency}
|
||||
label={currency}
|
||||
onSelect={() => changeCurrency(currency as CurrencyType)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export default CurrencyDropdown
|
|
@ -0,0 +1,51 @@
|
|||
import { useState } from 'react'
|
||||
import ValidatorsMenuWithPrice from './ValidatorsMenuWithPrice'
|
||||
import { CLIENT_SETUP_SUBTITLE, DEPOSIT_SUBTITLE } from '../../constants'
|
||||
|
||||
export default {
|
||||
title: 'General/ValidatorsMenuWithPrice',
|
||||
component: ValidatorsMenuWithPrice,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
|
||||
type WrapperComponentProps = {
|
||||
initialCount: number
|
||||
label: string
|
||||
}
|
||||
|
||||
const WrapperComponent = ({ initialCount, label }: WrapperComponentProps) => {
|
||||
const [validatorCount, setValidatorCount] = useState(initialCount)
|
||||
|
||||
const handleValidatorCountChange = (value: string) => {
|
||||
const numberValue = Number(value)
|
||||
if (!isNaN(numberValue)) {
|
||||
setValidatorCount(numberValue)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ValidatorsMenuWithPrice
|
||||
validatorCount={validatorCount}
|
||||
changeValidatorCountHandler={handleValidatorCountChange}
|
||||
label={label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const Default = () => <WrapperComponent initialCount={2} label={DEPOSIT_SUBTITLE} />
|
||||
|
||||
export const ClientSetupLabel = () => (
|
||||
<WrapperComponent initialCount={2} label={CLIENT_SETUP_SUBTITLE} />
|
||||
)
|
||||
|
||||
export const WithLongValidatorCount = () => (
|
||||
<WrapperComponent initialCount={2000} label={DEPOSIT_SUBTITLE} />
|
||||
)
|
||||
|
||||
export const WithoutValidatorCount = () => (
|
||||
<WrapperComponent initialCount={0} label={DEPOSIT_SUBTITLE} />
|
||||
)
|
||||
|
||||
export const WithoutLabel = () => <WrapperComponent initialCount={2} label={''} />
|
||||
|
||||
export const WithoutValues = () => <WrapperComponent initialCount={0} label={''} />
|
|
@ -0,0 +1,75 @@
|
|||
import { useState } from 'react'
|
||||
import { Input, Text } from '@status-im/components'
|
||||
import { AddIcon } from '@status-im/icons'
|
||||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
|
||||
import { CURRENCIES, ETH_PER_VALIDATOR } from '../../constants'
|
||||
import CurrencyDropdown from './CurrencyDropdown'
|
||||
|
||||
type ValidatorsMenuWithPriceProps = {
|
||||
validatorCount: number
|
||||
changeValidatorCountHandler: (value: string) => void
|
||||
label: string
|
||||
}
|
||||
|
||||
export type CurrencyType = keyof typeof CURRENCIES
|
||||
|
||||
const ValidatorsMenuWithPrice = ({
|
||||
validatorCount,
|
||||
changeValidatorCountHandler,
|
||||
label,
|
||||
}: ValidatorsMenuWithPriceProps) => {
|
||||
const [currency, setCurrency] = useState(Object.keys(CURRENCIES)[0] as CurrencyType)
|
||||
|
||||
const changeCurrency = (currency: CurrencyType) => {
|
||||
if (CURRENCIES[currency]) {
|
||||
setCurrency(currency)
|
||||
}
|
||||
}
|
||||
|
||||
const totalETH = validatorCount * ETH_PER_VALIDATOR
|
||||
const totalPrice = totalETH * CURRENCIES[currency as keyof typeof CURRENCIES]
|
||||
|
||||
return (
|
||||
<XStack justifyContent={'space-between'} width={'80%'}>
|
||||
<Stack space={'$2'}>
|
||||
<Text size={15} weight="regular" color={'#647084'}>
|
||||
{label}
|
||||
</Text>
|
||||
<Input
|
||||
icon={
|
||||
<AddIcon
|
||||
size={16}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => changeValidatorCountHandler((validatorCount + 1).toString())}
|
||||
/>
|
||||
}
|
||||
style={{ fontWeight: 'bold' }}
|
||||
value={validatorCount.toString()}
|
||||
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%' }}>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
{currency}
|
||||
</Text>
|
||||
<CurrencyDropdown changeCurrency={changeCurrency} />
|
||||
</XStack>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
{totalPrice.toFixed(2)} {currency}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ValidatorsMenuWithPrice
|
|
@ -16,6 +16,15 @@ 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
|
||||
|
||||
// for now, this will be constant values
|
||||
export const CURRENCIES = {
|
||||
USD: 1583.42,
|
||||
EUR: 1323.61,
|
||||
}
|
||||
export const DEPOSIT_SUBTITLE = 'Connect you Wallet to stake required ETH for new validators'
|
||||
export const CLIENT_SETUP_SUBTITLE = 'How many Validators would you like to run?'
|
||||
/* Dashboard */
|
||||
|
||||
export const years = [
|
||||
|
|
|
@ -57,39 +57,44 @@ const BalanceChartCard = () => {
|
|||
</Text>
|
||||
</XStack>
|
||||
</YStack>
|
||||
{isCalendarVisible && (
|
||||
<Calendar
|
||||
style={{
|
||||
backgroundColor: '#fff',
|
||||
position: 'absolute',
|
||||
zIndex: 9999,
|
||||
top: 30,
|
||||
left: 100,
|
||||
}}
|
||||
mode="range"
|
||||
selected={dateRange}
|
||||
onSelect={handleRangeSelect}
|
||||
/>
|
||||
)}
|
||||
<XStack
|
||||
onClick={() => setIsCalendarVisible(prev => !prev)}
|
||||
style={{
|
||||
border: '2px solid #09101C14',
|
||||
height: 'fit-content',
|
||||
padding: '3px',
|
||||
padding: '5px',
|
||||
borderRadius: '10px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
space={'$2'}
|
||||
>
|
||||
<Text size={13} weight={'semibold'}>
|
||||
{dateRange?.from ? dateRange.from.toLocaleDateString() + ' ->' : 'Start Date -> '}{' '}
|
||||
{dateRange?.from ? dateRange.from.toLocaleDateString() : `Start Date`}
|
||||
</Text>
|
||||
<Text size={13} weight={'semibold'}>
|
||||
{dateRange?.to ? dateRange.to.toLocaleDateString() : ' End Date'}
|
||||
{'->'}
|
||||
</Text>
|
||||
<Text size={13} weight={'semibold'}>
|
||||
{dateRange?.to ? dateRange.to.toLocaleDateString() : 'End Date'}
|
||||
</Text>
|
||||
<Icon src="/icons/edit.svg" />
|
||||
</XStack>
|
||||
</XStack>
|
||||
{isCalendarVisible && (
|
||||
<Calendar
|
||||
style={{
|
||||
backgroundColor: 'white',
|
||||
position: 'absolute',
|
||||
zIndex: 1000,
|
||||
top: '100%',
|
||||
right: '0',
|
||||
}}
|
||||
mode="range"
|
||||
selected={dateRange}
|
||||
onSelect={handleRangeSelect}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack>
|
||||
<LineChart years={filteredYears} userGains={filteredUserGains} />
|
||||
</Stack>
|
||||
|
|
|
@ -34,7 +34,7 @@ const LineChart = ({ years, userGains }: LineChartProps) => {
|
|||
},
|
||||
}
|
||||
|
||||
return <Line options={data.options} data={data} style={{ width: 'max-content' }} />
|
||||
return <Line options={data.options} data={data} />
|
||||
}
|
||||
|
||||
export default LineChart
|
||||
|
|
|
@ -17,7 +17,7 @@ import MemoryCard from './MemoryCard/MemoryCard'
|
|||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<YStack minHeight={'100vh'} maxWidth={'100vw'}>
|
||||
<YStack maxHeight={'100vh'} maxWidth={'100vw'}>
|
||||
<XStack justifyContent={'space-between'}>
|
||||
<LeftSidebar />
|
||||
|
||||
|
@ -25,7 +25,7 @@ const Dashboard = () => {
|
|||
space={'$4'}
|
||||
alignItems="start"
|
||||
px="24px"
|
||||
style={{ flexGrow: '1', marginTop: '16px' }}
|
||||
style={{ flexGrow: '1', marginTop: '16px', overflowY: 'scroll' }}
|
||||
>
|
||||
<TitleLogo />
|
||||
<XStack space={'$4'} justifyContent={'space-between'} width={'100%'}>
|
||||
|
|
|
@ -45,10 +45,7 @@ const DeviceHealthCheck = () => {
|
|||
currentMemory={deviceHealthState.memory}
|
||||
maxMemory={deviceHealthState.maxMemory}
|
||||
/>
|
||||
<DeviceNetworkHealth
|
||||
uploadRate={deviceHealthState.uploadRate}
|
||||
downloadRate={deviceHealthState.downloadRate}
|
||||
/>
|
||||
<DeviceNetworkHealth latency={deviceHealthState.latency} />
|
||||
</XStack>
|
||||
<HealthInfoSection
|
||||
usedStorage={120}
|
||||
|
|
|
@ -21,7 +21,7 @@ const LandingPage = () => {
|
|||
<XStack pt={'70px'}>
|
||||
<NimbusLogo />
|
||||
</XStack>
|
||||
<YStack style={{ width: '100%', margin: '30vh 0 4vh' }}>
|
||||
<YStack style={{ width: '100%', margin: '30vh 0 4vh' }} space={'16px'}>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
Light and performant clients, for all Ethereum validators.
|
||||
</Text>
|
||||
|
|
|
@ -29,11 +29,11 @@ const GenerateId = ({ isAwaitingPairing }: GenerateIdProps) => {
|
|||
</StatusText>
|
||||
<Button
|
||||
variant="outline"
|
||||
size={24}
|
||||
size={32}
|
||||
icon={<CompleteIdIcon size={20} />}
|
||||
onPress={generateIdHandler}
|
||||
>
|
||||
Generate ID
|
||||
Regenerate ID
|
||||
</Button>
|
||||
</XStack>
|
||||
<YStack space={'$2'}>
|
||||
|
|
|
@ -15,7 +15,7 @@ import Icon from '../../components/General/Icon'
|
|||
const PairDevice = () => {
|
||||
const [isAwaitingPairing, setIsAwaitingPairing] = useState(false)
|
||||
const isPaired = false
|
||||
const isPairing = false
|
||||
const isPairing = true
|
||||
|
||||
const changeSetIsAwaitingPairing = (result: boolean) => {
|
||||
setIsAwaitingPairing(result)
|
||||
|
|
|
@ -17,5 +17,8 @@ export default meta
|
|||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
args: {
|
||||
advisoriesIcons: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
|
||||
subStepAdvisories: 0,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
|
||||
import AdvisoriesContent from './AdvisoriesContent'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from '../../../redux/store'
|
||||
|
||||
type AdvisoryTopicsType = {
|
||||
[key: string]: string[]
|
||||
}
|
||||
|
||||
const Advisories = () => {
|
||||
const [selectedTitle, setSelectedTitle] = useState(Object.keys(advisoryTopics)[3])
|
||||
const { subStepAdvisories } = useSelector((state: RootState) => state.advisories)
|
||||
|
||||
const isSameTitle = (title: string) => selectedTitle === title
|
||||
const unicodeNumbers = ['➀', '➁', '➂', '➃', '➄', '➅']
|
||||
const advisoriesIcons = unicodeNumbers.map((number, index) =>
|
||||
index <= subStepAdvisories ? '✓' : number,
|
||||
)
|
||||
|
||||
const [selectedTitle, setSelectedTitle] = useState(Object.keys(advisoryTopics)[0])
|
||||
useEffect(() => {
|
||||
setSelectedTitle(Object.keys(advisoryTopics)[subStepAdvisories])
|
||||
}, [subStepAdvisories])
|
||||
|
||||
const isCurrent = (currentTitle: string): boolean => {
|
||||
const topics = Object.keys(advisoryTopics)
|
||||
const index = topics.indexOf(currentTitle)
|
||||
return index <= subStepAdvisories ? true : false
|
||||
}
|
||||
|
||||
return (
|
||||
<XStack
|
||||
|
@ -34,15 +50,15 @@ const Advisories = () => {
|
|||
>
|
||||
<Text
|
||||
size={19}
|
||||
weight={isSameTitle(title) && 'semibold'}
|
||||
color={isSameTitle(title) ? 'blue' : ''}
|
||||
weight={isCurrent(title) && 'semibold'}
|
||||
color={isCurrent(title) ? 'blue' : ''}
|
||||
>
|
||||
{unicodeNumbers[index]}
|
||||
{advisoriesIcons[index]}
|
||||
</Text>
|
||||
<Text
|
||||
size={19}
|
||||
weight={isSameTitle(title) && 'semibold'}
|
||||
color={isSameTitle(title) ? 'blue' : ''}
|
||||
weight={isCurrent(title) ? 'semibold' : ''}
|
||||
color={isCurrent(title) ? 'blue' : ''}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
|
@ -56,8 +72,6 @@ const Advisories = () => {
|
|||
|
||||
export default Advisories
|
||||
|
||||
const unicodeNumbers = ['➀', '➁', '➂', '➃', '➄', '➅']
|
||||
|
||||
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.',
|
||||
|
|
|
@ -1,13 +1,41 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { Separator, YStack } from 'tamagui'
|
||||
import { Text } from '@status-im/components'
|
||||
|
||||
import SetupRow from './SetupRow'
|
||||
import WithdrawalAddress from './WithdrawalAddress'
|
||||
import LinkWithArrow from '../../../components/General/LinkWithArrow'
|
||||
import ValidatorsMenuWithPrice from '../../../components/General/ValidatorsMenuWithPrice'
|
||||
import { CLIENT_SETUP_SUBTITLE } from '../../../constants'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { setIsValidatorSet } from '../../../redux/ValidatorOnboarding/ValidatorSetup/slice'
|
||||
|
||||
const ClientSetup = () => {
|
||||
const dispatch = useDispatch()
|
||||
const [validatorCount, setValidatorCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setIsValidatorSet(validatorCount > 0))
|
||||
}, [validatorCount])
|
||||
|
||||
const changeValidatorCountHandler = (value: string) => {
|
||||
const numberValue = Number(value)
|
||||
if (!isNaN(numberValue)) {
|
||||
setValidatorCount(numberValue)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<YStack space={'$8'} padding={'26px'} width={'100%'} minHeight={'65vh'}>
|
||||
<SetupRow title={'Setup up Validators'} />
|
||||
<YStack space={'$6'} padding={'26px'} width={'100%'} minHeight={'65vh'}>
|
||||
<YStack space={'$4'}>
|
||||
<Text size={19} weight={'semibold'}>
|
||||
Setup up Validators
|
||||
</Text>
|
||||
<ValidatorsMenuWithPrice
|
||||
validatorCount={validatorCount}
|
||||
changeValidatorCountHandler={changeValidatorCountHandler}
|
||||
label={CLIENT_SETUP_SUBTITLE}
|
||||
/>
|
||||
</YStack>
|
||||
<Separator borderColor={'#F0F2F5'} />
|
||||
<WithdrawalAddress title={'Withdrawal address'} />
|
||||
<LinkWithArrow
|
||||
|
@ -19,4 +47,5 @@ const ClientSetup = () => {
|
|||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClientSetup
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { withRouter } from 'storybook-addon-react-router-v6'
|
||||
|
||||
import SetupRow from './SetupRow'
|
||||
|
||||
const meta = {
|
||||
title: 'ValidatorOnboarding/SetupRow',
|
||||
component: SetupRow,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
decorators: [withRouter],
|
||||
} satisfies Meta<typeof SetupRow>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: 'Setup up Validators',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithoutTitle: Story = {
|
||||
args: {
|
||||
title: '',
|
||||
},
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
import { Input as StatusInput, Text } from '@status-im/components'
|
||||
import { AddIcon, ChevronDownIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
|
||||
type SetupRowProps = {
|
||||
title: string
|
||||
}
|
||||
|
||||
const SetupRow = ({ title }: SetupRowProps) => {
|
||||
const [validatorCount, setValidatorCount] = useState(0)
|
||||
|
||||
const addValidatorHandler = () => {
|
||||
setValidatorCount((state: number) => state + 1)
|
||||
}
|
||||
|
||||
const changeValidatorCountHandler = (e: any) => {
|
||||
if (!isNaN(e.target.value)) {
|
||||
setValidatorCount(Number(e.target.value))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<YStack space={'$4'}>
|
||||
<Text size={19} weight={'semibold'}>
|
||||
{title}
|
||||
</Text>
|
||||
<XStack justifyContent={'space-between'} width={'80%'}>
|
||||
<Stack space={'$2'}>
|
||||
<Text size={15} weight="regular" color={'#647084'}>
|
||||
How many Validators would you like to run?
|
||||
</Text>
|
||||
<StatusInput
|
||||
icon={<AddIcon size={16} style={{ cursor: 'pointer' }} onClick={addValidatorHandler} />}
|
||||
value={validatorCount.toString()}
|
||||
onChange={changeValidatorCountHandler}
|
||||
style={{ fontWeight: 'bold' }}
|
||||
/>
|
||||
</Stack>
|
||||
<YStack space={'$2'}>
|
||||
<Text size={15} weight={'semibold'} color="#09101C">
|
||||
ETH
|
||||
</Text>
|
||||
<Text size={27} weight={'semibold'} color="#09101C">
|
||||
64
|
||||
</Text>
|
||||
</YStack>
|
||||
<YStack space={'$2'}>
|
||||
<XStack style={{ justifyContent: 'space-between' }}>
|
||||
<Text size={15} weight={'semibold'} color="#09101C">
|
||||
USD
|
||||
</Text>
|
||||
<ChevronDownIcon size={16} color={'#919191'} />
|
||||
</XStack>
|
||||
<Text size={27} weight={'semibold'} color="#09101C">
|
||||
$4,273 USD
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
export default SetupRow
|
|
@ -2,6 +2,7 @@ import { Stack, YStack } from 'tamagui'
|
|||
import { InformationBox, Input as StatusInput, Text } from '@status-im/components'
|
||||
import { ClearIcon, CloseCircleIcon } from '@status-im/icons'
|
||||
import { useState } from 'react'
|
||||
import { isAddress } from 'web3-validator'
|
||||
|
||||
type WithdrawalAddressProps = {
|
||||
title: string
|
||||
|
@ -9,15 +10,19 @@ type WithdrawalAddressProps = {
|
|||
|
||||
const WithdrawalAddress = ({ title }: WithdrawalAddressProps) => {
|
||||
const [withdrawalAddress, setWithdrawalAddress] = useState('')
|
||||
|
||||
const changeWithdrawalAddressHandler = (e: any) => {
|
||||
setWithdrawalAddress(e.target.value)
|
||||
const [isValidAddress, setIsValidAddress] = useState(true)
|
||||
const changeWithdrawalAddressHandler = (value: string) => {
|
||||
setWithdrawalAddress(value)
|
||||
}
|
||||
|
||||
const removeWithdrawalAddressHandler = () => {
|
||||
setWithdrawalAddress('')
|
||||
}
|
||||
|
||||
const checkAddress = (e: any) => {
|
||||
if (e.nativeEvent.text.length !== 0) {
|
||||
setIsValidAddress(isAddress(e.nativeEvent.text))
|
||||
}
|
||||
}
|
||||
return (
|
||||
<YStack space={'$4'}>
|
||||
<Text size={19} weight={'semibold'}>
|
||||
|
@ -39,7 +44,8 @@ const WithdrawalAddress = ({ title }: WithdrawalAddressProps) => {
|
|||
/>
|
||||
}
|
||||
value={withdrawalAddress}
|
||||
onChange={changeWithdrawalAddressHandler}
|
||||
onChangeText={changeWithdrawalAddressHandler}
|
||||
onBlur={e => checkAddress(e)}
|
||||
/>
|
||||
</Stack>
|
||||
<InformationBox
|
||||
|
@ -47,6 +53,13 @@ const WithdrawalAddress = ({ title }: WithdrawalAddressProps) => {
|
|||
variant="error"
|
||||
icon={<CloseCircleIcon size={20} color="$red" />}
|
||||
/>
|
||||
{!isValidAddress && (
|
||||
<InformationBox
|
||||
message="Not valid ethereum address"
|
||||
variant="error"
|
||||
icon={<CloseCircleIcon size={20} color="$red" />}
|
||||
/>
|
||||
)}
|
||||
</YStack>
|
||||
</YStack>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Stack, XStack } from 'tamagui'
|
||||
import { Button, InformationBox } from '@status-im/components'
|
||||
import { CloseCircleIcon } from '@status-im/icons'
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
@ -14,6 +15,7 @@ import {
|
|||
setIsCopyPastedPhrase,
|
||||
setValidWords,
|
||||
} from '../../redux/ValidatorOnboarding/KeyGeneration/slice'
|
||||
import { setSubStepAdvisories } from '../../redux/ValidatorOnboarding/Advisories/slice'
|
||||
|
||||
const ContinueButton = () => {
|
||||
const [isDisabled, setIsDisabled] = useState(false)
|
||||
|
@ -25,9 +27,13 @@ const ContinueButton = () => {
|
|||
recoveryMechanism,
|
||||
generatedMnemonic,
|
||||
} = useSelector((state: RootState) => state.keyGeneration)
|
||||
|
||||
const { activeStep, subStepValidatorSetup } = useSelector(
|
||||
(state: RootState) => state.validatorOnboarding,
|
||||
)
|
||||
const { isValidatorSet } = useSelector((state: RootState) => state.validatorSetup)
|
||||
const { subStepAdvisories } = useSelector((state: RootState) => state.advisories)
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
const isActivationValScreen = activeStep === 3 && subStepValidatorSetup === 3
|
||||
|
@ -35,20 +41,42 @@ const ContinueButton = () => {
|
|||
useEffect(() => {
|
||||
const getDisabledButton = () => {
|
||||
if (activeStep === 4 && isConfirmPhraseStage) {
|
||||
if (validWords.some(w => w === false)) {
|
||||
return false
|
||||
if (
|
||||
validWords.some(w => w === false) ||
|
||||
generatedMnemonic.some((w, i) => w !== mnemonic[i])
|
||||
) {
|
||||
return true
|
||||
}
|
||||
} else if (activeStep === 3 && !isValidatorSet) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
setIsDisabled(getDisabledButton())
|
||||
}, [activeStep, subStepValidatorSetup, isConfirmPhraseStage, mnemonic, validWords])
|
||||
}, [
|
||||
activeStep,
|
||||
subStepValidatorSetup,
|
||||
isConfirmPhraseStage,
|
||||
mnemonic,
|
||||
validWords,
|
||||
isValidatorSet,
|
||||
])
|
||||
|
||||
const handleStep3 = () => {
|
||||
subStepValidatorSetup < 3
|
||||
? dispatch(setSubStepValidatorSetup(subStepValidatorSetup + 1))
|
||||
: dispatch(setSubStepValidatorSetup(0))
|
||||
const handleStep1 = () => {
|
||||
if (subStepAdvisories < 5) {
|
||||
dispatch(setSubStepAdvisories(subStepAdvisories + 1))
|
||||
} else {
|
||||
dispatch(setSubStepAdvisories(0))
|
||||
dispatch(setActiveStep(activeStep + 1))
|
||||
}
|
||||
}
|
||||
|
||||
const handleStep2 = () => {
|
||||
if (subStepValidatorSetup === 3) {
|
||||
return dispatch(setActiveStep(activeStep + 1))
|
||||
}
|
||||
dispatch(setSubStepValidatorSetup(subStepValidatorSetup + 1))
|
||||
}
|
||||
|
||||
const handleStep4 = () => {
|
||||
|
@ -65,8 +93,7 @@ const ContinueButton = () => {
|
|||
dispatch(setValidWords(newValidWords))
|
||||
|
||||
if (!newValidWords.includes(false)) {
|
||||
setActiveStep(activeStep + 1)
|
||||
dispatch(setIsConfirmPhraseStage(false))
|
||||
dispatch(setActiveStep(activeStep + 1))
|
||||
if (isCopyPastedPhrase) {
|
||||
dispatch(setIsCopyPastedPhrase(false))
|
||||
}
|
||||
|
@ -75,13 +102,15 @@ const ContinueButton = () => {
|
|||
}
|
||||
|
||||
const continueHandler = () => {
|
||||
if (activeStep === 3) {
|
||||
handleStep3()
|
||||
if (activeStep === 1) {
|
||||
handleStep1()
|
||||
} else if (activeStep === 2) {
|
||||
handleStep2()
|
||||
} else if (activeStep === 4) {
|
||||
handleStep4()
|
||||
} else {
|
||||
if (activeStep < 5) {
|
||||
setActiveStep(activeStep + 1)
|
||||
if (activeStep < 6) {
|
||||
dispatch(setActiveStep(activeStep + 1))
|
||||
} else {
|
||||
navigate('/')
|
||||
}
|
||||
|
@ -116,7 +145,7 @@ const ContinueButton = () => {
|
|||
/>
|
||||
)}
|
||||
<Button onPress={continueHandler} size={40} disabled={isDisabled}>
|
||||
{activeStep < 5 ? 'Continue' : 'Continue to Dashboard'}
|
||||
{activeStep < 6 ? 'Continue' : 'Continue to Dashboard'}
|
||||
</Button>
|
||||
</XStack>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { Avatar, Button } from '@status-im/components'
|
||||
import { useAccountCenter, useConnectWallet } from '@web3-onboard/react'
|
||||
import { XStack } from 'tamagui'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import { setIsWalletConnected } from '../../../redux/ValidatorOnboarding/Deposit/slice'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
const ConnectWallet = () => {
|
||||
const [{ wallet, connecting }, connect, disconnect] = useConnectWallet()
|
||||
const updateAccountCenter = useAccountCenter()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet) {
|
||||
dispatch(setIsWalletConnected(true))
|
||||
} else {
|
||||
dispatch(setIsWalletConnected(false))
|
||||
}
|
||||
}, [wallet])
|
||||
|
||||
const onConnectWalletClick = () => {
|
||||
if (wallet) {
|
||||
disconnect(wallet)
|
||||
} else {
|
||||
connect()
|
||||
updateAccountCenter({ enabled: false })
|
||||
}
|
||||
}
|
||||
|
||||
// let ethersProvider
|
||||
// if (wallet) {
|
||||
// ethersProvider = new ethers.BrowserProvider(wallet.provider, 'any')
|
||||
// }
|
||||
|
||||
return (
|
||||
<XStack space={'$2'} alignItems={'center'}>
|
||||
<Avatar type="icon" size={32} icon={<img src={'/icons/eth-logo.svg'} alt="eth-logo" />} />
|
||||
<Button disabled={connecting} onPress={onConnectWalletClick}>
|
||||
{connecting ? 'Connecting' : wallet ? 'Disconnect Wallet' : 'Connect Wallet'}
|
||||
</Button>
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectWallet
|
|
@ -0,0 +1,70 @@
|
|||
import { Avatar, Text } from '@status-im/components'
|
||||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
|
||||
import { getFormattedWalletAddress } from '../../../utilities'
|
||||
import { useConnectWallet } from '@web3-onboard/react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ethers } from 'ethers'
|
||||
|
||||
const ConnectedWallet = () => {
|
||||
const [{ wallet }] = useConnectWallet()
|
||||
const [address, setAddress] = useState('')
|
||||
const [balance, setBalance] = useState('')
|
||||
const [networkName, setNetworkName] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBalance = async () => {
|
||||
if (wallet?.provider) {
|
||||
const ethProvider = new ethers.BrowserProvider(wallet.provider)
|
||||
const signer = ethProvider.getSigner()
|
||||
|
||||
const address = (await signer).address
|
||||
const userBalance = await ethProvider.getBalance(address)
|
||||
const network = await ethProvider.getNetwork()
|
||||
|
||||
setAddress(address)
|
||||
setBalance(ethers.formatEther(userBalance))
|
||||
setNetworkName(network.name)
|
||||
}
|
||||
}
|
||||
|
||||
fetchBalance()
|
||||
}, [wallet])
|
||||
|
||||
return (
|
||||
<XStack style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<XStack space={'$2'}>
|
||||
<Avatar
|
||||
type="icon"
|
||||
size={32}
|
||||
icon={
|
||||
<img src={'/icons/connected-wallet-eth-logo.svg'} alt="connected-wallet-eth-logo" />
|
||||
}
|
||||
/>
|
||||
<YStack>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
{networkName}
|
||||
</Text>
|
||||
<Text size={13} weight={'semibold'}>
|
||||
{getFormattedWalletAddress(address)}
|
||||
</Text>
|
||||
<Stack style={{ marginTop: '3px' }}>
|
||||
<Text size={13} color="#2A4CF4" weight={'semiboldF'}>
|
||||
Connected
|
||||
</Text>
|
||||
</Stack>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<YStack>
|
||||
<Text size={15} weight={'semibold'}>
|
||||
Balance
|
||||
</Text>
|
||||
<Text size={27} weight={'semibold'}>
|
||||
{balance} ETH
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectedWallet
|
|
@ -0,0 +1,79 @@
|
|||
import { DividerLine, InformationBox, Text } from '@status-im/components'
|
||||
import { PlaceholderIcon } from '@status-im/icons'
|
||||
import { YStack } from 'tamagui'
|
||||
import { useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import ValidatorRequest from './ValidatorRequest/ValidatorRequest'
|
||||
import ConnectWallet from './ConnectWallet'
|
||||
import ConnectedWallet from './ConnectedWallet'
|
||||
import DepositTitle from './DepositTitle'
|
||||
import ValidatorsMenuWithPrice from '../../../components/General/ValidatorsMenuWithPrice'
|
||||
import { RootState } from '../../../redux/store'
|
||||
import { DEPOSIT_SUBTITLE } from '../../../constants'
|
||||
|
||||
const Deposit = () => {
|
||||
const [isInfoBoxVisible, setIsInfoBoxVisible] = useState(true)
|
||||
const [validatorCount, setValidatorCount] = useState(2)
|
||||
const { isWalletConnected, isTransactionConfirmation } = useSelector(
|
||||
(state: RootState) => state.deposit,
|
||||
)
|
||||
|
||||
const changeValidatorCountHandler = (value: string) => {
|
||||
const numberValue = Number(value)
|
||||
if (!isNaN(numberValue)) {
|
||||
setValidatorCount(numberValue)
|
||||
}
|
||||
}
|
||||
|
||||
const onCloseInfoBox = () => {
|
||||
setIsInfoBoxVisible(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<YStack
|
||||
space={'$3'}
|
||||
style={{ width: '100%', padding: '16px 32px', alignItems: 'start', paddingBottom: '30px' }}
|
||||
>
|
||||
<DepositTitle />
|
||||
{isTransactionConfirmation ? (
|
||||
<Text size={15} weight="regular" color={'#647084'}>
|
||||
{DEPOSIT_SUBTITLE}
|
||||
</Text>
|
||||
) : (
|
||||
<ValidatorsMenuWithPrice
|
||||
validatorCount={validatorCount}
|
||||
changeValidatorCountHandler={changeValidatorCountHandler}
|
||||
label={DEPOSIT_SUBTITLE}
|
||||
/>
|
||||
)}
|
||||
{isTransactionConfirmation && <ConnectedWallet />}
|
||||
<DividerLine style={{ marginTop: isTransactionConfirmation ? '0px' : '15px' }} />
|
||||
{Array.from({ length: validatorCount }).map((_, index) => (
|
||||
<ValidatorRequest
|
||||
key={index}
|
||||
number={index + 1}
|
||||
isTransactionConfirmation={isTransactionConfirmation}
|
||||
/>
|
||||
))}
|
||||
{isInfoBoxVisible && !isTransactionConfirmation && (
|
||||
<InformationBox
|
||||
message="Your Validator balances currently require a deposit. If you have already made a deposit using Launchpad please wait until the transaction is posted on execution layer to continue."
|
||||
variant="error"
|
||||
onClosePress={onCloseInfoBox}
|
||||
icon={<PlaceholderIcon size={16} />}
|
||||
/>
|
||||
)}
|
||||
{!isTransactionConfirmation && (
|
||||
<YStack space={'$3'} style={{ width: '100%' }}>
|
||||
<Text size={19} weight={'semibold'}>
|
||||
Connect Wallet
|
||||
</Text>
|
||||
{isWalletConnected ? <ConnectedWallet /> : <ConnectWallet />}
|
||||
</YStack>
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default Deposit
|
|
@ -0,0 +1,19 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DepositTitle from './DepositTitle'
|
||||
|
||||
const meta = {
|
||||
title: 'ValidatorOnboarding/DepositTitle',
|
||||
component: DepositTitle,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof DepositTitle>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import { RootState } from '../../../redux/store'
|
||||
|
||||
const DepositTitle = () => {
|
||||
const { isTransactionConfirmation } = useSelector((state: RootState) => state.deposit)
|
||||
|
||||
return (
|
||||
<Text size={19} weight={'semibold'}>
|
||||
{isTransactionConfirmation ? 'Transaction Confirmation' : 'Deposit Funds'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
export default DepositTitle
|
|
@ -0,0 +1,43 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import TransactionStatus from './TransactionStatus'
|
||||
|
||||
const meta = {
|
||||
title: 'ValidatorOnboarding/TransactionStatus',
|
||||
component: TransactionStatus,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof TransactionStatus>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
transactionStatus: '',
|
||||
isTransactionConfirmation: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const Complete: Story = {
|
||||
args: {
|
||||
transactionStatus: 'Complete',
|
||||
isTransactionConfirmation: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const Pending: Story = {
|
||||
args: {
|
||||
transactionStatus: 'Pending',
|
||||
isTransactionConfirmation: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const Fail: Story = {
|
||||
args: {
|
||||
transactionStatus: 'Fail',
|
||||
isTransactionConfirmation: true,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { FullscreenMacOsIcon, MinimizeMacOsIcon, CloseMacOsIcon } from '@status-im/icons'
|
||||
import { XStack } from 'tamagui'
|
||||
|
||||
type VTransactionStatusProps = {
|
||||
transactionStatus: string
|
||||
isTransactionConfirmation?: boolean
|
||||
}
|
||||
|
||||
const TransactionStatus = ({
|
||||
transactionStatus,
|
||||
isTransactionConfirmation,
|
||||
}: VTransactionStatusProps) => {
|
||||
const isTransactionCompleted = transactionStatus === 'Complete'
|
||||
|
||||
return (
|
||||
<>
|
||||
{isTransactionConfirmation ? (
|
||||
<XStack space={'$2'} alignItems={'center'}>
|
||||
{isTransactionCompleted && <FullscreenMacOsIcon size={20} />}
|
||||
{transactionStatus === 'Pending' && <MinimizeMacOsIcon size={20} />}
|
||||
{transactionStatus === 'Fail' && <CloseMacOsIcon size={20} />}
|
||||
<Text
|
||||
size={13}
|
||||
color={isTransactionCompleted ? '#2A4AF5' : '#828282'}
|
||||
weight={'semibold'}
|
||||
>
|
||||
Transaction {transactionStatus}
|
||||
</Text>
|
||||
</XStack>
|
||||
) : (
|
||||
<Text size={13} color="#2F80ED" weight={'semibold'}>
|
||||
Requires Deposit
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TransactionStatus
|
|
@ -0,0 +1,36 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import ValidatorRequest from './ValidatorRequest'
|
||||
|
||||
const meta = {
|
||||
title: 'ValidatorOnboarding/ValidatorRequest',
|
||||
component: ValidatorRequest,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof ValidatorRequest>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
number: 1,
|
||||
isTransactionConfirmation: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const TransactionConfirmation: Story = {
|
||||
args: {
|
||||
number: 1,
|
||||
isTransactionConfirmation: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const BigNumber: Story = {
|
||||
args: {
|
||||
number: 123456789,
|
||||
isTransactionConfirmation: false,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { Avatar, DividerLine, Text } from '@status-im/components'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
|
||||
import { getFormattedValidatorAddress } from '../../../../utilities'
|
||||
import TransactionStatus from './TransactionStatus'
|
||||
|
||||
type ValidatorRequestProps = {
|
||||
number: number
|
||||
isTransactionConfirmation?: boolean
|
||||
}
|
||||
|
||||
const ValidatorRequest = ({ number, isTransactionConfirmation }: ValidatorRequestProps) => {
|
||||
let transactionStatus = 'Complete'
|
||||
const isTransactionCompleted = transactionStatus === 'Complete'
|
||||
|
||||
return (
|
||||
<YStack space={'$3'} style={{ width: '100%' }}>
|
||||
<XStack style={{ justifyContent: 'space-between', width: '100%', alignItems: 'center' }}>
|
||||
<XStack style={{ justifyContent: 'space-between', width: '44%', alignItems: 'center' }}>
|
||||
<XStack space={'$2'}>
|
||||
<Avatar
|
||||
type="user"
|
||||
size={32}
|
||||
src="/icons/validator-request.svg"
|
||||
name={number.toString()}
|
||||
indicator="online"
|
||||
/>
|
||||
<YStack>
|
||||
<Text size={13} weight={'semibold'}>
|
||||
Validator {number}
|
||||
</Text>
|
||||
<Text size={13} color="#647084">
|
||||
{getFormattedValidatorAddress('zQ3asdf9d4Gs0')}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<Text size={13} color="#647084" weight={'semibold'}>
|
||||
Keys Generated
|
||||
</Text>
|
||||
</XStack>
|
||||
<XStack
|
||||
style={{
|
||||
justifyContent: isTransactionConfirmation ? 'space-between' : 'end',
|
||||
width: '53%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{isTransactionConfirmation && (
|
||||
<Text
|
||||
color={isTransactionCompleted ? '#2A4AF5' : '#E0E0E0'}
|
||||
size={13}
|
||||
weight={'semibold'}
|
||||
>
|
||||
{isTransactionCompleted ? 32 : 0} ETH
|
||||
</Text>
|
||||
)}
|
||||
<TransactionStatus
|
||||
transactionStatus={transactionStatus}
|
||||
isTransactionConfirmation={isTransactionConfirmation}
|
||||
/>
|
||||
</XStack>
|
||||
</XStack>
|
||||
<DividerLine />
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ValidatorRequest
|
|
@ -10,6 +10,7 @@ const steps = [
|
|||
{ 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' },
|
||||
]
|
||||
|
||||
|
@ -20,6 +21,12 @@ type FormStepperProps = {
|
|||
const FormStepper = ({ activeStep }: FormStepperProps) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const changeStepOnClickHandler = (index: number) => {
|
||||
if (activeStep > index) {
|
||||
dispatch(setActiveStep(index))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
|
@ -28,11 +35,10 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
|
|||
connectorStyleConfig={customConnectorStyle}
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
zIndex: 999,
|
||||
zIndex: 1,
|
||||
width: '100%',
|
||||
padding: 0,
|
||||
marginBottom: '3rem',
|
||||
fontFamily: 'Inter',
|
||||
}}
|
||||
>
|
||||
{steps.map((step, index) => (
|
||||
|
@ -40,7 +46,7 @@ const FormStepper = ({ activeStep }: FormStepperProps) => {
|
|||
key={index}
|
||||
label={step.label}
|
||||
className="custom-step"
|
||||
onClick={() => dispatch(setActiveStep(index))}
|
||||
onClick={() => changeStepOnClickHandler(index)}
|
||||
completed={activeStep > index - 1}
|
||||
data-subtitle={step.subtitle}
|
||||
data-step={step.label}
|
||||
|
|
|
@ -10,12 +10,10 @@ import ConfirmRecoveryPhrase from './ConfirmRecoveryPhrase/ConfirmRecoveryPhrase
|
|||
import { BOTH_KEY_AND_RECOVERY, KEYSTORE_FILES, RECOVERY_PHRASE } from '../../../constants'
|
||||
import { RootState } from '../../../redux/store'
|
||||
|
||||
type KeyGenerationProps = {
|
||||
isConfirmPhraseStage: boolean
|
||||
}
|
||||
|
||||
const KeyGeneration = ({ isConfirmPhraseStage }: KeyGenerationProps) => {
|
||||
const { recoveryMechanism } = useSelector((state: RootState) => state.keyGeneration)
|
||||
const KeyGeneration = () => {
|
||||
const { recoveryMechanism, isConfirmPhraseStage } = useSelector(
|
||||
(state: RootState) => state.keyGeneration,
|
||||
)
|
||||
|
||||
const isKeystoreFiles =
|
||||
recoveryMechanism === KEYSTORE_FILES || recoveryMechanism === BOTH_KEY_AND_RECOVERY
|
||||
|
|
|
@ -14,7 +14,7 @@ const ValidatorBoxWrapper = ({ children }: ValidatorBoxWrapperProps) => {
|
|||
border: 'none',
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#fff',
|
||||
zIndex: 999,
|
||||
zIndex: 1,
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { YStack } from 'tamagui'
|
|||
|
||||
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'
|
||||
|
@ -14,15 +15,18 @@ import Advisories from './Advisories/Advisories'
|
|||
import ValidatorSetup from './ValidatorSetup/ValidatorSetup/ValidatorSetup'
|
||||
import ValidatorSetupInstall from './ValidatorSetup/ValidatorInstalling/ValidatorInstall'
|
||||
import ContinueButton from './ContinueButton'
|
||||
import { RootState } from '../../redux/store'
|
||||
|
||||
import ActivationValidatorSetup from './ValidatorSetup/ValidatorActivation/ActivationValidatorSetup'
|
||||
import './layoutGradient.css'
|
||||
|
||||
import Deposit from './Deposit/Deposit'
|
||||
|
||||
import './layoutGradient.css'
|
||||
|
||||
const ValidatorOnboarding = () => {
|
||||
const { activeStep, subStepValidatorSetup } = useSelector(
|
||||
(state: RootState) => state.validatorOnboarding,
|
||||
)
|
||||
const { isConfirmPhraseStage } = useSelector((state: RootState) => state.keyGeneration)
|
||||
|
||||
return (
|
||||
<div className="gradient-wrapper">
|
||||
|
@ -44,15 +48,16 @@ const ValidatorOnboarding = () => {
|
|||
<ValidatorBoxWrapper>
|
||||
{activeStep === 0 && <Overview />}
|
||||
{activeStep === 1 && <Advisories />}
|
||||
{activeStep === 2 && <ClientSetup />}
|
||||
|
||||
{activeStep === 3 && subStepValidatorSetup === 0 && <ValidatorSetup />}
|
||||
{activeStep === 3 && subStepValidatorSetup === 1 && <ValidatorSetupInstall />}
|
||||
{activeStep === 3 && subStepValidatorSetup === 2 && <ConsensusSelection />}
|
||||
{activeStep === 3 && subStepValidatorSetup === 3 && <ActivationValidatorSetup />}
|
||||
{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 isConfirmPhraseStage={isConfirmPhraseStage} />}
|
||||
{activeStep === 5 && (
|
||||
{activeStep === 4 && <KeyGeneration />}
|
||||
{activeStep === 5 && <Deposit />}
|
||||
{activeStep === 6 && (
|
||||
<Activation
|
||||
validatorsValue="4"
|
||||
executionSyncStatus1={{
|
||||
|
|
|
@ -38,7 +38,7 @@ const ConsensusSelection = () => {
|
|||
Validator Setup
|
||||
</Text>
|
||||
<XStack space={'$2'}>
|
||||
<PairedDeviceCard isVisibleState={true} />
|
||||
<PairedDeviceCard />
|
||||
<ConsensusGaugeCard
|
||||
color="blue"
|
||||
synced={134879}
|
||||
|
|
|
@ -17,7 +17,5 @@ export default meta
|
|||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
isVisibleState: true,
|
||||
},
|
||||
args: {},
|
||||
}
|
||||
|
|
|
@ -1,21 +1,8 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { ClearIcon } from '@status-im/icons'
|
||||
import { InfoIcon } from '@status-im/icons'
|
||||
import { Avatar, Text } from '@status-im/components'
|
||||
|
||||
type PairedDeviceCardProps = {
|
||||
isVisibleState: boolean
|
||||
}
|
||||
|
||||
const PairedDeviceCard = ({ isVisibleState }: PairedDeviceCardProps) => {
|
||||
const [isVisible, setIsVisible] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setIsVisible(isVisibleState)
|
||||
}, [isVisibleState])
|
||||
|
||||
if (!isVisible) return null
|
||||
|
||||
const PairedDeviceCard = () => {
|
||||
return (
|
||||
<XStack
|
||||
space={'$7'}
|
||||
|
@ -37,7 +24,7 @@ const PairedDeviceCard = ({ isVisibleState }: PairedDeviceCardProps) => {
|
|||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<ClearIcon size={20} color="#A1ABBD" cursor={'pointer'} onClick={() => setIsVisible(false)} />
|
||||
<InfoIcon size={20} color="#A1ABBD" cursor={'pointer'} />
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ type Story = StoryObj<typeof meta>
|
|||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
icon: '/icons/MAC.png',
|
||||
icon: '/icons/MAC.svg',
|
||||
name: 'Mac',
|
||||
isSelected: true,
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Text } from '@status-im/components'
|
|||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import Icon from '../../../../components/General/Icon'
|
||||
import { selectClient } from '../../../../redux/ValidatorOnboarding/ValidatorSetup/slice'
|
||||
import { selectClient } from '../../../../redux/ValidatorOnboarding/ClientSetup/slice'
|
||||
import { RootState } from '../../../../redux/store'
|
||||
|
||||
type ExecClientCardProps = {
|
||||
|
|
|
@ -16,7 +16,7 @@ const ValidatorSetup = () => {
|
|||
<Text size={27} weight={'semibold'}>
|
||||
Validator Setup
|
||||
</Text>
|
||||
<PairedDeviceCard isVisibleState={true} />
|
||||
<PairedDeviceCard />
|
||||
</XStack>
|
||||
|
||||
<YStack>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
|
||||
interface AdvisoriesState {
|
||||
subStepAdvisories: number
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
subStepAdvisories: 0,
|
||||
}
|
||||
|
||||
const AdvisoriesSlice = createSlice({
|
||||
name: 'advisories',
|
||||
initialState,
|
||||
reducers: {
|
||||
setSubStepAdvisories: (state: AdvisoriesState, action: PayloadAction<number>) => {
|
||||
state.subStepAdvisories = action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { setSubStepAdvisories } = AdvisoriesSlice.actions
|
||||
|
||||
export default AdvisoriesSlice.reducer
|
|
@ -0,0 +1,23 @@
|
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
|
||||
interface ExecClientState {
|
||||
selectedClient: string
|
||||
}
|
||||
|
||||
const initialState: ExecClientState = {
|
||||
selectedClient: 'Erigon',
|
||||
}
|
||||
|
||||
const execClientSlice = createSlice({
|
||||
name: 'execClient',
|
||||
initialState,
|
||||
reducers: {
|
||||
selectClient: (state: ExecClientState, action: PayloadAction<string>) => {
|
||||
state.selectedClient = action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { selectClient } = execClientSlice.actions
|
||||
|
||||
export default execClientSlice.reducer
|
|
@ -0,0 +1,28 @@
|
|||
import { PayloadAction, createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
type DepositState = {
|
||||
isWalletConnected: boolean
|
||||
isTransactionConfirmation: boolean
|
||||
}
|
||||
|
||||
const initialState: DepositState = {
|
||||
isWalletConnected: false,
|
||||
isTransactionConfirmation: false,
|
||||
}
|
||||
|
||||
const depositSlice = createSlice({
|
||||
name: 'deposit',
|
||||
initialState,
|
||||
reducers: {
|
||||
setIsWalletConnected: (state, action: PayloadAction<boolean>) => {
|
||||
state.isWalletConnected = action.payload
|
||||
},
|
||||
setIsTransactionConfirmation: (state, action: PayloadAction<boolean>) => {
|
||||
state.isTransactionConfirmation = action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { setIsWalletConnected, setIsTransactionConfirmation } = depositSlice.actions
|
||||
|
||||
export default depositSlice.reducer
|
|
@ -1,23 +1,22 @@
|
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
|
||||
interface ExecClientState {
|
||||
selectedClient: string
|
||||
type ValidatorSetupState = {
|
||||
isValidatorSet: boolean
|
||||
}
|
||||
const initialState: ValidatorSetupState = {
|
||||
isValidatorSet: false,
|
||||
}
|
||||
|
||||
const initialState: ExecClientState = {
|
||||
selectedClient: 'Erigon',
|
||||
}
|
||||
|
||||
const execClientSlice = createSlice({
|
||||
name: 'execClient',
|
||||
const validatorSetup = createSlice({
|
||||
name: 'validatorSetup',
|
||||
initialState,
|
||||
reducers: {
|
||||
selectClient: (state: ExecClientState, action: PayloadAction<string>) => {
|
||||
state.selectedClient = action.payload
|
||||
setIsValidatorSet: (state: ValidatorSetupState, action: PayloadAction<boolean>) => {
|
||||
state.isValidatorSet = action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { selectClient } = execClientSlice.actions
|
||||
export const { setIsValidatorSet } = validatorSetup.actions
|
||||
|
||||
export default execClientSlice.reducer
|
||||
export default validatorSetup.reducer
|
||||
|
|
|
@ -6,18 +6,16 @@ interface DeviceHealthState {
|
|||
cpuLoad: number[]
|
||||
memory: number[]
|
||||
maxMemory: number
|
||||
uploadRate: number[]
|
||||
downloadRate: number[]
|
||||
latency: number[]
|
||||
}
|
||||
|
||||
const initialState: DeviceHealthState = {
|
||||
storage: 44,
|
||||
maxStorage: 100,
|
||||
cpuLoad: [12, 123, 4, 90],
|
||||
memory: [25, 31, 5, 14, 20, 81],
|
||||
cpuLoad: [25, 31, 5, 14, 20, 81, 50, 34, 12, 123, 4, 90, 56, 35, 90],
|
||||
memory: [15, 31, 5, 14, 20, 81, 50, 34, 12, 123, 4, 90, 56, 35, 90],
|
||||
maxMemory: 120,
|
||||
uploadRate: [1, 4, 25, 65],
|
||||
downloadRate: [20, 3, 50, 30],
|
||||
latency: [55, 31, 5, 14, 20, 81, 50, 34, 12, 50, 4, 90, 56, 35, 59],
|
||||
}
|
||||
|
||||
const deviceHealthSlice = createSlice({
|
||||
|
@ -41,12 +39,8 @@ const deviceHealthSlice = createSlice({
|
|||
state.memory = action.payload.memory
|
||||
state.maxMemory = action.payload.maxMemory
|
||||
},
|
||||
setNetworkHealth: (
|
||||
state: DeviceHealthState,
|
||||
action: PayloadAction<{ uploadRate: number[]; downloadRate: number[] }>,
|
||||
) => {
|
||||
state.uploadRate = action.payload.uploadRate
|
||||
state.downloadRate = action.payload.downloadRate
|
||||
setNetworkHealth: (state: DeviceHealthState, action: PayloadAction<{ latency: number[] }>) => {
|
||||
state.latency = action.payload.latency
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
import themeReducer from './theme/slice'
|
||||
import deviceHealthReducer from './deviceHealthCheck/slice'
|
||||
import pinnedMessageReducer from './PinnedMessage/slice'
|
||||
import execClientReducer from './ValidatorOnboarding/ValidatorSetup/slice'
|
||||
import themeReducer from './theme/slice'
|
||||
import execClientReducer from './ValidatorOnboarding/ClientSetup/slice'
|
||||
import keyGenerationReducer from './ValidatorOnboarding/KeyGeneration/slice'
|
||||
import depositReducer from './ValidatorOnboarding/Deposit/slice'
|
||||
import leftSidebarReducer from './Sidebars/slice'
|
||||
import rightSidebarReducer from './RightSidebar/slice'
|
||||
import validatorOnboardingReducer from './ValidatorOnboarding/slice'
|
||||
import advisoriesReducer from './ValidatorOnboarding/Advisories/slice'
|
||||
import validatorSetupReducer from './ValidatorOnboarding/ValidatorSetup/slice'
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
|
@ -15,9 +19,12 @@ const store = configureStore({
|
|||
execClient: execClientReducer,
|
||||
theme: themeReducer,
|
||||
keyGeneration: keyGenerationReducer,
|
||||
deposit: depositReducer,
|
||||
leftSidebar: leftSidebarReducer,
|
||||
rightSidebar: rightSidebarReducer,
|
||||
validatorOnboarding: validatorOnboardingReducer,
|
||||
advisories: advisoriesReducer,
|
||||
validatorSetup: validatorSetupReducer,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -36,3 +36,15 @@ export const getMonthIndicesFromRange = (range: DateRange) => {
|
|||
|
||||
return [range.from.getMonth(), range.to.getMonth()]
|
||||
}
|
||||
|
||||
export const getFormattedValidatorAddress = (address: string) => {
|
||||
// zQ3asdf9d4Gs0 -> zQ3...9d4Gs0
|
||||
const start = address.slice(0, 3)
|
||||
const end = address.slice(-6)
|
||||
return `${start}...${end}`
|
||||
}
|
||||
|
||||
export const getFormattedWalletAddress = (address: string) => {
|
||||
// 0xb9dasdfc35 -> 0xb9d...c35
|
||||
return `${address.slice(0, 5)}...${address.slice(-3)}`
|
||||
}
|
||||
|
|