Merge pull request #43 from status-im/rd-hn-.create-DeviceHealthPage

feat: create device health page
This commit is contained in:
Hristo Nedelkov 2023-08-15 09:23:04 +03:00 committed by GitHub
commit 11c18a316f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 378 additions and 43 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -4,12 +4,17 @@ import { Provider as StatusProvider } from '@status-im/components'
import './App.css'
import config from '../tamagui.config'
import LandingPage from './components/LandingPage'
import DeviceHealthCheck from './pages/DeviceHealthCheck/DeviceHealthCheck'
const router = createBrowserRouter([
{
path: '/',
element: <LandingPage />,
},
{
path: '/device-health-check',
element: <DeviceHealthCheck />,
},
])
function App() {

View File

@ -0,0 +1,33 @@
import type { Meta, StoryObj } from '@storybook/react'
import DeviceCPULoad from './DeviceCPULoad'
const meta = {
title: 'Device Health/DeviceCPULoad',
component: DeviceCPULoad,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof DeviceCPULoad>
export default meta
type Story = StoryObj<typeof meta>
export const GoodStats: Story = {
args: {
load: [12, 123, 4,32,40],
},
}
export const BadStats: Story = {
args: {
load: [12, 34, 81, 90, 91],
},
}
export const EmptyStats: Story = {
args: {
load: [],
},
}

View File

@ -1,7 +1,7 @@
import StandartLineChart from './StandardLineChart'
import IconText from './IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import { Shadow } from '@status-im/components'
import { Shadow, Text } from '@status-im/components'
type DataPoint = {
x: number
@ -34,7 +34,16 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
const message = currentLoad < 80 ? 'Good' : 'Poor'
return (
<Shadow style={{ width: '284px', height: '136px', borderRadius: '16px' }}>
<Shadow
variant="$2"
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<YStack>
<XStack
justifyContent="space-between"
@ -57,10 +66,17 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
</XStack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
<IconText icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}>
<IconText
icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}
weight={'semibold'}
>
{message}
</IconText>
{/* <Text color={'#E95460'}>This is additional text</Text> */}
{message === 'Poor' && (
<Text size={13} color="#E95460">
{((currentLoad / 80) * 100).toFixed(0)}% Utilization
</Text>
)}
</XStack>
</YStack>
</Shadow>

View File

@ -0,0 +1,35 @@
import type { Meta, StoryObj } from '@storybook/react'
import DeviceMemoryHealth from './DeviceMemoryHealth'
const meta = {
title: 'Device Health/DeviceMemoryHealth',
component: DeviceMemoryHealth,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof DeviceMemoryHealth>
export default meta
type Story = StoryObj<typeof meta>
export const GoodStats: Story = {
args: {
currentMemory: [25, 31, 5, 14, 20],
maxMemory: 64,
},
}
export const BadStats: Story = {
args: {
currentMemory: [25, 31, 5, 14, 80],
maxMemory: 64,
},
}
export const EmptyStats: Story = {
args: {
currentMemory: [],
maxMemory:0,
},
}

View File

@ -2,7 +2,7 @@ import StandartLineChart from './StandardLineChart'
import IconText from './IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import { Shadow as ShadowBox } from '@status-im/components'
import { Shadow as ShadowBox, Text } from '@status-im/components'
type DataPoint = {
x: number
@ -16,11 +16,11 @@ type ChartData = {
maxValue?: number
}
type DeviceMemoryProps = {
type DeviceMemoryHealthProps = {
currentMemory: number[]
maxMemory?: number
maxMemory: number
}
const DeviceMemory = ({ currentMemory, maxMemory }: DeviceMemoryProps) => {
const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProps) => {
const chartData: ChartData[] = [
{
id: 'cpu',
@ -35,16 +35,25 @@ const DeviceMemory = ({ currentMemory, maxMemory }: DeviceMemoryProps) => {
const currentLoad =
chartData[0].data.length > 0 ? chartData[0].data[chartData[0].data.length - 1].y : 0
const message = currentLoad < 80 ? 'Good' : 'Poor'
const message = currentLoad < maxMemory ? 'Good' : 'Poor'
return (
<ShadowBox style={{ width: '284px', height: '136px' }}>
<ShadowBox
variant="$2"
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<YStack>
<XStack
justifyContent="space-between"
style={{
padding: '8px 16px',
position: 'relative', // Make XStack a positioning context
position: 'relative',
}}
>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
@ -64,11 +73,15 @@ const DeviceMemory = ({ currentMemory, maxMemory }: DeviceMemoryProps) => {
<IconText icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}>
{message}
</IconText>
{/* <Text color={'#E95460'}>This is additional text</Text> */}
{message === 'Poor' && (
<Text size={13} color="#E95460">
{((currentLoad / maxMemory || 0) * 100).toFixed(0)}% Utilization
</Text>
)}
</XStack>
</YStack>
</ShadowBox>
)
}
export default DeviceMemory
export default DeviceMemoryHealth

View File

@ -0,0 +1,36 @@
import type { Meta, StoryObj } from '@storybook/react'
import DeviceNetworkHealth from './DeviceNetworkHealth'
const meta = {
title: 'Device Health/DeviceNetworkHealth',
component: DeviceNetworkHealth,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof DeviceNetworkHealth>
export default meta
type Story = StoryObj<typeof meta>
export const GoodStats: Story = {
args: {
uploadRate: [1, 4, 23, 61, 34],
downloadRate: [20, 3, 40, 56, 32],
},
}
export const BadStats: Story = {
args: {
uploadRate: [1, 4, 23, 55],
downloadRate: [20, 3, 40, 56, 80],
},
}
export const NoStats: Story = {
args: {
uploadRate: [],
downloadRate: [],
},
}

View File

@ -1,7 +1,7 @@
import StandartLineChart from './StandardLineChart'
import IconText from './IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import { Shadow as ShadowBox } from '@status-im/components'
import { Shadow as ShadowBox, Text } from '@status-im/components'
type DataPoint = {
x: number
@ -40,10 +40,19 @@ const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthPr
const currentLoad =
chartData[0].data.length > 0 ? chartData[0].data[chartData[0].data.length - 1].y : 0
const message = currentLoad < 80 ? 'Good' : 'Poor'
const message = currentLoad > 60 ? 'Good' : 'Poor'
return (
<ShadowBox style={{ width: '284px', height: '136px' }}>
<ShadowBox
variant="$2"
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<YStack>
<XStack
justifyContent="space-between"
@ -69,7 +78,11 @@ const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthPr
<IconText icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}>
{message}
</IconText>
{/* <Text color={'#E95460'}>This is additional text</Text> */}
{message === 'Poor' && (
<Text size={13} color="#E95460">
{((currentLoad / 60) * 100).toFixed(0)}% Utilization
</Text>
)}
</XStack>
</YStack>
</ShadowBox>

View File

@ -0,0 +1,36 @@
import type { Meta, StoryObj } from '@storybook/react'
import DeviceStorageHealth from './DeviceStorageHealth'
const meta = {
title: 'Device Health/DeviceStorageHealth',
component: DeviceStorageHealth,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof DeviceStorageHealth>
export default meta
type Story = StoryObj<typeof meta>
export const GoodStats: Story = {
args: {
storage: 10,
maxStorage: 20,
},
}
export const BadStats: Story = {
args: {
storage: 20,
maxStorage: 20,
},
}
export const NoStats: Story = {
args: {
storage: 0,
maxStorage: 0,
},
}

View File

@ -1,22 +1,22 @@
import IconText from './IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import StandardGauge from './StandardGauge'
import { Shadow } from '@status-im/components'
import { Shadow, Text } from '@status-im/components'
interface DeviceStorageHealthProps {
storage: number
maxStorage: number
}
const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxStorage }) => {
const message = storage < maxStorage ? 'Good' : 'Poor'
const data = (storage: number, maxStorage: number) => {
const used = storage
const free = maxStorage - storage
const free = maxStorage - storage
const utilization = (storage / (maxStorage || 1)) * 100
const data = (free: number) => {
return [
{
id: 'storage',
label: 'Used',
value: used,
value: storage,
color: '#E95460',
},
{
@ -27,8 +27,18 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
},
]
}
return (
<Shadow style={{ width: '284px', height: '136px', borderRadius: '16px' }}>
<Shadow
variant="$2"
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<YStack>
<XStack
justifyContent="space-between"
@ -45,7 +55,7 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
height: '75px',
}}
>
<StandardGauge data={data(storage, maxStorage)} />
<StandardGauge data={data(free)} />
</div>
<YStack space={'$3'}>
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
@ -58,10 +68,17 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
</XStack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
<IconText icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}>
<IconText
icon={message === 'Good' ? '/icons/check-circle.png' : '/icons/alert.png'}
weight={'semibold'}
>
{message}
</IconText>
{/* <Text color={'#E95460'}>This is additional text</Text> */}
{message === 'Poor' && (
<Text size={13} color="#E95460">
{utilization.toFixed(0)}% Utilization
</Text>
)}
</XStack>
</YStack>
</Shadow>

View File

@ -30,7 +30,7 @@ const HealthInfoSection = (props: HealthInfoSectionProps) => {
const networkLatencyPercentage = networkLatency > 100 ? 100 : 0
return (
<YStack space={'$2'}>
<YStack space={'$3'}>
<StatusIconText
percentage={usedStoragePercentage}
threshold={80}

View File

@ -5,9 +5,10 @@ import { Text } from '@status-im/components'
type IconTextProps = {
icon: string
children: string
weight?: 'regular' | 'medium' | 'semibold'
}
const IconText = ({ icon, children }: IconTextProps) => {
const IconText = ({ icon, children, weight }: IconTextProps) => {
return (
<XStack
style={{
@ -16,7 +17,7 @@ const IconText = ({ icon, children }: IconTextProps) => {
space={'$2'}
>
<Icon src={icon} />
<Text size={11} color={'#000000'} weight={"medium"} >
<Text size={13} color={'#000000'} weight={weight}>
{children}
</Text>
</XStack>

View File

@ -5,7 +5,10 @@ import QuickStartBar from './QuickStartBar'
function LandingPage() {
return (
<>
<LayoutComponent content={<Content />} />
<LayoutComponent
content={<Content />}
rightImageSrc="src/assets/bg-img/landing-page-bg.png"
/>
<QuickStartBar />
</>
)

View File

@ -4,6 +4,7 @@ import './layout.css'
type LeftProps = {
breadcrumbBar?: ReactNode
content: ReactNode
rightImageSrc?: string
}
function LayoutComponent(props: LeftProps) {
return (
@ -12,16 +13,16 @@ function LayoutComponent(props: LeftProps) {
{props.breadcrumbBar}
<div className="container">{props.content}</div>
</section>
<LayoutRight />
<LayoutRight rightImageSrc={props.rightImageSrc} />
</div>
)
}
function LayoutRight() {
function LayoutRight({ rightImageSrc }: { rightImageSrc?: string }) {
return (
<section className="layout-right">
<div className="image-container">
<img src="src/assets/bg-img/day-night-bg.png" alt="" />
<img src={rightImageSrc} alt="background" />
</div>
</section>
)

View File

@ -0,0 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react'
import NimbusLogo from './NimbusLogo'
const meta = {
title: 'General/NimbusLogo',
component: NimbusLogo,
tags: ['autodocs'],
} satisfies Meta<typeof NimbusLogo>
export default meta
type Story = StoryObj<typeof meta>
export const ExampleNimbusLogo: Story = {}

View File

@ -0,0 +1,29 @@
import type { Meta, StoryObj } from '@storybook/react'
import Titles from './Titles'
const meta = {
title: 'General/Titles',
component: Titles,
tags: ['autodocs'],
} satisfies Meta<typeof Titles>
export default meta
type Story = StoryObj<typeof meta>
export const WelcomeTitles: Story = {
args: {
title: 'Welcome, John. This is your complete access to a truly decentralized Web 3.0',
subtitle:
'Status Nodes allows you to finally take control and ownership of the services you wish to run in a completely trustless and decentralized manner.',
isAdvancedSettings: false,
},
}
export const DeviceHealthCheckTitles: Story = {
args: {
title: 'Device Health Check',
subtitle: 'Configure your device to start Staking on Nimbus',
isAdvancedSettings: true,
},
}

View File

@ -1,25 +1,24 @@
import { XStack, YStack } from 'tamagui'
import { Button, Text } from '@status-im/components'
import Icon from './Icon'
import Title from './Title'
type TitlesProps = {
title: string
subtitle: string
button?: boolean
isAdvancedSettings?: boolean
}
const Titles = ({ title, subtitle, button }: TitlesProps) => {
const Titles = ({ title, subtitle, isAdvancedSettings }: TitlesProps) => {
return (
<YStack>
<XStack justifyContent="space-between">
<Text size={27} weight="semibold">
{title}
</Text>
{button ? (
<Button variant="outline" size={32} icon={<Icon src={'/icons/reveal.png'} />}>
<YStack style={{ width: '100%' }}>
<XStack style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Title color={'#09101C'}>{title}</Title>
{isAdvancedSettings && (
<Button size={32} variant="outline" icon={<Icon src={'/icons/reveal.png'} />}>
Advanced Settings
</Button>
) : null}
)}
</XStack>
<Text size={15} weight="regular">
{subtitle}

View File

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

View File

@ -0,0 +1,68 @@
import { Stack, XStack, YStack } from 'tamagui'
import LayoutComponent from '../../components/LayoutComponent'
import NimbusLogo from '../../components/NimbusLogo'
import Titles from '../../components/Titles'
import DeviceStorageHealth from '../../components/DeviceStorageHealth'
import DeviceCPULoad from '../../components/DeviceCPULoad'
import HealthInfoSection from '../../components/HealthInfoSection'
import { Button, InformationBox } from '@status-im/components'
import Icon from '../../components/Icon'
import DeviceMemory from '../../components/DeviceMemoryHealth'
import DeviceNetworkHealth from '../../components/DeviceNetworkHealth'
const DeviceHealthCheck = () => {
return (
<LayoutComponent
content={<DeviceHealthCheckContent />}
rightImageSrc="/background-images/eye-background.png"
/>
)
}
export default DeviceHealthCheck
const DeviceHealthCheckContent = () => {
return (
<div className="container-inner landing-page">
<YStack
space={'$4'}
style={{
justifyContent: 'end',
alignItems: 'start',
marginBottom: '2rem',
maxWidth: '100%',
}}
>
<NimbusLogo />
<Titles
title="Device Health Check"
subtitle="Configure your device to start Staking on Nimbus"
isAdvancedSettings={true}
/>
<XStack space={'$4'}>
<DeviceStorageHealth storage={44} maxStorage={30} />
<DeviceCPULoad load={[12, 123, 4, 90]} />
</XStack>
<XStack space={'$4'}>
<DeviceMemory currentMemory={[25, 31, 5, 14, 20, 81]} maxMemory={38} />
<DeviceNetworkHealth uploadRate={[1, 4, 23, 55]} downloadRate={[20, 3, 40, 56]} />
</XStack>
<HealthInfoSection
usedStorage={120}
maxStorage={160}
usedRamMemory={8}
maxRamMemory={16}
cpuClockRate={2.5}
networkLatency={75}
/>
<InformationBox
icon={<Icon src="/icons/close.png" width={11} height={11} />}
message="The information provided in the Nodes Health Check is meant to utilized as a guide to guage the readiness of your device, however please do your own due diligence prior to commiting any funds. Read our Health Check Disclosure for more information."
/>
<Stack style={{ marginTop: '1rem' }}>
<Button>Continue</Button>
</Stack>
</YStack>
</div>
)
}