Merge branch 'main' into rd.pair-device-responsive

This commit is contained in:
Radoslav Dimchev 2023-12-14 17:27:14 +02:00 committed by GitHub
commit 201a25a01a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1065 additions and 85 deletions

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ dist-ssr
# vercel
/.vercel
.vercel

View File

@ -16,11 +16,11 @@ import PairDevice from './pages/PairDevice/PairDevice'
import PinnedNotification from './components/General/PinnedNottification'
import CreateLocalNodePage from './pages/CreateLocalNodePage/CreateLocalNodePage'
import ValidatorOnboarding from './pages/ValidatorOnboarding/ValidatorOnboarding'
import { ethereumRopsten, wcV2InitOptions, apiKey } from './constants'
import Dashboard from './pages/Dashboard/Dashboard'
import ConnectExistingInstance from './pages/ConnectExistingInstance/ConnectExistingInstance'
import './App.css'
import ValidatorManagement from './pages/ValidatorManagement/ValidatorManagement'
import { ethereumRopsten, wcV2InitOptions, apiKey } from './constants'
import './App.css'
const injected = injectedModule()
const walletConnect = walletConnectModule(wcV2InitOptions)

View File

@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react'
import AddCardsContainer from './AddCardsContainer'
const meta = {
title: 'Dashboard/AddCardsContainer',
title: 'General/AddCardsContainer',
component: AddCardsContainer,
parameters: {
layout: 'centered',
@ -15,5 +15,13 @@ export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
args: {
cardsAmount: 2,
},
}
export const WithoutCards: Story = {
args: {
cardsAmount: 0,
},
}

View File

@ -4,14 +4,19 @@ import AddCard from './AddCard'
import DashboardCardWrapper from '../../../pages/Dashboard/DashboardCardWrapper'
import { getHeightPercentages } from '../../../utilities'
const AddCardsContainer = () => {
const cards = 2
type AddCardsContainerProps = {
cardsAmount: number
}
const AddCardsContainer = ({ cardsAmount }: AddCardsContainerProps) => {
return (
<DashboardCardWrapper padding="0" minWidth="50px">
<YStack height={'100%'}>
{Array.from({ length: cards }).map((_, index) => (
<AddCard key={index} style={{ padding: '56px', height: getHeightPercentages(cards) }} />
{Array.from({ length: cardsAmount }).map((_, index) => (
<AddCard
key={index}
style={{ padding: '40px', height: getHeightPercentages(cardsAmount) }}
/>
))}
</YStack>
</DashboardCardWrapper>

View File

@ -2,44 +2,23 @@ import { Tabs } from '@status-im/components'
import { Stack } from 'tamagui'
import ValidatorsList from './ValidatorsList'
import { useMemo } from 'react'
import { VALIDATOR_TABS_RIGHT_SIDEBAR } from '../../../../constants'
const ValidatorsTabs = () => {
const VALIDATOR_TABS = useMemo(
() => [
{
label: 'Active',
value: 'active',
children: <ValidatorsList />,
},
{
label: 'Pending',
value: 'pending',
children: <ValidatorsList />,
},
{
label: 'Inactive',
value: 'inactive',
children: <ValidatorsList />,
},
],
[],
)
return (
<Tabs defaultValue="active">
<Tabs defaultValue={VALIDATOR_TABS_RIGHT_SIDEBAR[0]}>
<Stack style={{ cursor: 'pointer', width: 'fit-content' }}>
<Tabs.List size={32}>
{VALIDATOR_TABS.map(tab => (
<Tabs.Trigger key={tab.value} type="default" value={tab.value}>
{tab.label}
{VALIDATOR_TABS_RIGHT_SIDEBAR.map(tab => (
<Tabs.Trigger key={tab} type="default" value={tab}>
{tab}
</Tabs.Trigger>
))}
</Tabs.List>
</Stack>
{VALIDATOR_TABS.map(tab => (
<Tabs.Content key={tab.value} value={tab.value} style={{ marginTop: '8px' }}>
{tab.children}
{VALIDATOR_TABS_RIGHT_SIDEBAR.map(tab => (
<Tabs.Content key={tab} value={tab} style={{ marginTop: '8px' }}>
<ValidatorsList />
</Tabs.Content>
))}
</Tabs>

View File

@ -1,15 +1,15 @@
import type { Meta, StoryObj } from '@storybook/react'
import KeyGenerationSyncCard from './KeyGenerationSyncCard'
import SyncStatusCard from './SyncStatusCard'
const meta = {
title: 'ValidatorOnboarding/KeyGenerationSyncCard',
component: KeyGenerationSyncCard,
title: 'General/SyncStatusCard',
component: SyncStatusCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof KeyGenerationSyncCard>
} satisfies Meta<typeof SyncStatusCard>
export default meta
type Story = StoryObj<typeof meta>

View File

@ -2,18 +2,18 @@ import { Stack, XStack, YStack } from 'tamagui'
import { InfoBadgeIcon } from '@status-im/icons'
import { Text } from '@status-im/components'
import StandardGauge from '../../../../components/Charts/StandardGauge'
import BorderBox from '../../../../components/General/BorderBox'
import { formatNumbersWithComa } from '../../../../utilities'
import StandardGauge from '../Charts/StandardGauge'
import BorderBox from './BorderBox'
import { formatNumbersWithComa } from '../../utilities'
type KeyGenerationSyncCardProps = {
type SyncStatusCardProps = {
synced: number
total: number
title: string
color: string
}
const KeyGenerationSyncCard = ({ synced, total, title, color }: KeyGenerationSyncCardProps) => {
const SyncStatusCard = ({ synced, total, title, color }: SyncStatusCardProps) => {
return (
<BorderBox style={{ borderRadius: '10.1px', borderWidth: '0.5px' }}>
<XStack space={'$2'} alignItems="center">
@ -54,4 +54,4 @@ const KeyGenerationSyncCard = ({ synced, total, title, color }: KeyGenerationSyn
)
}
export default KeyGenerationSyncCard
export default SyncStatusCard

View File

@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react'
import TitleLogo from './TitleLogo'
const meta = {
title: 'Dashboard/TitleLogo',
title: 'General/TitleLogo',
component: TitleLogo,
parameters: {
layout: 'centered',
@ -15,5 +15,11 @@ export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
subtitle: 'Node Management Dashboard',
},
}
export const WithoutSubtitle: Story = {
args: {},
}

View File

@ -1,7 +1,11 @@
import { Avatar, Text } from '@status-im/components'
import { Stack, XStack, YStack } from 'tamagui'
const TitleLogo = () => {
type TitleLogoProps = {
subtitle?: string
}
const TitleLogo = ({ subtitle }: TitleLogoProps) => {
return (
<XStack space={'$2'}>
<Stack style={{ marginTop: '3px' }}>
@ -18,7 +22,7 @@ const TitleLogo = () => {
Nimbus
</Text>
<Text size={19} color="#647084">
Node Management Dashboard
{subtitle}
</Text>
</YStack>
</XStack>

View File

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import ValidatorProfile from './ValidatorProfile'
const meta = {
title: 'General/ValidatorProfile',
component: ValidatorProfile,
tags: ['autodocs'],
} satisfies Meta<typeof ValidatorProfile>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
number: 1,
address: 'zQ3asdf9d4Gs0',
},
}

View File

@ -0,0 +1,33 @@
import { Avatar, Text } from '@status-im/components'
import { XStack, YStack } from 'tamagui'
import { getFormattedValidatorAddress } from '../../utilities'
type ValidatorProfileProps = {
number: number
address: string
}
const ValidatorProfile = ({ number, address }: ValidatorProfileProps) => {
return (
<XStack space={'$2'}>
<Avatar
type="user"
size={32}
src="/icons/validator-request.svg"
name={number.toString()}
indicator="online"
/>
<YStack>
<Text size={15} weight={'semibold'}>
Validator {number}
</Text>
<Text size={13} color="#647084">
{getFormattedValidatorAddress(address)}
</Text>
</YStack>
</XStack>
)
}
export default ValidatorProfile

View File

@ -45,7 +45,6 @@ export const DEPOSIT_SUBTITLE = 'Connect you Wallet to stake required ETH for ne
export const CLIENT_SETUP_SUBTITLE = 'How many Validators would you like to run?'
// Dashboard
export const years = [
'JAN',
'FEB',
@ -60,3 +59,57 @@ export const years = [
'NOV',
'DEC',
]
export const VALIDATOR_TABS_RIGHT_SIDEBAR = ['Active', 'Pending', 'Inactive']
// Validator Management
export const VALIDATOR_TABS_MANAGEMENT = [
'Active',
'Pending',
'Inactive',
'Exited',
'Withdraw',
'All',
]
export const VALIDATORS_DATA = [
{
number: 1,
address: 'zQ3asdf9d4Gs0',
balance: 32.0786,
income: 0.0786,
proposals: '1/102',
attestations: '1/102',
effectiveness: 98,
status: 'Active',
},
{
number: 1,
address: 'zQ3asdf9d4Gs0',
balance: 32.0786,
income: 0.0786,
proposals: '1/102',
attestations: '1/102',
effectiveness: 98,
status: 'Active',
},
{
number: 1,
address: 'zQ3asdf9d4Gs0',
balance: 32.0786,
income: 0.0786,
proposals: '1/102',
attestations: '1/102',
effectiveness: 98,
status: 'Active',
},
{
number: 1,
address: 'zQ3asdf9d4Gs0',
balance: 32.0786,
income: 0.0786,
proposals: '1/102',
attestations: '1/102',
effectiveness: 98,
status: 'Active',
},
]

View File

@ -62,7 +62,6 @@
body {
margin: 0;
display: flex;
min-width: 320px;
min-height: 100vh;
}
h1,
@ -103,11 +102,13 @@ ul li {
}
.transparent-scrollbar::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.transparent-scrollbar::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 10px;
height: 8px;
}
.transparent-scrollbar::-webkit-scrollbar-thumb:hover {
@ -136,4 +137,10 @@ ul li {
flex-direction: column;
gap: 12px;
}
}
@media (max-width: 900px) {
.right-sidebar-wrapper {
display: none;
}
}

View File

@ -1,4 +1,5 @@
import { Stack, YStack } from 'tamagui'
import { Stack, YStack, XStack } from 'tamagui'
import BasicInfoCards from './BasicInfoCards/BasicInfoCards'
import AddCardsContainer from '../../components/General/AddCards/AddCardsContainer'
import BalanceChartCard from './BalanceChartCard/BalanceChartCard'
@ -6,15 +7,16 @@ import CPUCard from './CPULoad/CPUCard'
import ConsensusUptimeCard from './ConsensusUptime/ConsensusUptimeCard'
import ExecutionUptime from './ExecutionUptime/ExecutionUptime'
import DeviceUptime from './DeviceUptime/DeviceUptime'
import TitleLogo from './TitleLogo'
import TitleLogo from '../../components/General/TitleLogo'
import StorageCard from './StorageCard/StorageCard'
import NetworkCard from './NetworkCard/NetworkCard'
import SyncStatusCard from './SyncStatusCards/SyncStatusCards'
import SyncStatusCards from './SyncStatusCards/SyncStatusCards'
import MemoryCard from './MemoryCard/MemoryCard'
import { XStack } from 'tamagui'
type DashboardContentProps = {
windowWidth: number
}
const DashboardContent = ({ windowWidth }: DashboardContentProps) => {
return (
<YStack
@ -30,7 +32,7 @@ const DashboardContent = ({ windowWidth }: DashboardContentProps) => {
}}
className={'transparent-scrollbar'}
>
<TitleLogo />
<TitleLogo subtitle="Node Management Dashboard" />
<Stack
style={{
display: 'grid',
@ -39,8 +41,8 @@ const DashboardContent = ({ windowWidth }: DashboardContentProps) => {
gridAutoFlow: 'row',
}}
>
<SyncStatusCard />
<AddCardsContainer />
<SyncStatusCards />
<AddCardsContainer cardsAmount={2} />
{windowWidth < 1375 ? (
<Stack style={{ gridColumn: '1 / span 2' }} width={'101%'}>
<BalanceChartCard />

View File

@ -5,7 +5,7 @@ import DashboardCardWrapper from '../DashboardCardWrapper'
import ExecutionClientCard from './ExecutionClientCard'
import ConsensusCard from './ConsensusClientCard'
const SyncStatusCard = () => {
const SyncStatusCards = () => {
return (
<DashboardCardWrapper padding="0" minWidth="50px">
<YStack space={'$2'}>
@ -24,4 +24,4 @@ const SyncStatusCard = () => {
)
}
export default SyncStatusCard
export default SyncStatusCards

View File

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import ManagementCard from './ManagementCard'
const meta = {
title: 'ValidatorManagement/ManagementCard',
component: ManagementCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ManagementCard>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}

View File

@ -0,0 +1,31 @@
import { Text } from '@status-im/components'
import { Separator, Stack, YStack } from 'tamagui'
const ManagementCard = () => {
return (
<YStack
space={'$3'}
style={{ border: '1px solid #F0F2F5', borderRadius: '16px', minWidth: '33%' }}
>
<Stack style={{ padding: '12px 16px' }}>
<Text size={15} weight={'semibold'}>
Validators
</Text>
</Stack>
<Separator borderColor={'#F0F2F5'} />
<Stack style={{ padding: '12px 16px' }}>
<Text size={15} weight={'semibold'} color="#647084">
Total Balance
</Text>
</Stack>
<Separator borderColor={'#F0F2F5'} />
<Stack style={{ padding: '12px 16px', marginBottom: '16px' }}>
<Text size={15} weight={'semibold'} color="#647084">
Total Income
</Text>
</Stack>
</YStack>
)
}
export default ManagementCard

View File

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import ManagementHeader from './ManagementHeader'
const meta = {
title: 'ValidatorManagement/ManagementHeader',
component: ManagementHeader,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ManagementHeader>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}

View File

@ -0,0 +1,39 @@
import { XStack } from 'tamagui'
import TitleLogo from '../../components/General/TitleLogo'
import SyncStatusCard from '../../components/General/SyncStatusCard'
const ManagementHeader = () => {
return (
<XStack
style={{
width: '100%',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: '16px',
}}
>
<TitleLogo subtitle="Validator Management" />
<XStack space={'$2'}>
<div className="sync-status-card-container-first">
<SyncStatusCard
synced={123.524}
total={172.503}
title="Execution Sync Status"
color="#2a4af5"
/>
</div>
<div className="sync-status-card-container-second">
<SyncStatusCard
synced={123.524}
total={172.503}
title="Consensus Sync Status"
color="#ff6161"
/>
</div>
</XStack>
</XStack>
)
}
export default ManagementHeader

View File

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import DropdownFilter from './DropdownFilter'
const meta = {
title: 'ValidatorManagement/DropdownFilter',
component: DropdownFilter,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof DropdownFilter>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}

View File

@ -0,0 +1,37 @@
import { DropdownMenu } from '@status-im/components'
import { SortIcon } from '@status-im/icons'
import { Stack } from 'tamagui'
const DropdownFilter = () => {
return (
<DropdownMenu>
<Stack style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>
<SortIcon
size={20}
color="#647084"
style={{
border: '1px solid #DCE0E5',
borderRadius: '10px',
padding: '8px',
cursor: 'pointer',
}}
/>
<Stack
style={{
position: 'absolute',
right: -2,
top: -1.5,
width: '9px',
height: '9px',
borderRadius: '50%',
backgroundColor: '#1992D7',
border: '1.5px solid #fff',
}}
/>
</Stack>
<DropdownMenu.Content sideOffset={5} position="absolute" zIndex={999} />
</DropdownMenu>
)
}
export default DropdownFilter

View File

@ -0,0 +1,18 @@
table {
width: 100%;
border-spacing: 0;
margin: 20px 0;
font-size: 14px;
border: 1px solid #e7eaee;
border-radius: 16px;
}
th {
border-bottom: 1px solid #e7eaee;
}
th,
td {
padding: 9px 19px;
text-align: center;
}

View File

@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react'
import { useState } from 'react'
import ManagementTable from './ManagementTable'
import { VALIDATOR_TABS_MANAGEMENT } from '../../../constants'
const meta = {
title: 'ValidatorManagement/ManagementTable',
component: ManagementTable,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ManagementTable>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = () => {
const [searchValue, setSearchValue] = useState('')
const changeSearchValue = (os: string) => {
setSearchValue(os)
}
return (
<ManagementTable
tab={VALIDATOR_TABS_MANAGEMENT[0]}
searchValue={searchValue}
changeSearchValue={changeSearchValue}
/>
)
}
Default.args = {
tab: VALIDATOR_TABS_MANAGEMENT[0],
searchValue: '',
changeSearchValue: () => {},
}

View File

@ -0,0 +1,92 @@
import { useEffect, useMemo, useState } from 'react'
import { YStack, XStack } from 'tamagui'
import { VALIDATORS_DATA, VALIDATOR_TABS_MANAGEMENT } from '../../../constants'
import SearchManagement from './SearchManagement'
import DropdownFilter from './DropdownFilter'
import ManagementTableHeader from './ManagementTableHeader'
import ManagementTableBody from './ManagementTableBody'
import './ManagementTable.css'
type ManagementTableProps = {
tab: string
searchValue: string
changeSearchValue: (value: string) => void
}
export type Validator = {
number: number
address: string
balance: number
income: number
proposals: string
attestations: string
effectiveness: number
status: string
}
const isValidStatus = (validatorStatus: string, tabStatus: string) => {
if (
validatorStatus === tabStatus ||
tabStatus === VALIDATOR_TABS_MANAGEMENT[VALIDATOR_TABS_MANAGEMENT.length - 1]
) {
return true
}
return false
}
const isValidNumberOrAddress = (
validatorNumber: number,
validatorAddress: string,
searchValue: string,
) => {
if (validatorNumber.toString().includes(searchValue) || validatorAddress.includes(searchValue)) {
return true
}
return false
}
const ManagementTable = ({ tab, searchValue, changeSearchValue }: ManagementTableProps) => {
const [validators, setValidators] = useState<Validator[]>([])
const [isAllSelected, setIsAllSelected] = useState(false)
useEffect(() => {
setValidators(VALIDATORS_DATA)
}, [])
useEffect(() => {
setIsAllSelected(false)
}, [validators, tab, searchValue])
const filteredValidators = useMemo(() => {
return validators
.filter(validator => isValidStatus(validator.status, tab))
.filter(validator => isValidNumberOrAddress(validator.number, validator.address, searchValue))
}, [validators, tab, searchValue])
const handleSelectAll = () => {
setIsAllSelected(state => !state)
}
return (
<YStack>
<XStack space={'$3'} justifyContent="space-between" alignItems="center">
<SearchManagement searchValue={searchValue} changeSearchValue={changeSearchValue} />
<DropdownFilter />
</XStack>
<table>
<ManagementTableHeader
validatorsAmount={filteredValidators.length}
isAllSelected={isAllSelected}
handleSelectAll={handleSelectAll}
/>
<ManagementTableBody
filteredValidators={filteredValidators}
isAllSelected={isAllSelected}
/>
</table>
</YStack>
)
}
export default ManagementTable

View File

@ -0,0 +1,30 @@
import type { Meta, StoryObj } from '@storybook/react'
import ManagementTableBody from './ManagementTableBody'
import { VALIDATORS_DATA } from '../../../constants'
const meta = {
title: 'ValidatorManagement/ManagementTableBody',
component: ManagementTableBody,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ManagementTableBody>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
filteredValidators: VALIDATORS_DATA,
isAllSelected: false,
},
}
export const AllSelected: Story = {
args: {
filteredValidators: VALIDATORS_DATA,
isAllSelected: true,
},
}

View File

@ -0,0 +1,34 @@
import { Text } from '@status-im/components'
import { Validator } from './ManagementTable'
import ManagementTableRow from './ManagementTableRow'
type ManagementTableBodyProps = {
filteredValidators: Validator[]
isAllSelected: boolean
}
const ManagementTableBody = ({ filteredValidators, isAllSelected }: ManagementTableBodyProps) => {
return (
<tbody>
{filteredValidators.map(validator => (
<ManagementTableRow
key={validator.address}
validator={validator}
isAllSelected={isAllSelected}
/>
))}
{filteredValidators.length === 0 && (
<tr>
<td colSpan={11}>
<Text size={15} color={'#647084'} weight={'semibold'}>
No validators
</Text>
</td>
</tr>
)}
</tbody>
)
}
export default ManagementTableBody

View File

@ -0,0 +1,38 @@
import type { Meta, StoryObj } from '@storybook/react'
import { useState } from 'react'
import ManagementTableHeader from './ManagementTableHeader'
const meta = {
title: 'ValidatorManagement/ManagementTableHeader',
component: ManagementTableHeader,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ManagementTableHeader>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = () => {
const [isAllSelected, setIsAllSelected] = useState(false)
const handleSelectAll = () => {
setIsAllSelected(state => !state)
}
return (
<ManagementTableHeader
validatorsAmount={4}
isAllSelected={isAllSelected}
handleSelectAll={handleSelectAll}
/>
)
}
Default.args = {
isAllSelected: false,
validatorsAmount: 4,
handleSelectAll: () => {},
}

View File

@ -0,0 +1,66 @@
import { Checkbox, Text } from '@status-im/components'
type ManagementTableHeaderProps = {
validatorsAmount: number
isAllSelected: boolean
handleSelectAll: () => void
}
const ManagementTableHeader = ({
validatorsAmount,
isAllSelected,
handleSelectAll,
}: ManagementTableHeaderProps) => {
return (
<thead>
<tr>
<th>
<Checkbox
id="table"
variant="outline"
selected={isAllSelected}
onCheckedChange={handleSelectAll}
/>
</th>
<th>
<Text size={15} color={'#647084'}>
{validatorsAmount} Validators
</Text>
</th>
<th>
<Text size={15} color={'#647084'}>
Balance
</Text>
</th>
<th>
<Text size={15} color={'#647084'}>
Income
</Text>
</th>
<th>
<Text size={15} color={'#647084'}>
Proposals
</Text>
</th>
<th>
<Text size={15} color={'#647084'}>
Attestations
</Text>
</th>
<th>
<Text size={15} color={'#647084'}>
Effectiveness
</Text>
</th>
<th>
<Text size={15} color={'#647084'}>
Status
</Text>
</th>
<th />
</tr>
</thead>
)
}
export default ManagementTableHeader

View File

@ -0,0 +1,23 @@
import type { Meta, StoryObj } from '@storybook/react'
import ManagementTableRow from './ManagementTableRow'
import { VALIDATORS_DATA } from '../../../constants'
const meta = {
title: 'ValidatorManagement/ManagementTableRow',
component: ManagementTableRow,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ManagementTableRow>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
validator: VALIDATORS_DATA[0],
isAllSelected: false,
},
}

View File

@ -0,0 +1,74 @@
import { useEffect, useState } from 'react'
import { Checkbox, Text } from '@status-im/components'
import { OptionsIcon } from '@status-im/icons'
import ValidatorProfile from '../../../components/General/ValidatorProfile'
import { Validator } from './ManagementTable'
type ManagementTableRowProps = {
validator: Validator
isAllSelected: boolean
}
const ManagementTableRow = ({ validator, isAllSelected }: ManagementTableRowProps) => {
const [isSelected, setIsSelected] = useState(false)
useEffect(() => {
setIsSelected(isAllSelected)
}, [isAllSelected])
const handleChangeIsSelected = () => {
setIsSelected(state => !state)
}
return (
<tr>
<td>
<Checkbox
id={validator.address}
variant="outline"
selected={isSelected}
onCheckedChange={handleChangeIsSelected}
/>
</td>
<td>
<ValidatorProfile number={validator.number} address={validator.address} />
</td>
<td>
<Text size={15} color={'#647084'} weight={'semibold'}>
{validator.balance}
</Text>
</td>
<td>
<Text size={15} color={'#647084'} weight={'semibold'}>
{validator.income}
</Text>
</td>
<td>
<Text size={15} color={'#647084'}>
{validator.proposals}
</Text>
</td>
<td>
<Text size={15} color={'#647084'}>
{validator.attestations}
</Text>
</td>
<td>
<Text size={15} color={'#647084'}>
{validator.effectiveness}%
</Text>
</td>
<td>
<Text size={15} color={'#2F80ED'} weight={'semibold'}>
{validator.status}
</Text>
</td>
<td>
<OptionsIcon size={20} color="#647084" style={{ cursor: 'pointer' }} />
</td>
</tr>
)
}
export default ManagementTableRow

View File

@ -0,0 +1,26 @@
import { useState } from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import SearchManagement from './SearchManagement'
const meta = {
title: 'ValidatorManagement/SearchManagement',
component: SearchManagement,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof SearchManagement>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = (args: { searchValue: string }) => {
const [searchValue, setSearchValue] = useState(args.searchValue)
return <SearchManagement searchValue={searchValue} changeSearchValue={setSearchValue} />
}
Default.args = {
searchValue: '',
}

View File

@ -0,0 +1,24 @@
import { Input } from '@status-im/components'
import { SearchIcon } from '@status-im/icons'
type SearchManagementProps = {
searchValue: string
changeSearchValue: (value: string) => void
}
const SearchManagement = ({ searchValue, changeSearchValue }: SearchManagementProps) => {
return (
<div style={{ width: '100%' }}>
<Input
placeholder="Filter Validators"
value={searchValue}
onChangeText={changeSearchValue}
icon={<SearchIcon size={20} />}
onClear={() => changeSearchValue('')}
size={40}
/>
</div>
)
}
export default SearchManagement

View File

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import ManagementTabs from './ManagementTabs'
const meta = {
title: 'ValidatorManagement/ManagementTabs',
component: ManagementTabs,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ManagementTabs>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}

View File

@ -0,0 +1,43 @@
import { Tabs } from '@status-im/components'
import { Stack } from 'tamagui'
import { useState } from 'react'
import ManagementTable from './ManagementTable/ManagementTable'
import { VALIDATOR_TABS_MANAGEMENT } from '../../constants'
const ManagementTabs = () => {
const [searchValue, setSearchValue] = useState('')
const changeSearchValue = (value: string) => {
setSearchValue(value)
}
return (
<div style={{ width: '100%' }}>
<Tabs defaultValue={VALIDATOR_TABS_MANAGEMENT[0]}>
<div className="tabs transparent-scrollbar">
<Stack maxWidth={'120px'} style={{ cursor: 'pointer', margin: '8px 0' }}>
<Tabs.List size={32}>
{VALIDATOR_TABS_MANAGEMENT.map(tab => (
<Tabs.Trigger key={tab} type="default" value={tab}>
{tab}
</Tabs.Trigger>
))}
</Tabs.List>
</Stack>
</div>
{VALIDATOR_TABS_MANAGEMENT.map(tab => (
<Tabs.Content key={tab} value={tab} style={{ marginTop: '8px' }}>
<ManagementTable
tab={tab}
searchValue={searchValue}
changeSearchValue={changeSearchValue}
/>
</Tabs.Content>
))}
</Tabs>
</div>
)
}
export default ManagementTabs

View File

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import ValidatorManagement from './ValidatorManagement'
const meta = {
title: 'Pages/ValidatorManagement',
component: ValidatorManagement,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ValidatorManagement>
export default meta
type Story = StoryObj<typeof meta>
export const Page: Story = {
args: {},
}

View File

@ -1,7 +1,20 @@
import { YStack } from 'tamagui'
import { XStack } from 'tamagui'
import ValidatorManagementContent from './ValidatorManagementContent'
import LeftSidebar from '../../components/General/LeftSidebar/LeftSidebar'
import RightSidebar from '../../components/General/RightSideBar/RightSidebar'
import './validatorManagement.css'
const ValidatorManagement = () => {
return <YStack></YStack>
return (
<XStack style={{ height: '100vh' }}>
<LeftSidebar />
<ValidatorManagementContent />
<div className="right-sidebar-wrapper">
<RightSidebar />
</div>
</XStack>
)
}
export default ValidatorManagement

View File

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import ValidatorManagementContent from './ValidatorManagementContent'
const meta = {
title: 'ValidatorManagement/ValidatorManagementContent',
component: ValidatorManagementContent,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ValidatorManagementContent>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}

View File

@ -0,0 +1,44 @@
import { Text } from '@status-im/components'
import { YStack } from 'tamagui'
import ManagementTabs from './ManagementTabs'
import AddCardsContainer from '../../components/General/AddCards/AddCardsContainer'
import ManagementHeader from './ManagementHeader'
import ManagementCard from './ManagementCard'
const ValidatorManagementContent = () => {
return (
<YStack
space="$4"
alignItems="start"
px="24px"
style={{
flexGrow: '1',
overflowY: 'auto',
}}
className="transparent-scrollbar"
>
<ManagementHeader />
<div
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between',
width: '100%',
gap: '16px',
}}
className="cards"
>
<ManagementCard />
<ManagementCard />
<AddCardsContainer cardsAmount={2} />
</div>
<Text size={27} weight={'semibold'}>
Validators
</Text>
<ManagementTabs />
</YStack>
)
}
export default ValidatorManagementContent

View File

@ -0,0 +1,94 @@
@media (max-width: 1130px) {
.sync-status-card-container-first {
display: none;
}
}
@media (max-width: 900px) and (min-width: 810px) {
.sync-status-card-container-first {
display: block;
}
}
@media (max-width: 590px) {
.sync-status-card-container-second {
display: none;
}
}
@media (max-width: 600px) {
.tabs {
overflow-x: auto;
overflow-y: none;
}
}
@media (max-width: 600px) {
.cards {
flex-direction: column;
}
}
/* Hide Effectiveness */
@media (max-width: 1300px) {
th:nth-child(7),
td:nth-child(7) {
display: none;
}
}
/* Hide the Attestations */
@media (max-width: 1200px) {
th:nth-child(6),
td:nth-child(6) {
display: none;
}
}
/* Hide the Proposals */
@media (max-width: 1100px) {
th:nth-child(5),
td:nth-child(5) {
display: none;
}
}
/* Hide and show Proposals */
@media (max-width: 900px) and (min-width: 800px) {
th:nth-child(5),
td:nth-child(5) {
display: table-cell;
}
}
/* Hide the Income */
@media (max-width: 1000px) {
th:nth-child(4),
td:nth-child(4) {
display: none;
}
}
/* Hide and show Income */
@media (max-width: 900px) and (min-width: 700px) {
th:nth-child(4),
td:nth-child(4) {
display: table-cell;
}
}
/* Hide Status */
@media (max-width: 560px) {
th:nth-child(8),
td:nth-child(8) {
display: none;
}
}
/* Hide Balance */
@media (max-width: 475px) {
th:nth-child(3),
td:nth-child(3) {
display: none;
}
}

View File

@ -1,8 +1,8 @@
import { Avatar, DividerLine, Text } from '@status-im/components'
import { DividerLine, Text } from '@status-im/components'
import { XStack, YStack } from 'tamagui'
import { getFormattedValidatorAddress } from '../../../../utilities'
import TransactionStatus from './TransactionStatus'
import ValidatorProfile from '../../../../components/General/ValidatorProfile'
type ValidatorRequestProps = {
number: number
@ -17,23 +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' }}>
<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>
<ValidatorProfile number={number} address={'zQ3asdf9d4Gs0'} />
<Text size={13} color="#647084" weight={'semibold'}>
Keys Generated
</Text>

View File

@ -1,6 +1,6 @@
import { XStack } from 'tamagui'
import KeyGenerationSyncCard from './KeyGenerationSyncCard'
import SyncStatusCard from '../../../../components/General/SyncStatusCard'
import KeyGenerationTitle from '../KeyGenerationTitle'
const KeyGenerationHeader = () => {
@ -8,13 +8,13 @@ const KeyGenerationHeader = () => {
<XStack style={{ width: '100%', justifyContent: 'space-between' }}>
<KeyGenerationTitle />
<XStack space={'$2'}>
<KeyGenerationSyncCard
<SyncStatusCard
synced={123.524}
total={172.503}
title="Execution Sync Status"
color="#2a4af5"
/>
<KeyGenerationSyncCard
<SyncStatusCard
synced={123.524}
total={172.503}
title="Consensus Sync Status"