Merge branch 'Create-Dashboard' of github.com:nimbus-gui/nimbus-gui into Create-Dashboard

This commit is contained in:
Ivana Andersson 2023-09-26 13:43:44 +03:00
commit fb9682755c
97 changed files with 1289 additions and 1035 deletions

View File

@ -55,6 +55,8 @@ jobs:
${{ runner.os }}-yarn-v3
- name: Build
run: yarn build
- name: Check formatting
run: yarn format:check
- name: Pull Vercel configuration
run: yarn vercel pull --yes --token ${{ secrets.vercel_token }}
- name: Build Vercel bundle

View File

@ -1,27 +1,36 @@
# nimbus-gui
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
A GUI for managing your [Nimbus](https://nimbus.team/) nodes.
Currently, two official plugins are available:
## Deployed pages showing the project
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
We have a Storybook up at https://nimbus-gui.github.io/nimbus-gui/ which shows
the components of the project. We also have a deployed version of the GUI up at
https://nimbus-gui.vercel.app/ which shows the GUI as it currently looks in the
`main` branch of the
[`nimbus-gui/nimbus-gui`](https://github.com/nimbus-gui/nimbus-gui) repository.
## Expanding the ESLint configuration
## Development and running the project yourself
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
### Dependencies
- Configure the top-level `parserOptions` property like this:
Run `yarn` in the root directory of the project in order to install dependencies.
```js
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
### Running a development server
If you want to run a development server to see what the GUI looks like you can
run the following command:
```bash
yarn dev
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
This will start the server on port 5173 and you can open https://localhost:5173
in order to see the page.
### Running storybook locally
If you want to run the Storybook locally you can simply run `yarn storybook` in
the root of the project. This is useful if you want to contribute a component
and be sure that it renders as you expect it to, without testing it out on any
given page.

View File

@ -5,6 +5,20 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<link
rel="preload"
href="./Inter-font/Inter-Regular.ttf"
as="font"
type="font/ttf"
crossOrigin=""
/>
<link
rel="preload"
href="./Inter-font/Inter-Bold.ttf"
as="font"
type="font/ttf"
crossOrigin=""
/>
</head>
<body>
<script>window.global = window;</script>

View File

@ -10,7 +10,9 @@
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test-storybook": "test-storybook"
"test-storybook": "test-storybook",
"format:check": "yarn prettier --check src",
"format": "yarn prettier --write src"
},
"resolutions": {
"@tamagui/web": "1.36.4",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,3 @@
<svg width="74" height="60" viewBox="0 0 74 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame 31443">
<path id="Vector" d="M59.1019 45.0375V59.0747C51.3854 59.0747 47.4072 58.373 42.7293 52.0562C38.0514 45.7393 35.0293 45.0373 31.0346 45.0373V59.0747H17.001V45.0373H31.0346V31C38.2347 31 42.7293 31.9358 47.4072 38.0187C52.0851 44.1016 54.5517 45.0375 59.1019 45.0375V31H73.1355V45.0375H59.1019Z" fill="#DCE0E5"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" width="74" height="60" viewBox="0 0 74 60" fill="none">
<path d="M59.1019 45.0375V59.0747C51.3854 59.0747 47.4072 58.373 42.7293 52.0562C38.0514 45.7393 35.0293 45.0373 31.0346 45.0373V59.0747H17.001V45.0373H31.0346V31C38.2347 31 42.7293 31.9358 47.4072 38.0187C52.0851 44.1016 54.5517 45.0375 59.1019 45.0375V31H73.1355V45.0375H59.1019Z" fill="#DCE0E5"/>
</svg>

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1,3 +1,3 @@
#root {
width: 100%;
}
}

View File

@ -1,6 +1,6 @@
import StandartLineChart from './StandardLineChart'
import IconText from '../General/IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import { Separator, XStack, YStack } from 'tamagui'
import { Shadow, Text } from '@status-im/components'
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
@ -38,8 +38,8 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
<Shadow
variant="$2"
style={{
width: '284px',
height: '136px',
width: '50%',
minHeight: '135px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
@ -49,7 +49,7 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
<XStack
justifyContent="space-between"
style={{
padding: '8px 16px',
padding: '0.75rem 1rem',
position: 'relative',
}}
>
@ -57,16 +57,16 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
<StandartLineChart data={chartData} />
</div>
<YStack space={'$3'}>
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
<Text size={15} weight={'semibold'}>
CPU
</Paragraph>
<Paragraph color={'#09101C'} size={'$8'} fontWeight={'700'}>
</Text>
<Text size={27} weight={'semibold'}>
{currentLoad} GB
</Paragraph>
</Text>
</YStack>
</XStack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
<XStack space={'$4'} style={{ padding: '0.65rem 1rem' }}>
<IconText
icon={message === 'Good' ? <CheckCircleIcon size={16} /> : <IncorrectIcon size={16} />}
weight={'semibold'}
@ -74,7 +74,7 @@ const DeviceCPULoad: React.FC<DeviceCPULoadProps> = ({ load }) => {
{message}
</IconText>
{message === 'Poor' && (
<Text size={13} color="#E95460">
<Text size={13} color={'#E95460'} weight={'semibold'}>
{((currentLoad / 80) * 100).toFixed(0)}% Utilization
</Text>
)}

View File

@ -1,7 +1,7 @@
import StandartLineChart from './StandardLineChart'
import IconText from '../General/IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import { Separator, XStack, YStack } from 'tamagui'
import { Shadow as ShadowBox, Text } from '@status-im/components'
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
@ -42,8 +42,8 @@ const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProp
<ShadowBox
variant="$2"
style={{
width: '284px',
height: '136px',
width: '50%',
minHeight: '135px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
@ -53,7 +53,7 @@ const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProp
<XStack
justifyContent="space-between"
style={{
padding: '8px 16px',
padding: '0.75rem 1rem',
position: 'relative',
}}
>
@ -61,23 +61,24 @@ const DeviceMemoryHealth = ({ currentMemory, maxMemory }: DeviceMemoryHealthProp
<StandartLineChart data={chartData} />
</div>
<YStack space={'$3'}>
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
<Text size={15} weight={'semibold'}>
Memory
</Paragraph>
<Paragraph color={'#09101C'} size={'$8'} fontWeight={'700'}>
</Text>
<Text size={27} weight={'semibold'}>
{currentLoad} GB
</Paragraph>
</Text>
</YStack>
</XStack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
<XStack space={'$4'} style={{ padding: '0.65rem 1rem' }}>
<IconText
icon={message === 'Good' ? <CheckCircleIcon size={16} /> : <IncorrectIcon size={16} />}
weight={'semibold'}
>
{message}
</IconText>
{message === 'Poor' && (
<Text size={13} color="#E95460">
<Text size={13} color={'#E95460'} weight={'semibold'}>
{((currentLoad / maxMemory || 0) * 100).toFixed(0)}% Utilization
</Text>
)}

View File

@ -1,6 +1,6 @@
import StandartLineChart from './StandardLineChart'
import IconText from '../General/IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import { Separator, XStack, YStack } from 'tamagui'
import { Shadow as ShadowBox, Text } from '@status-im/components'
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
@ -47,8 +47,8 @@ const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthPr
<ShadowBox
variant="$2"
style={{
width: '284px',
height: '136px',
width: '50%',
minHeight: '135px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
@ -58,7 +58,7 @@ const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthPr
<XStack
justifyContent="space-between"
style={{
padding: '8px 16px',
padding: '0.75rem 1rem',
position: 'relative',
}}
>
@ -66,23 +66,24 @@ const DeviceNetworkHealth = ({ uploadRate, downloadRate }: DeviceNetworkHealthPr
<StandartLineChart data={chartData} />
</div>
<YStack space={'$3'}>
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
<Text size={15} weight={'semibold'}>
Network
</Paragraph>
<Paragraph color={'#09101C'} size={'$8'} fontWeight={'700'}>
</Text>
<Text size={27} weight={'semibold'}>
{currentLoad} GB
</Paragraph>
</Text>
</YStack>
</XStack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
<XStack space={'$4'} style={{ padding: '0.65rem 1rem' }}>
<IconText
icon={message === 'Good' ? <CheckCircleIcon size={16} /> : <IncorrectIcon size={16} />}
weight={'semibold'}
>
{message}
</IconText>
{message === 'Poor' && (
<Text size={13} color="#E95460">
<Text size={13} color={'#E95460'} weight={'semibold'}>
{((currentLoad / 60) * 100).toFixed(0)}% Utilization
</Text>
)}

View File

@ -1,5 +1,5 @@
import IconText from '../General/IconText'
import { Paragraph, Separator, XStack, YStack } from 'tamagui'
import { Separator, XStack, YStack } from 'tamagui'
import StandardGauge from './StandardGauge'
import { Shadow, Text } from '@status-im/components'
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
@ -33,8 +33,8 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
<Shadow
variant="$2"
style={{
width: '284px',
height: '136px',
width: '50%',
minHeight: '135px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
@ -44,31 +44,31 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
<XStack
justifyContent="space-between"
style={{
padding: '8px 16px',
padding: '0.75rem 1rem',
position: 'relative',
}}
>
<div
style={{
position: 'absolute',
right: '44px',
width: '75px',
height: '75px',
right: '33px',
width: '4.75rem',
height: '4.75rem',
}}
>
<StandardGauge data={data(free)} />
</div>
<YStack space={'$3'}>
<Paragraph color={'#09101C'} size={'$6'} fontWeight={'600'}>
<Text size={15} weight={'semibold'}>
Storage
</Paragraph>
<Paragraph color={'#09101C'} size={'$8'} fontWeight={'700'}>
</Text>
<Text size={27} weight={'semibold'}>
{storage} GB
</Paragraph>
</Text>
</YStack>
</XStack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
<XStack space={'$4'} style={{ padding: '0.65rem 1rem' }}>
<IconText
icon={message === 'Good' ? <CheckCircleIcon size={16} /> : <IncorrectIcon size={16} />}
weight={'semibold'}
@ -76,7 +76,7 @@ const DeviceStorageHealth: React.FC<DeviceStorageHealthProps> = ({ storage, maxS
{message}
</IconText>
{message === 'Poor' && (
<Text size={13} color="#E95460">
<Text size={13} color={'#E95460'} weight={'semibold'}>
{utilization.toFixed(0)}% Utilization
</Text>
)}

View File

@ -1,30 +1,30 @@
.breadcrumb-bar-nav {
width: 100%;
flex: 1 1 100%;
padding: 1rem 2rem;
margin: 0.5rem;
width: 100%;
flex: 1 1 100%;
padding: 1rem 2rem;
margin: 0.5rem;
}
.breadcrumb-bar-ul {
display: flex;
display: flex;
}
.breadcrumb-bar-li {
color: #647084;
font-weight: 500;
position: relative;
color: #647084;
font-weight: 500;
position: relative;
}
.breadcrumb-bar-li:last-child {
color: #09101C;
.breadcrumb-bar-li:last-child {
color: #09101c;
}
.breadcrumb-bar-li::after {
display: inline-block;
content: url("./icons/chevron.svg");
color: #09101C;
position: absolute;
top: 2px;
left: 100%;
transform: translateX(-50%);
display: inline-block;
content: url('./icons/chevron.svg');
color: #09101c;
position: absolute;
top: 2px;
left: 100%;
transform: translateX(-50%);
}
.breadcrumb-bar-li:last-child::after {
display: none;
display: none;
}

View File

@ -1,40 +1,40 @@
.color-picker-button {
width: 40px;
height: 40px;
border-radius: 50%;
margin-bottom: 10px;
margin-right: 10px;
position: relative;
outline: none;
border: none;
width: 40px;
height: 40px;
border-radius: 50%;
margin-bottom: 10px;
margin-right: 10px;
position: relative;
outline: none;
border: none;
}
.color-picker-button::before ,
.color-picker-button::after {
display: block;
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
transform-origin: center;
opacity: 0;
transition: 120ms opacity ease-in-out;
.color-picker-button::before,
.color-picker-button::after {
display: block;
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transform-origin: center;
opacity: 0;
transition: 120ms opacity ease-in-out;
}
.color-picker-button::before {
content: "\2713";
font-size: 25px;
color: white;
content: '\2713';
font-size: 25px;
color: white;
}
.color-picker-button::after {
width: 40px;
height: 40px;
border-radius: 50%;
border: 3px solid currentColor;
width: 40px;
height: 40px;
border-radius: 50%;
border: 3px solid currentColor;
}
.color-picker-button:focus::before,
.color-picker-button:focus::after,
.color-picker-button:focus::before,
.color-picker-button:focus::after,
.color-picker-button:hover::after {
opacity: 1;
opacity: 1;
}

View File

@ -1,10 +1,10 @@
.device-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
margin-right: -30px;
}
width: 80px;
height: 80px;
border-radius: 50%;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
margin-right: -30px;
}

View File

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { XStack, YStack } from 'tamagui'
import { Avatar, Text } from '@status-im/components'
import { ReactionIcon } from '@status-im/icons'
@ -6,15 +6,30 @@ import './CreateAvatar.css'
import LabelInputField from '../LabelInputField'
import ColorPicker from '../ColorPicker/ColorPicker'
import EmojiPickerDialog from '../EmojiPickerDialog'
import { Emoji, EmojiClickData } from 'emoji-picker-react'
import { Emoji, EmojiClickData, EmojiStyle } from 'emoji-picker-react'
const CreateAvatar = () => {
const [chosenColor, setChosenColor] = useState('#2A4AF5')
const [isEmojiDialogOpen, setIsEmojiDialogOpen] = useState(false)
const [selectedEmoji, setSelectedEmoji] = useState<string>('1f600')
const emojiRef = useRef<HTMLDivElement | null>(null)
function changeEmoji(emojiData: EmojiClickData) {
setSelectedEmoji(emojiData.unified)
}
useEffect(() => {
function handleClickOutside(event: any) {
if (emojiRef.current && !emojiRef.current.contains(event.target)) {
setIsEmojiDialogOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [emojiRef])
return (
<YStack my={16}>
<XStack space>
@ -27,7 +42,9 @@ const CreateAvatar = () => {
</Text>
<XStack my={10} alignItems={'end'}>
<div className="device-avatar" style={{ background: chosenColor }}>
{selectedEmoji ? <Emoji unified={selectedEmoji} size={30} /> : null}
{selectedEmoji ? (
<Emoji unified={selectedEmoji} emojiStyle={EmojiStyle.TWITTER} size={30} />
) : null}
</div>
<Avatar
type="icon"
@ -40,9 +57,11 @@ const CreateAvatar = () => {
/>
}
/>
{isEmojiDialogOpen && (
<EmojiPickerDialog changeEmoji={changeEmoji} emojiStyle="TWITTER" />
)}
<div ref={emojiRef}>
{isEmojiDialogOpen && (
<EmojiPickerDialog changeEmoji={changeEmoji} emojiStyle="TWITTER" />
)}
</div>
</XStack>
</YStack>
<YStack flexWrap="wrap" width="80%">

View File

@ -1,7 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
import EmojiPickerDialog from './EmojiPickerDialog'
const meta = {
title: 'General/EmojiPickerDialog',
component: EmojiPickerDialog,
@ -15,5 +14,5 @@ export default meta
type Story = StoryObj<typeof meta>
export const DefaultColors: Story = {
args: {emojiStyle: 'TWITTER'},
args: { emojiStyle: 'TWITTER' },
}

View File

@ -16,13 +16,13 @@ type EmojiPickerProps = {
function EmojiPickerDialog({ emojiStyle, changeEmoji }: EmojiPickerProps) {
return (
<Stack position="absolute" zIndex={1} left={100}>
<Stack position="absolute" zIndex={1} left={100} top={-150}>
<EmojiPicker
onEmojiClick={changeEmoji}
autoFocusSearch={false}
theme={Theme.AUTO}
height={350}
width={350}
height={'50vh'}
width={'50vw'}
emojiVersion="1"
lazyLoadEmojis={false}
previewConfig={{ showPreview: false }}

View File

@ -8,7 +8,7 @@ type HeaderProps = {
const Header = ({ selectedTag }: HeaderProps) => {
return (
<XStack justifyContent="space-between" py={'25px'} mt={'70px'}>
<XStack justifyContent="space-between" py={'25px'} mt={'4.4rem'}>
<NimbusLogo />
<TagContainer selectedTag={selectedTag} />
</XStack>

View File

@ -17,7 +17,7 @@ const IconText = ({ icon, children, weight }: IconTextProps) => {
space={'$2'}
>
{icon}
<Text size={13} color={'#000000'} weight={weight}>
<Text size={13} color={'#09101C'} weight={weight}>
{children}
</Text>
</XStack>

View File

@ -1,6 +1,6 @@
.input-container {
background-color: #fff;
border-radius: 12px;
margin-top: 8px;
width: 100%;
}
background-color: #fff;
border-radius: 12px;
margin-top: 8px;
width: 100%;
}

View File

@ -37,7 +37,15 @@ const LinkWithArrow = ({
onClick={navigateHandler}
>
{arrowLeft && <ArrowLeftIcon size={20} color="#2A4CF4" />}
<Link style={{ color: textColor || '#2A4CF4', marginBottom: '2px' }} to={to}>
<Link
style={{
color: textColor || '#2A4CF4',
marginBottom: '2px',
fontSize: 'inherit',
fontWeight: 'inherit',
}}
to={to}
>
{text}
</Link>
{arrowRight && <ArrowRightIcon size={20} color="#2A4CF4" />}

View File

@ -1,28 +1,28 @@
.quick-start-bar {
border-radius: 18px;
border: 1px solid rgba(0, 0, 0, 0.10);
opacity: 0.949999988079071;
background: rgba(255, 255, 255, 0.70);
backdrop-filter: blur(15px);
display: flex;
align-items: center;
width: 66%;
margin: 0 auto;
padding: 12px 1rem;
position: relative;
top: -18vh;
border-radius: 18px;
border: 1px solid rgba(0, 0, 0, 0.1);
opacity: 0.949999988079071;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(15px);
display: flex;
align-items: center;
width: 66%;
margin: 0 auto;
padding: 12px 1rem;
position: relative;
top: -18vh;
}
.quick-start-bar > div {
width: 100%;
border-radius: 24px;
background: rgba(100, 112, 132, 0.05);
margin-left: 1rem;
padding: 1rem 0.5rem;
width: 100%;
border-radius: 24px;
background: rgba(100, 112, 132, 0.05);
margin-left: 1rem;
padding: 1rem 0.5rem;
}
.quick-start-bar ul {
display: flex;
.quick-start-bar ul {
display: flex;
}
.quick-start-bar ul li {
color: #0D1625;
font-size: 13px;
color: #0d1625;
font-size: 13px;
}

View File

@ -1,3 +1,3 @@
/* .tag-container div:nth-child(1) {
background:transparent;
} */
} */

View File

@ -1,10 +1,9 @@
.nimbus-logomark{
display: flex;
justify-content: center;
align-items: center;
.nimbus-logomark {
display: flex;
justify-content: center;
align-items: center;
}
.nimbus-logomark svg {
width: auto;
height: 24px;
}
width: auto;
height: 24px;
}

View File

@ -8,6 +8,7 @@ type PageWrapperShadowProps = {
rightImageSrc?: string
rightImageLogo?: boolean
children: ReactNode
imgHeight?: string
}
const PageWrapperShadow = ({
@ -15,6 +16,7 @@ const PageWrapperShadow = ({
rightImageSrc,
rightImageLogo,
children,
imgHeight,
}: PageWrapperShadowProps) => {
const theme = useTheme()
@ -26,10 +28,15 @@ const PageWrapperShadow = ({
<div className="container-inner">{children}</div>
</div>
</section>
<section className="layout-right">
<div className="image-container">
<img src={rightImageSrc} alt="background" className="background-img" />
<img
src={rightImageSrc}
alt="background"
className="background-img"
style={{ height: imgHeight }}
/>
{rightImageLogo ? <NimbusLogoMark /> : null}
</div>
</section>

View File

@ -1,85 +1,85 @@
.layout{
background-color: #FFF;
height: 100%;
position: relative;
display: flex;
flex-wrap: wrap;
overflow: hidden;
.layout {
background-color: #fff;
height: 100%;
position: relative;
display: flex;
flex-wrap: wrap;
overflow: hidden;
}
.layout::after {
display: block;
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(113, 64, 253, 0.075) 15%, rgba(255,255,255,0) 50%);
display: block;
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(113, 64, 253, 0.075) 15%, rgba(255, 255, 255, 0) 50%);
}
.layout-left {
flex: 0 0 55%;
max-width: 55%;
z-index: 2;
flex: 0 0 55%;
max-width: 55%;
z-index: 2;
}
.container {
display: flex;
flex-wrap: wrap;
justify-content: end;
height: 100%;
/* padding: 70px 0 0; */
display: flex;
flex-wrap: wrap;
justify-content: end;
height: 100%;
/* padding: 70px 0 0; */
}
.container-inner {
max-width: 70%;
flex: 1 0 70%;
display: flex;
flex-direction: column;
max-width: 70%;
flex: 1 0 70%;
display: flex;
flex-direction: column;
}
.content {
flex-grow: 1;
flex-grow: 1;
}
/* LAYOUT RIGHT ELEMENT WITH IMAGE TAKING UP THE WHOLE HIGHT OF THE VIEWPORT */
.layout-right {
flex: 0 0 45%;
max-width: 45%;
z-index: 0;
flex: 0 0 45%;
max-width: 45%;
z-index: 0;
}
.image-container {
height: 100%;
position: relative;
overflow: hidden;
color: #FFF;
height: 100%;
position: relative;
overflow: hidden;
color: #fff;
}
.image-container::before {
display: block;
content: "";
padding-bottom: 100%;
display: block;
content: '';
padding-bottom: 100%;
}
.image-container::after {
display: block;
content: "";
position: absolute;
top: 0;
left: -1%;
width: 50%;
height: 100%;
background: linear-gradient(to right, rgba(255, 255, 255, 1) 20%, rgba(255, 255, 255, 0.0));
display: block;
content: '';
position: absolute;
top: 0;
left: -1%;
width: 50%;
height: 100%;
background: linear-gradient(to right, rgba(255, 255, 255, 1) 20%, rgba(255, 255, 255, 0));
}
.image-container .background-img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 140%;
width: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 140%;
width: auto;
}
.image-container .nimbus-logomark {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.image-container .nimbus-logomark svg {
height: 73px;
height: 73px;
}

View File

@ -1,12 +1,55 @@
@font-face {
font-family: 'Inter';
src: url('./Inter-font/Inter-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('./Inter-font/Inter-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('./Inter-font/Inter-Medium.ttf') format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('./Inter-font/Inter-SemiBold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('./Inter-font/Inter-Bold.ttf') format('truetype');
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('./Inter-font/Inter-Black.ttf') format('truetype');
font-weight: 900;
font-style: normal;
font-display: swap;
}
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(0, 0, 0, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
@ -22,7 +65,12 @@ body {
min-width: 320px;
min-height: 100vh;
}
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.2;
font-weight: 700;
}
@ -44,7 +92,7 @@ button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
ul{
ul {
list-style-type: none;
padding: 0;
margin: 0;
@ -55,7 +103,7 @@ ul li {
}
@media (prefers-color-scheme: light) {
:root {
color: #09101C;
color: #09101c;
background-color: #ffffff;
}
a:hover {

View File

@ -38,7 +38,6 @@ const BalanceChartCard = () => {
return (
<DashboardCardWrapper>
<YStack space={'$4'} style={{ width: '536px' }}>
<XStack justifyContent={'space-between'}>
<YStack>
@ -90,11 +89,10 @@ const BalanceChartCard = () => {
onSelect={handleRangeSelect}
/>
)}
<Stack >
<Stack>
<LineChart years={filteredYears} userGains={filteredUserGains} />
</Stack>
</YStack>
</DashboardCardWrapper>
)
}

View File

@ -31,9 +31,7 @@ const LineChart = ({ years, userGains }: LineChartProps) => {
],
options: {
responsive: true,
},
}
return <Line options={data.options} data={data} />

View File

@ -1,354 +1,354 @@
.rdp {
--rdp-cell-size: 32px;
--rdp-caption-font-size: 15px;
--rdp-accent-color: #2a4af5;
--rdp-background-color: #e7edff;
--rdp-background-color-selected-secondary: #f5f6f8;
--rdp-hover-color: #f5f6f8;
--rdp-hover-color-darker: #f0f2f5;
--rdp-accent-color-dark: #223bc4;
--rdp-outline: 2px solid var(--rdp-accent-color); /* Outline border for focused elements */
--rdp-outline-selected: 3px solid var(--rdp-accent-color); /* Outline border for focused _and_ selected elements */
--rdp-text-color: #09101c;
color: var(--rdp-text-color);
font-family: Inter, sans-serif;
font-weight: 500;
user-select: none;
}
/* Hide elements for devices that are not screen readers */
.rdp-vhidden {
box-sizing: border-box;
padding: 0;
margin: 0;
background: transparent;
border: 0;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
position: absolute !important;
top: 0;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
overflow: hidden !important;
clip: rect(1px, 1px, 1px, 1px) !important;
border: 0 !important;
}
/* Buttons */
.rdp-button_reset {
appearance: none;
position: relative;
margin: 0;
padding: 0;
cursor: default;
color: inherit;
background: none;
font: inherit;
-moz-appearance: none;
-webkit-appearance: none;
}
.rdp-button_reset:focus-visible {
/* Make sure to reset outline only when :focus-visible is supported */
outline: none;
}
.rdp-button {
border: 2px solid transparent;
font-size: 0.8125rem;
}
.rdp-button[disabled]:not(.rdp-day_selected) {
opacity: 0.25;
}
.rdp-button:not([disabled]) {
cursor: pointer;
}
.rdp-button:focus-visible:not([disabled]) {
color: inherit;
background-color: var(--rdp-background-color);
border: var(--rdp-outline);
}
.rdp-button:hover:not([disabled]):not(.rdp-day_selected) {
background-color: var(--rdp-hover-color);
}
.rdp-months {
display: flex;
}
.rdp-month {
margin: 0 1em;
}
.rdp-month:first-child {
margin-left: 0;
}
.rdp-month:last-child {
margin-right: 0;
}
.rdp-table {
margin: 0;
max-width: calc(var(--rdp-cell-size) * 7);
border-collapse: separate;
border-spacing: 0 2px;
padding: 0 0.75rem 0.625rem 0.75rem;
}
.rdp-with_weeknumber .rdp-table {
max-width: calc(var(--rdp-cell-size) * 8);
border-collapse: collapse;
}
.rdp-caption {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0;
text-align: left;
}
.rdp-multiple_months .rdp-caption {
position: relative;
display: block;
text-align: center;
}
.rdp-caption_dropdowns {
position: relative;
display: inline-flex;
}
.rdp-caption_label {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
margin: 0;
white-space: nowrap;
color: currentColor;
font-family: inherit;
font-size: var(--rdp-caption-font-size);
font-weight: bold;
}
.rdp-nav {
white-space: nowrap;
padding: 0.375rem 0.375rem 0 0.75rem;
}
.rdp-multiple_months .rdp-caption_start .rdp-nav {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
}
.rdp-multiple_months .rdp-caption_end .rdp-nav {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
}
.rdp-nav_button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
padding: 0.25em;
border-radius: 10px;
}
/* ---------- */
/* Dropdowns */
/* ---------- */
.rdp-dropdown_year,
.rdp-dropdown_month {
position: relative;
display: inline-flex;
align-items: center;
}
.rdp-dropdown {
appearance: none;
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
left: 0;
width: 100%;
margin: 0;
padding: 0;
cursor: inherit;
opacity: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.rdp-dropdown[disabled] {
opacity: unset;
color: unset;
}
.rdp-dropdown:focus-visible:not([disabled]) + .rdp-caption_label {
background-color: var(--rdp-background-color);
border: var(--rdp-outline);
border-radius: 6px;
}
.rdp-dropdown_icon {
margin: 0 0 0 5px;
}
.rdp-head {
border: 0;
}
.rdp-head_row,
.rdp-row {
height: 100%;
padding-bottom: 2px;
}
.rdp-head_cell {
vertical-align: middle;
font-size: 0.8125em;
font-weight: 500;
text-align: center;
height: var(--rdp-cell-size);
padding: 0;
line-height: 140%;
color: #647084;
letter-spacing: -0.003em;
}
.rdp-tbody {
border: 0;
}
.rdp-tfoot {
margin: 0.5em;
}
.rdp-cell {
width: var(--rdp-cell-size);
height: var(--rdp-cell-size);
text-align: center;
padding: 0;
}
.rdp-cell_selected_start {
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-cell_selected_end {
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-cell_selected_range {
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-weeknumber {
font-size: 0.75em;
}
.rdp-weeknumber,
.rdp-day {
display: flex;
overflow: hidden;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: var(--rdp-cell-size);
max-width: var(--rdp-cell-size);
height: var(--rdp-cell-size);
margin: 0;
border: 2px solid transparent;
border-radius: 10px;
}
.rdp-day_today:not(.rdp-day_outside) {
position: relative;
}
.rdp-day_today:not(.rdp-day_outside)::after {
content: '';
position: absolute;
bottom: 3px;
left: 50%;
width: 4px;
height: 2px;
transform: translateX(-50%);
border-radius: 10px;
background-color: var(--rdp-accent-color);
}
.rdp-day_selected {
opacity: 1;
background-color: var(--rdp-accent-color);
color: var(--rdp-background-color);
transition: all 150ms ease-in-out;
}
.rdp-day_selected:focus-visible,
.rdp-day_selected:hover {
opacity: 1;
background-color: var(--rdp-accent-color-dark);
border-radius: 10px;
}
.rdp-day_outside {
opacity: 0.3;
}
.rdp-day_selected:focus-visible {
/* Since the background is the same use again the outline */
outline: var(--rdp-outline);
outline-offset: 2px;
z-index: 1;
}
.rdp[dir='rtl'] .rdp-day_range_start:not(.rdp-day_range_end) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.rdp[dir='rtl'] .rdp-day_range_end:not(.rdp-day_range_start) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.rdp-day_range_end.rdp-day_range_start {
border-radius: 10px;
}
.rdp-day_range_middle {
color: var(--rdp-text-color);
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-day_range_middle:hover {
color: var(--rdp-text-color);
background-color: var(--rdp-hover-color-darker);
}
--rdp-cell-size: 32px;
--rdp-caption-font-size: 15px;
--rdp-accent-color: #2a4af5;
--rdp-background-color: #e7edff;
--rdp-background-color-selected-secondary: #f5f6f8;
--rdp-hover-color: #f5f6f8;
--rdp-hover-color-darker: #f0f2f5;
--rdp-accent-color-dark: #223bc4;
--rdp-outline: 2px solid var(--rdp-accent-color); /* Outline border for focused elements */
--rdp-outline-selected: 3px solid var(--rdp-accent-color); /* Outline border for focused _and_ selected elements */
--rdp-text-color: #09101c;
color: var(--rdp-text-color);
font-family: Inter, sans-serif;
font-weight: 500;
user-select: none;
}
/* Hide elements for devices that are not screen readers */
.rdp-vhidden {
box-sizing: border-box;
padding: 0;
margin: 0;
background: transparent;
border: 0;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
position: absolute !important;
top: 0;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
overflow: hidden !important;
clip: rect(1px, 1px, 1px, 1px) !important;
border: 0 !important;
}
/* Buttons */
.rdp-button_reset {
appearance: none;
position: relative;
margin: 0;
padding: 0;
cursor: default;
color: inherit;
background: none;
font: inherit;
-moz-appearance: none;
-webkit-appearance: none;
}
.rdp-button_reset:focus-visible {
/* Make sure to reset outline only when :focus-visible is supported */
outline: none;
}
.rdp-button {
border: 2px solid transparent;
font-size: 0.8125rem;
}
.rdp-button[disabled]:not(.rdp-day_selected) {
opacity: 0.25;
}
.rdp-button:not([disabled]) {
cursor: pointer;
}
.rdp-button:focus-visible:not([disabled]) {
color: inherit;
background-color: var(--rdp-background-color);
border: var(--rdp-outline);
}
.rdp-button:hover:not([disabled]):not(.rdp-day_selected) {
background-color: var(--rdp-hover-color);
}
.rdp-months {
display: flex;
}
.rdp-month {
margin: 0 1em;
}
.rdp-month:first-child {
margin-left: 0;
}
.rdp-month:last-child {
margin-right: 0;
}
.rdp-table {
margin: 0;
max-width: calc(var(--rdp-cell-size) * 7);
border-collapse: separate;
border-spacing: 0 2px;
padding: 0 0.75rem 0.625rem 0.75rem;
}
.rdp-with_weeknumber .rdp-table {
max-width: calc(var(--rdp-cell-size) * 8);
border-collapse: collapse;
}
.rdp-caption {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0;
text-align: left;
}
.rdp-multiple_months .rdp-caption {
position: relative;
display: block;
text-align: center;
}
.rdp-caption_dropdowns {
position: relative;
display: inline-flex;
}
.rdp-caption_label {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
margin: 0;
white-space: nowrap;
color: currentColor;
font-family: inherit;
font-size: var(--rdp-caption-font-size);
font-weight: bold;
}
.rdp-nav {
white-space: nowrap;
padding: 0.375rem 0.375rem 0 0.75rem;
}
.rdp-multiple_months .rdp-caption_start .rdp-nav {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
}
.rdp-multiple_months .rdp-caption_end .rdp-nav {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
}
.rdp-nav_button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
padding: 0.25em;
border-radius: 10px;
}
/* ---------- */
/* Dropdowns */
/* ---------- */
.rdp-dropdown_year,
.rdp-dropdown_month {
position: relative;
display: inline-flex;
align-items: center;
}
.rdp-dropdown {
appearance: none;
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
left: 0;
width: 100%;
margin: 0;
padding: 0;
cursor: inherit;
opacity: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.rdp-dropdown[disabled] {
opacity: unset;
color: unset;
}
.rdp-dropdown:focus-visible:not([disabled]) + .rdp-caption_label {
background-color: var(--rdp-background-color);
border: var(--rdp-outline);
border-radius: 6px;
}
.rdp-dropdown_icon {
margin: 0 0 0 5px;
}
.rdp-head {
border: 0;
}
.rdp-head_row,
.rdp-row {
height: 100%;
padding-bottom: 2px;
}
.rdp-head_cell {
vertical-align: middle;
font-size: 0.8125em;
font-weight: 500;
text-align: center;
height: var(--rdp-cell-size);
padding: 0;
line-height: 140%;
color: #647084;
letter-spacing: -0.003em;
}
.rdp-tbody {
border: 0;
}
.rdp-tfoot {
margin: 0.5em;
}
.rdp-cell {
width: var(--rdp-cell-size);
height: var(--rdp-cell-size);
text-align: center;
padding: 0;
}
.rdp-cell_selected_start {
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-cell_selected_end {
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-cell_selected_range {
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-weeknumber {
font-size: 0.75em;
}
.rdp-weeknumber,
.rdp-day {
display: flex;
overflow: hidden;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: var(--rdp-cell-size);
max-width: var(--rdp-cell-size);
height: var(--rdp-cell-size);
margin: 0;
border: 2px solid transparent;
border-radius: 10px;
}
.rdp-day_today:not(.rdp-day_outside) {
position: relative;
}
.rdp-day_today:not(.rdp-day_outside)::after {
content: '';
position: absolute;
bottom: 3px;
left: 50%;
width: 4px;
height: 2px;
transform: translateX(-50%);
border-radius: 10px;
background-color: var(--rdp-accent-color);
}
.rdp-day_selected {
opacity: 1;
background-color: var(--rdp-accent-color);
color: var(--rdp-background-color);
transition: all 150ms ease-in-out;
}
.rdp-day_selected:focus-visible,
.rdp-day_selected:hover {
opacity: 1;
background-color: var(--rdp-accent-color-dark);
border-radius: 10px;
}
.rdp-day_outside {
opacity: 0.3;
}
.rdp-day_selected:focus-visible {
/* Since the background is the same use again the outline */
outline: var(--rdp-outline);
outline-offset: 2px;
z-index: 1;
}
.rdp[dir='rtl'] .rdp-day_range_start:not(.rdp-day_range_end) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.rdp[dir='rtl'] .rdp-day_range_end:not(.rdp-day_range_start) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.rdp-day_range_end.rdp-day_range_start {
border-radius: 10px;
}
.rdp-day_range_middle {
color: var(--rdp-text-color);
background-color: var(--rdp-background-color-selected-secondary);
}
.rdp-day_range_middle:hover {
color: var(--rdp-text-color);
background-color: var(--rdp-hover-color-darker);
}

View File

@ -18,5 +18,5 @@ export const Default: Story = {
args: { load: [32, 12, 45, 10] },
}
export const BadStats: Story = {
args:{load:[12,32,14,35,65,90]}
}
args: { load: [12, 32, 14, 35, 65, 90] },
}

View File

@ -39,15 +39,16 @@ const CPUCard = ({ load }: CPUCardProps) => {
const message = currentLoad < 80 ? 'Good' : 'Poor'
return (
<DashboardCardWrapper padding='0'>
<YStack style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}>
<DashboardCardWrapper padding="0">
<YStack
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<XStack
justifyContent="space-between"
style={{

View File

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

View File

@ -0,0 +1,40 @@
import { IconButton } from '@status-im/components'
import { Stack } from 'tamagui'
type IconButtonWithDotProps = {
iconEl: any
variant: 'ghost' | 'outline'
isDotOn: boolean
selected?: boolean
disabled?: boolean
}
const IconButtonWithDot = ({
iconEl,
variant,
isDotOn,
selected,
disabled,
}: IconButtonWithDotProps) => {
return (
<Stack style={{ position: 'relative', display: 'inline-block' }}>
<IconButton icon={iconEl} variant={variant} selected={selected} disabled={disabled} />
{isDotOn && (
<Stack
style={{
position: 'absolute',
right: 7,
top: 5,
width: '9px',
height: '9px',
borderRadius: '50%',
backgroundColor: '#1992D7',
border: '1.5px solid #fff',
}}
/>
)}
</Stack>
)
}
export default IconButtonWithDot

View File

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

View File

@ -1,4 +1,3 @@
import { IconButton } from '@status-im/components'
import {
DashboardIcon,
SpeedIcon,
@ -10,8 +9,12 @@ import {
SettingsIcon,
} from '@status-im/icons'
import { YStack } from 'tamagui'
import IconButtonWithDot from './IconButtonWithDot'
import { useDispatch, useSelector } from 'react-redux'
const LeftSidebar = () => {
const buttons = useSelector((state: any) => state.leftSidebar.buttons)
console.log(buttons)
return (
<YStack
space={'$4'}
@ -24,14 +27,24 @@ const LeftSidebar = () => {
border: '1px solid #F0F2F5',
}}
>
<IconButton icon={<DashboardIcon size={20} />} variant="ghost" selected />
<IconButton icon={<SpeedIcon size={20} />} variant="ghost" />
<IconButton icon={<ChartIcon size={20} />} variant="outline" disabled />
<IconButton icon={<HeartIcon size={20} />} variant="ghost" />
<IconButton icon={<CodeBlockIcon size={20} />} variant="ghost" />
<IconButton icon={<CommunitiesIcon size={20} />} variant="ghost" />
<IconButton icon={<ActivityCenterIcon size={20} />} variant="ghost" />
<IconButton icon={<SettingsIcon size={20} />} variant="ghost" />
<IconButtonWithDot
iconEl={<DashboardIcon size={20} />}
variant="ghost"
isDotOn={false}
selected={true}
/>
<IconButtonWithDot iconEl={<SpeedIcon size={20} />} variant="ghost" isDotOn={false} />
<IconButtonWithDot
iconEl={<ChartIcon size={20} />}
variant="outline"
isDotOn={false}
disabled={true}
/>
<IconButtonWithDot iconEl={<HeartIcon size={20} />} variant="ghost" isDotOn={false} />
<IconButtonWithDot iconEl={<CodeBlockIcon size={20} />} variant="ghost" isDotOn={false} />
<IconButtonWithDot iconEl={<CommunitiesIcon size={20} />} variant="ghost" isDotOn={false} />
<IconButtonWithDot iconEl={<ActivityCenterIcon size={20} />} variant="ghost" isDotOn={true} />
<IconButtonWithDot iconEl={<SettingsIcon size={20} />} variant="ghost" isDotOn={false} />
</YStack>
)
}

View File

@ -3,20 +3,20 @@ import type { Meta, StoryObj } from '@storybook/react'
import MemoryCard from './MemoryCard'
const meta = {
title: 'Dashboard/MemoryCard',
component: MemoryCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
title: 'Dashboard/MemoryCard',
component: MemoryCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof MemoryCard>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: { currentMemory: [12, 24, 12, 4, 30, 50], maxMemory: 100 },
args: { currentMemory: [12, 24, 12, 4, 30, 50], maxMemory: 100 },
}
export const BadStats: Story = {
args: { maxMemory: 60, currentMemory: [2, 5, 7, 32, 23, 12, 89] }
}
args: { maxMemory: 60, currentMemory: [2, 5, 7, 32, 23, 12, 89] },
}

View File

@ -1,5 +1,5 @@
import { Paragraph, Separator, Stack, XStack, YStack } from 'tamagui'
import { Text } from '@status-im/components'
import { Text } from '@status-im/components'
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
import StandartLineChart from '../../../components/Charts/StandardLineChart'
@ -24,7 +24,6 @@ type MemoryCardProps = {
}
const MemoryCard = ({ currentMemory, maxMemory }: MemoryCardProps) => {
const chartData: ChartData[] = [
{
id: 'cpu',
@ -42,14 +41,16 @@ const MemoryCard = ({ currentMemory, maxMemory }: MemoryCardProps) => {
const message = currentLoad < maxMemory ? 'Good' : 'Poor'
return (
<DashboardCardWrapper padding='0'>
<YStack style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}>
<DashboardCardWrapper padding="0">
<YStack
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<XStack
justifyContent="space-between"
style={{
@ -71,7 +72,7 @@ const MemoryCard = ({ currentMemory, maxMemory }: MemoryCardProps) => {
</XStack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$4'} style={{ padding: '10px 16px 10px 16px' }}>
<IconText
icon={message === 'Good' ? <CheckCircleIcon size={16} /> : <IncorrectIcon size={16} />}
@ -85,7 +86,7 @@ const MemoryCard = ({ currentMemory, maxMemory }: MemoryCardProps) => {
)}
</XStack>
</YStack>
</DashboardCardWrapper >
</DashboardCardWrapper>
)
}

View File

@ -3,20 +3,20 @@ import type { Meta, StoryObj } from '@storybook/react'
import NetworkCard from './NetworkCard'
const meta = {
title: 'Dashboard/NetworkCard',
component: NetworkCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
title: 'Dashboard/NetworkCard',
component: NetworkCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof NetworkCard>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {downloadRate:[12,24,12,4,30,50], uploadRate:[2,5,7,32,23,82] },
args: { downloadRate: [12, 24, 12, 4, 30, 50], uploadRate: [2, 5, 7, 32, 23, 82] },
}
export const BadStats: Story = {
args: { downloadRate:[12,24,12,4,30, 1], uploadRate:[2,5,7,32,23,12] }
}
args: { downloadRate: [12, 24, 12, 4, 30, 1], uploadRate: [2, 5, 7, 32, 23, 12] },
}

View File

@ -1,5 +1,5 @@
import { Paragraph, Separator, Stack, XStack, YStack } from 'tamagui'
import { Text } from '@status-im/components'
import { Text } from '@status-im/components'
import { CheckCircleIcon, IncorrectIcon } from '@status-im/icons'
import StandartLineChart from '../../../components/Charts/StandardLineChart'
@ -23,7 +23,6 @@ type NetworkCardProps = {
}
const NetworkCard = ({ uploadRate, downloadRate }: NetworkCardProps) => {
const chartData: ChartData[] = [
{
id: 'uploadRate',
@ -49,16 +48,16 @@ const NetworkCard = ({ uploadRate, downloadRate }: NetworkCardProps) => {
const message = currentLoad > 60 ? 'Good' : 'Poor'
return (
<DashboardCardWrapper padding='0'>
<YStack style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}>
<DashboardCardWrapper padding="0">
<YStack
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<XStack
justifyContent="space-between"
style={{

View File

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

View File

@ -3,20 +3,20 @@ import type { Meta, StoryObj } from '@storybook/react'
import StorageCard from './StorageCard'
const meta = {
title: 'Dashboard/StorageCard',
component: StorageCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
title: 'Dashboard/StorageCard',
component: StorageCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof StorageCard>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: { storage: 45, maxStorage: 100 },
args: { storage: 45, maxStorage: 100 },
}
export const BadStats: Story = {
args: { storage: 110, maxStorage: 100 }
}
args: { storage: 110, maxStorage: 100 },
}

View File

@ -35,14 +35,16 @@ const StorageCard = ({ storage, maxStorage }: StorageCardProps) => {
}
return (
<DashboardCardWrapper padding="0" >
<YStack style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}>
<DashboardCardWrapper padding="0">
<YStack
style={{
width: '284px',
height: '136px',
borderRadius: '16px',
border: message === 'Poor' ? '1px solid #D92344' : 'none',
backgroundColor: message === 'Poor' ? '#fefafa' : '#fff',
}}
>
<XStack
justifyContent="space-between"
style={{

View File

@ -3,18 +3,17 @@ import type { Meta, StoryObj } from '@storybook/react'
import ConsensusClientCard from './ConsensusClientCard'
const meta = {
title: 'Dashboard/ConsensusClientCard',
component: ConsensusClientCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
title: 'Dashboard/ConsensusClientCard',
component: ConsensusClientCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ConsensusClientCard>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: { value: 200000, total: 123562 },
args: { value: 200000, total: 123562 },
}

View File

@ -9,58 +9,56 @@ import Icon from '../../../components/General/Icon'
import StandardGauge from '../../../components/Charts/StandardGauge'
type ConsensusCardProps = {
value: number
total: number
value: number
total: number
}
const ConsensusCard = ({ value, total }: ConsensusCardProps) => {
const style: CSSProperties = {}
const data = [
{
id: 'storage',
label: 'Used',
value: value,
color: '#ff6161',
},
{
id: 'storage',
label: 'Free',
value: total,
color: '#E7EAEE',
},
]
const style: CSSProperties = {}
const data = [
{
id: 'storage',
label: 'Used',
value: value,
color: '#ff6161',
},
{
id: 'storage',
label: 'Free',
value: total,
color: '#E7EAEE',
},
]
return (
<Shadow variant="$1" style={style}>
<YStack>
<Stack style={{ minHeight: '90px', padding: '12px 16px' }}>
<Text size={15} weight={'semibold'} color="#647084">
Consensus Client
</Text>
<XStack style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Icon src="./icons/vector.svg" height={30} width={60} />
<Stack
style={{
height: '56px',
width: '56px',
}}
>
<StandardGauge data={data} />
</Stack>
</XStack>
</Stack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$3'} style={{ padding: '12px 16px' }}>
<IconText icon={<SwapIcon size={16} />}>Syncing</IconText>
<Text size={13} weight={'semibold'}>
{formatNumbersWithComa(value)} / {formatNumbersWithComa(total)}
</Text>
</XStack>
</YStack>
</Shadow>
)
return (
<Shadow variant="$1" style={style}>
<YStack>
<Stack style={{ minHeight: '90px', padding: '12px 16px' }}>
<Text size={15} weight={'semibold'} color="#647084">
Consensus Client
</Text>
<XStack style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Icon src="./icons/vector.svg" height={30} width={60} />
<Stack
style={{
height: '56px',
width: '56px',
}}
>
<StandardGauge data={data} />
</Stack>
</XStack>
</Stack>
<Separator borderColor={'#e3e3e3'} />
<XStack space={'$3'} style={{ padding: '12px 16px' }}>
<IconText icon={<SwapIcon size={16} />}>Syncing</IconText>
<Text size={13} weight={'semibold'}>
{formatNumbersWithComa(value)} / {formatNumbersWithComa(total)}
</Text>
</XStack>
</YStack>
</Shadow>
)
}
export default ConsensusCard

View File

@ -3,18 +3,17 @@ import type { Meta, StoryObj } from '@storybook/react'
import ExecutionClientCard from './ExecutionClientCard'
const meta = {
title: 'Dashboard/ExecutionClientCard',
component: ExecutionClientCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
title: 'Dashboard/ExecutionClientCard',
component: ExecutionClientCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof ExecutionClientCard>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: { value: 200000, total: 123562 },
args: { value: 200000, total: 123562 },
}

View File

@ -11,7 +11,6 @@ import StandardGauge from '../../../components/Charts/StandardGauge'
type ExecutionClientCardProps = {
value: number
total: number
}
const ExecutionClientCard = ({ value, total }: ExecutionClientCardProps) => {
@ -31,8 +30,6 @@ const ExecutionClientCard = ({ value, total }: ExecutionClientCardProps) => {
]
const style: CSSProperties = {}
return (
<Shadow variant="$1" style={style}>
<YStack>
@ -41,7 +38,9 @@ const ExecutionClientCard = ({ value, total }: ExecutionClientCardProps) => {
Execution Client
</Text>
<XStack style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Text size={19} weight={'semibold'}>Geth</Text>
<Text size={19} weight={'semibold'}>
Geth
</Text>
<Stack
style={{
height: '56px',

View File

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

View File

@ -3,123 +3,122 @@ import type { Meta, StoryObj } from '@storybook/react'
import UptimeChart from './UptimeChart'
const meta = {
title: 'Dashboard/UptimeChart',
component: UptimeChart,
parameters: {
layout: 'centered',
},
decorators: [
Story => (
<div style={{ height: '20vh', width: '50vh' }}>
<Story />
</div>
),
],
tags: ['autodocs'],
title: 'Dashboard/UptimeChart',
component: UptimeChart,
parameters: {
layout: 'centered',
},
decorators: [
Story => (
<div style={{ height: '20vh', width: '50vh' }}>
<Story />
</div>
),
],
tags: ['autodocs'],
} satisfies Meta<typeof UptimeChart>
export default meta
type Story = StoryObj<typeof meta>
export const WithoutLabels: Story = {
args: {
data: [
{
pv: 1,
pa: 1,
},
{
pv: 0,
pa: 0,
},
{
pv: 0,
pa: 0,
},
{
pv: 0,
pa: 1,
},
{
pv: 0,
pa: 0,
},
{
pv: 1,
pa: 0,
},
{
pv: 0,
pa: 1,
},
]
},
args: {
data: [
{
pv: 1,
pa: 1,
},
{
pv: 0,
pa: 0,
},
{
pv: 0,
pa: 0,
},
{
pv: 0,
pa: 1,
},
{
pv: 0,
pa: 0,
},
{
pv: 1,
pa: 0,
},
{
pv: 0,
pa: 1,
},
],
},
}
export const WithMonths: Story = {
args: {
data: [
{
name: 'Jan',
pv: 0,
pa: 0,
},
{
name: 'Feb',
pv: 0,
pa: 0,
},
{
name: 'Mar',
pv: 0,
pa: 0,
},
{
name: 'Apr',
pv: 0,
pa: 0,
},
{
name: 'May',
pv: 0,
pa: 0,
},
{
name: 'Aug',
pv: 0,
pa: 0,
},
{
name: 'Jun',
pv: 0,
pa: 0,
},
{
name: 'Jul',
pv: 1,
pa: 0,
},
{
name: 'Sep',
pv: 0,
pa: 0,
},
{
name: 'Oct',
pv: 0,
pa: 0,
},
{
name: 'Nov',
pv: 1,
pa: 1,
},
{
name: 'Dec',
pv: 1,
pa: 1,
},
]
}
}
args: {
data: [
{
name: 'Jan',
pv: 0,
pa: 0,
},
{
name: 'Feb',
pv: 0,
pa: 0,
},
{
name: 'Mar',
pv: 0,
pa: 0,
},
{
name: 'Apr',
pv: 0,
pa: 0,
},
{
name: 'May',
pv: 0,
pa: 0,
},
{
name: 'Aug',
pv: 0,
pa: 0,
},
{
name: 'Jun',
pv: 0,
pa: 0,
},
{
name: 'Jul',
pv: 1,
pa: 0,
},
{
name: 'Sep',
pv: 0,
pa: 0,
},
{
name: 'Oct',
pv: 0,
pa: 0,
},
{
name: 'Nov',
pv: 1,
pa: 1,
},
{
name: 'Dec',
pv: 1,
pa: 1,
},
],
},
}

View File

@ -1,4 +1,4 @@
import { BarChart, Bar, ResponsiveContainer, XAxis } from 'recharts';
import { BarChart, Bar, ResponsiveContainer, XAxis } from 'recharts'
type DataItem = {
name?: string
@ -12,20 +12,15 @@ type UptimeChartProps = {
const UptimeChart = ({ data }: UptimeChartProps) => {
return (
<ResponsiveContainer >
<BarChart
data={data}
style={{ backgroundColor: '#F0F2F5' }}
>
<ResponsiveContainer>
<BarChart data={data} style={{ backgroundColor: '#F0F2F5' }}>
<Bar dataKey="pv" barSize={2} fill="#E95460" />
{/* @NOTE: We can add as many Bar items as we need and use them for each day of the month */}
<Bar dataKey="pa" barSize={2} fill="#E95460" />
{data[0].name && (
<XAxis dataKey="name" fontSize={'10px'} tickMargin={10} />
)}
{data[0].name && <XAxis dataKey="name" fontSize={'10px'} tickMargin={10} />}
</BarChart>
</ResponsiveContainer>
);
)
}
export default UptimeChart

View File

@ -16,13 +16,14 @@ const DeviceHealthCheck = () => {
const deviceHealthState = useSelector((state: RootState) => state.deviceHealth)
return (
<PageWrapperShadow rightImageSrc="./background-images/eye-background.png">
<PageWrapperShadow rightImageSrc="./background-images/eye-background.png" imgHeight="100%">
<YStack
space={'$4'}
style={{
justifyContent: 'end',
alignItems: 'start',
marginBottom: '2rem',
marginTop: '4.4rem',
maxWidth: '100%',
}}
>
@ -32,14 +33,14 @@ const DeviceHealthCheck = () => {
subtitle="Configure your device to start Staking on Nimbus"
isAdvancedSettings={true}
/>
<XStack space={'$4'}>
<XStack space={'$4'} width={'100%'}>
<DeviceStorageHealth
storage={deviceHealthState.storage}
maxStorage={deviceHealthState.maxMemory}
/>
<DeviceCPULoad load={deviceHealthState.cpuLoad} />
</XStack>
<XStack space={'$4'}>
<XStack space={'$4'} width={'100%'}>
<DeviceMemory
currentMemory={deviceHealthState.memory}
maxMemory={deviceHealthState.maxMemory}

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react'
import DeviceSyncStatus from './DeviceSyncStatus'
const meta = {

View File

@ -25,13 +25,17 @@ const DeviceSyncStatus = () => {
}, [dispatch])
return (
<PageWrapperShadow rightImageSrc="./background-images/sync-status-background.png">
<PageWrapperShadow
rightImageSrc="./background-images/sync-status-background.png"
imgHeight="120%"
>
<YStack
space={'$4'}
style={{
justifyContent: 'end',
alignItems: 'start',
maxWidth: '100%',
marginTop: '4.4rem',
}}
>
<NimbusLogo />

View File

@ -46,7 +46,7 @@ const SyncStatusCardConsensus: React.FC<DeviceStorageHealthProps> = ({ synced, t
style={{
padding: '8px 16px',
position: 'relative',
height: '160px',
minHeight: '180px',
}}
>
<YStack space={'$3'} style={{ width: '100%' }}>

View File

@ -45,7 +45,7 @@ const SyncStatusCardExecution: React.FC<DeviceStorageHealthProps> = ({ synced, t
style={{
padding: '8px 16px',
position: 'relative',
height: '160px',
minHeight: '180px',
}}
>
<YStack space={'$3'} style={{ width: '100%' }}>

View File

@ -1,4 +1,3 @@
.landing-page {
height: 100%;
.landing-page {
height: 100%;
}

View File

@ -16,7 +16,7 @@ const LandingPage = () => {
return (
<>
<PageWrapperShadow rightImageSrc="./background-images/landing-page-bg.png">
<PageWrapperShadow rightImageSrc="./background-images/landing-page-bg.png" imgHeight="150%">
<YStack className="landing-page">
<XStack pt={'70px'}>
<NimbusLogo />

View File

@ -48,8 +48,8 @@ const SyncStatus = ({
const timer = convertSecondsToTimerFormat(elapsedTime)
const connectViaIpHandler = () => {
navigate('/connect-device');
};
navigate('/connect-device')
}
return (
<YStack space={'$2'}>
@ -91,7 +91,7 @@ const SyncStatus = ({
)}
{isAwaitingPairing && (
<XStack>
<Button icon={<Icon src={ConnectionIcon} />} size={40} onPress={connectViaIpHandler} >
<Button icon={<Icon src={ConnectionIcon} />} size={40} onPress={connectViaIpHandler}>
Connect via IP
</Button>
</XStack>

View File

@ -20,22 +20,21 @@ export const Default: Story = {
args: {
validatorsValue: '4',
executionSyncStatus1: {
text: "Execution Sync Status",
text: 'Execution Sync Status',
isGaugeIncluded: true,
gaugeColor: "$blue",
gaugeColor: '$blue',
gaugeSynced: 123.524,
gaugeTotal: 172.503,
},
executionSyncStatus2: {
text: "Execution Sync Status",
text: 'Execution Sync Status',
isGaugeIncluded: true,
gaugeColor: "$red",
gaugeColor: '$red',
gaugeSynced: 123.524,
gaugeTotal: 172.503,
},
currentAPRValue: "4.40%",
estimatedActivationTimeValue: "32 Days",
validatorQueueValue: "92603",
currentAPRValue: '4.40%',
estimatedActivationTimeValue: '32 Days',
validatorQueueValue: '92603',
},
}

View File

@ -48,20 +48,20 @@ const Activation = ({
}, [])
return (
<Stack style={styles.confettiContainer} width={'100%'}>
<Stack style={styles.confettiContainer} width={'100%'} minHeight={'65vh'}>
{showConfetti && <Confetti style={styles.confettiCanvas} />}
<YStack style={{ padding: '16px 32px' }}>
<YStack style={{ padding: '26px 32px' }}>
<YStack space={'$5'}>
<Text size={27} weight={'semibold'}>
Activation
</Text>
<Stack style={{ width: '66%' }}>
<Text size={27}>
<Text size={19}>
Congratulations! You have successfully setup your Nimbus Validators and are currently
syncing your nodes.
</Text>
</Stack>
<YStack space={'$3'} marginTop={'10px'} width={'33%'}>
<YStack space={'$3'} marginTop={'25px'} width={'33%'}>
<XStack space={'$3'} justifyContent={'space-between'}>
<ActivationCard text="Validators" value={validatorsValue} />
<ActivationCard {...executionSyncStatus1} />
@ -81,7 +81,7 @@ const Activation = ({
text="Edit Validators"
to="/"
arrowLeft={true}
style={{ marginTop: '44px', marginBottom: '88px' }}
style={{ marginTop: '44px', marginBottom: '88px', fontSize: '13px' }}
/>
</YStack>
</Stack>

View File

@ -30,7 +30,7 @@ const ActivationCard = ({
}}
>
{!isGaugeIncluded && (
<Stack>
<Stack space={'$2'}>
<Text size={15} weight={'semibold'}>
{text}
</Text>
@ -40,7 +40,7 @@ const ActivationCard = ({
</Stack>
)}
{isGaugeIncluded && (
<Stack>
<Stack space={'$2'}>
<Text size={15} weight={'semibold'}>
{text}
</Text>

View File

@ -15,8 +15,8 @@ const Advisories = () => {
return (
<XStack
style={{ padding: '16px 32px', justifyContent: 'space-between' }}
height={'65vh'}
style={{ padding: '30px 33px', justifyContent: 'space-between' }}
minHeight={'65vh'}
width={'100%'}
>
<YStack space={'$2'}>
@ -33,7 +33,7 @@ const Advisories = () => {
space={'$2'}
>
<Text
size={27}
size={19}
weight={isSameTitle(title) && 'semibold'}
color={isSameTitle(title) ? 'blue' : ''}
>

View File

@ -17,11 +17,11 @@ const AdvisoriesContent = ({ title, content }: AdvisoriesContentProps) => {
</Stack>
<YStack space={'$4'}>
{content.map(row => (
<Text key={row} size={19}>
<Text key={row} size={15}>
{row}
</Text>
))}
<Text size={19}>
<Text size={15}>
<Link
to={'https://github.com/ethereum/consensus-specs'}
style={{ textDecorationLine: 'underline', color: '#484848' }}
@ -29,7 +29,7 @@ const AdvisoriesContent = ({ title, content }: AdvisoriesContentProps) => {
The Ethereum consensus layer specification
</Link>
</Text>
<Text size={19} weight={'semibold'}>
<Text size={15} weight={'semibold'}>
<Link
to={'https://github.com/ethereum/consensus-specs'}
style={{ textDecorationLine: 'underline', color: '#2A4CF4', fontWeight: 'bold' }}

View File

@ -6,7 +6,7 @@ import LinkWithArrow from '../../../components/General/LinkWithArrow'
const ClientSetup = () => {
return (
<YStack padding={'26px'} width={'100%'} space={'$5'}>
<YStack space={'$8'} padding={'26px'} width={'100%'} minHeight={'65vh'}>
<SetupRow title={'Setup up Validators'} />
<Separator borderColor={'#F0F2F5'} />
<WithdrawalAddress title={'Withdrawal address'} />
@ -14,7 +14,7 @@ const ClientSetup = () => {
text="Advanced Recovery Method"
to={'/'}
arrowRight={true}
style={{ marginBottom: '50px' }}
style={{ marginBottom: '50px', fontWeight: 'bold', fontSize: '15px' }}
/>
</YStack>
)

View File

@ -22,7 +22,7 @@ const SetupRow = ({ title }: SetupRowProps) => {
return (
<YStack space={'$4'}>
<Text size={27} weight={'semibold'}>
<Text size={19} weight={'semibold'}>
{title}
</Text>
<XStack justifyContent={'space-between'} width={'80%'}>
@ -38,7 +38,7 @@ const SetupRow = ({ title }: SetupRowProps) => {
/>
</Stack>
<YStack space={'$2'}>
<Text size={19} weight={'semibold'} color="#09101C">
<Text size={15} weight={'semibold'} color="#09101C">
ETH
</Text>
<Text size={27} weight={'semibold'} color="#09101C">
@ -47,7 +47,7 @@ const SetupRow = ({ title }: SetupRowProps) => {
</YStack>
<YStack space={'$2'}>
<XStack style={{ justifyContent: 'space-between' }}>
<Text size={19} weight={'semibold'} color="#09101C">
<Text size={15} weight={'semibold'} color="#09101C">
USD
</Text>
<ChevronDownIcon size={16} color={'#919191'} />

View File

@ -20,7 +20,7 @@ const WithdrawalAddress = ({ title }: WithdrawalAddressProps) => {
return (
<YStack space={'$4'}>
<Text size={27} weight={'semibold'}>
<Text size={19} weight={'semibold'}>
{title}
</Text>
<YStack space={'$3'}>

View File

@ -1,4 +1,4 @@
import { Stack, XStack } from 'tamagui'
import { Stack, YStack } from 'tamagui'
import { Button, InformationBox } from '@status-im/components'
import { CloseCircleIcon } from '@status-im/icons'
import { useDispatch, useSelector } from 'react-redux'
@ -43,7 +43,7 @@ const ContinueButton = ({
const isActivationValScreen = activeStep === 3 && subStepValidatorSetup === 3
return (
<XStack style={{ width: '100%', alignItems: 'center', zIndex: 999, marginTop: '40px' }}>
<YStack style={{ width: '100%', alignItems: 'center', zIndex: 999, marginTop: '30px' }}>
<Stack style={{ width: '100%' }}>
{isCopyPastedPhrase && (
<InformationBox
@ -66,14 +66,13 @@ const ContinueButton = ({
width: '100%',
zIndex: 999,
alignItems: 'end',
position: 'absolute',
}}
>
<Button onPress={continueHandler} size={40} disabled={isDisabled()}>
{activeStep < 5 ? 'Continue' : 'Continue to Dashboard'}
</Button>
</Stack>
</XStack>
</YStack>
)
}

View File

@ -1,7 +1,23 @@
.custom-step {
background-color: #ffffff;
}
div[class*='StepMain-'] {
align-items: start;
}
div[class*='LabelContainer-'] span[class*='Label-'] {
text-align: initial;
text-transform: uppercase;
font-family: 'Inter', sans-serif;
}
div[class*='ConnectorContainer-'] {
right: unset;
left: calc(-100% + 28px);
width: 100%;
}
span[class*='Connector-'] {
position: relative;
z-index: -1;
}
.custom-step.StepMain--active,
.custom-step.StepMain--completed {
background-color: #2a4cf4;
@ -37,32 +53,9 @@
content: attr(data-subtitle);
position: absolute;
top: calc(100% + 4px);
left: 8px;
font-size: 12px;
color: #A2A9B0;
background: transparent;
border: none;
font-family: 'Inter', sans-serif;
color: #a2a9b0;
width: max-content;
}
[data-step="Overview"]::after {
left: 35%;
}
[data-step="Advisories"]::after {
left: 30%;
}
[data-step="Client Setup"]::after {
left: 32%;
}
[data-step="Validator Setup"]::after {
left: 25%;
}
[data-step="Key Generation"]::after {
left: 24.5%;
}
[data-step="Activation"]::after {
left: 33%;
}

View File

@ -22,7 +22,14 @@ const FormStepper = ({ activeStep, changeActiveStep }: FormStepperProps) => {
nonLinear={true}
styleConfig={stepStyle}
connectorStyleConfig={customConnectorStyle}
style={{ fontSize: '14px', zIndex: 999, width: '100%', padding: 0, marginBottom: '2rem' }}
style={{
fontSize: '14px',
zIndex: 999,
width: '100%',
padding: 0,
marginBottom: '3rem',
fontFamily: 'Inter',
}}
>
{steps.map((step, index) => (
<Step

View File

@ -30,6 +30,8 @@
background-color: #f7f8f9;
width: 100%;
font-size: 16px;
color: rgb(13, 22, 37);
font-family: 'Inter', sans-serif;
}
.input-wrapper {
@ -40,9 +42,9 @@
position: absolute;
left: 15px;
transform: translateY(-50%);
pointer-events: none;
pointer-events: none;
z-index: 2;
color: #0D162566;
color: #0d162566;
top: 48%;
}
@ -76,4 +78,4 @@
.suggestion-list::-webkit-scrollbar-thumb:hover {
background-color: transparent;
}
}

View File

@ -12,7 +12,7 @@ const ConfirmRecoveryPhrase = () => {
return (
<YStack space={'$3'} style={{ width: '100%', marginTop: '20px' }}>
<KeyGenerationTitle />
<Text size={27}>Confirm Recovery Phrase</Text>
<Text size={19}>Confirm Recovery Phrase</Text>
<Stack
style={{
display: 'grid',

View File

@ -1,4 +1,4 @@
import { Text as TextTam, XStack, YStack } from 'tamagui'
import { XStack, YStack } from 'tamagui'
import { Text } from '@status-im/components'
import OverviewCard from './OverviewCard'
@ -7,29 +7,37 @@ import LinkWithArrow from '../../../components/General/LinkWithArrow'
const Overview = () => {
return (
<>
<YStack className="layout-left" space={'$5'} style={{ padding: '16px 32px' }}>
<TextTam fontSize={27} fontWeight={'600'}>
Overview
</TextTam>
<Text size={27}>
Becoming a validator is a big responsibility with important preparation steps. Only start
the deposit process when youre ready.
</Text>
<Text size={15} color="#939BA1">
By running a validator, you'll be responsible for securing the network and receive
continuous payouts for actions that help the network reach consensus.
</Text>
<Text size={15} color="#939BA1">
Since the successful transition to proof-of-stake via The Merge, Ethereum is fully secured
by proof-of-stake validators. By running a validator, you'll be helping to secure the
Ethereum network.
</Text>
<LinkWithArrow
text="Learn more"
to={'/'}
arrowRight={true}
style={{ marginBottom: '1%' }}
/>
<YStack
className="layout-left"
space={'$5'}
style={{ padding: '26px 0 32px 32px' }}
minHeight={'65vh'}
justifyContent={'space-between'}
>
<YStack space={'$5'}>
<Text size={27} weight={'semibold'}>
Overview
</Text>
<Text size={19}>
Becoming a validator is a big responsibility with important preparation steps. Only
start the deposit process when you're ready.
</Text>
<Text size={15} color="#939BA1">
By running a validator, you'll be responsible for securing the network and receive
continuous payouts for actions that help the network reach consensus.
</Text>
<Text size={15} color="#939BA1">
Since the successful transition to proof-of-stake via The Merge, Ethereum is fully
secured by proof-of-stake validators. By running a validator, you'll be helping to
secure the Ethereum network.
</Text>
<LinkWithArrow
text="Learn More"
to={'/'}
arrowRight={true}
style={{ marginBottom: '1%', fontSize: '13px' }}
/>
</YStack>
<XStack space={'$3'}>
<OverviewCard text={'Current APR'} value={'4.40%'} />
<OverviewCard text={'Total ETH Staked'} value={'9,451,123'} />

View File

@ -12,12 +12,12 @@ const OverviewCard = ({ text, value }: OverviewCardProps) => {
style={{
borderRadius: '16px',
border: '1px solid rgba(0, 0, 0, 0.15)',
width: '46%',
width: '44%',
padding: '12px 16px',
backgroundColor: '#FFF',
}}
>
<Text size={19} weight={'semibold'}>
<Text size={15} weight={'semibold'}>
{text}
</Text>
<Text size={27} color="blue" weight={'semibold'}>

View File

@ -82,8 +82,8 @@ const ValidatorOnboarding = () => {
<YStack
style={{
width: '100%',
margin: '0 auto',
padding: '2% 10% 2%',
maxWidth: '1100px',
margin: '4rem auto 2rem',
justifyContent: 'start',
alignItems: 'start',
}}

View File

@ -18,10 +18,10 @@ const ConsensusClientCard = ({ name, icon }: ConsensusClientCardProps) => {
padding: '12px 16px',
width: '29%',
}}
space={'$10'}
space={'$8'}
>
<Stack>
<Text size={27} weight={'semibold'}>
<Text size={15} weight={'semibold'}>
{name}
</Text>
</Stack>

View File

@ -18,6 +18,9 @@ type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
color: 'orange', synced: 140000, total: 200000, title: 'Synced Files'
color: 'orange',
synced: 140000,
total: 200000,
title: 'Synced Files',
},
}

View File

@ -32,29 +32,29 @@ const ConsensusSelection = () => {
]
return (
<YStack style={{ width: '100%', padding: '32px' }}>
<XStack justifyContent={'space-between'}>
<YStack style={{ width: '100%', padding: '32px' }} minHeight={'65vh'}>
<XStack justifyContent={'space-between'} alignItems={'center'} mb={'30px'}>
<Text size={27} weight={'semibold'}>
Validator Setup
</Text>
<XStack space={'$2'}>
<PairedDeviceCard isVisibleState={true} />
<ConsensusGaugeCard
color="blue"
synced={134879}
title="Execution Sync Status"
total={150000}
/>
<PairedDeviceCard isVisibleState={true} />
</XStack>
</XStack>
<YStack>
<YStack mb={'50px'}>
<Stack style={{ marginBottom: '4px' }}>
<Text size={13} color="#647084">
Consensus Client Detection
</Text>
</Stack>
<Text size={15} weight={'regular'}>
<Text size={15}>
No existing execution client installations have been detected on paired device.
</Text>
<Text size={13} color="#828282">
@ -63,14 +63,14 @@ const ConsensusSelection = () => {
</Text>
</YStack>
<TextTam fontSize={27} style={{ margin: '5px', marginLeft: 0, marginTop: '50px' }}>
<TextTam fontSize={19} style={{ marginLeft: 0, marginTop: '50px', marginBottom: '25px' }}>
Install Consensus client
</TextTam>
<XStack space={'$8'}>
<ConsensusClientCard name={clients[0].name} icon={clients[0].icon} />
<YStack width={'67%'} space={'$4'}>
<Text size={27}>The resource efficient Ethereum Clients.</Text>
<Text size={19}>The resource efficient Ethereum Clients.</Text>
<Text size={15}>
{selectedClient} is a client implementation for both execution and consensus layers that
strives to be as lightweight as possible in terms of resources used. This allows it to
@ -81,6 +81,7 @@ const ConsensusSelection = () => {
<LinkWithArrow
textColor="black"
text="Nimbus Documentation"
style={{ fontSize: '15px' }}
arrowRight={true}
to="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
/>

View File

@ -20,9 +20,9 @@ const PairedDeviceCard = ({ isVisibleState }: PairedDeviceCardProps) => {
<XStack
space={'$7'}
style={{
padding: '2px 6px',
padding: '6px 12px',
border: '1px solid #DCE0E5',
borderRadius: '15px',
borderRadius: '10px',
}}
alignItems={'center'}
>

View File

@ -4,16 +4,16 @@ import ActivationValidatorSetup from './ActivationValidatorSetup'
import { withRouter } from 'storybook-addon-react-router-v6'
const meta = {
title: 'ValidatorOnboarding/ActivationValidatorSetup',
component: ActivationValidatorSetup,
title: 'ValidatorOnboarding/ActivationValidatorSetup',
component: ActivationValidatorSetup,
tags: ['autodocs'],
decorators: [withRouter()],
tags: ['autodocs'],
decorators: [withRouter()],
} satisfies Meta<typeof ActivationValidatorSetup>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: { },
args: {},
}

View File

@ -19,21 +19,21 @@ const ActivationValidatorSetup = () => {
}, [])
return (
<Stack style={styles.confettiContainer} width={'100%'}>
<Stack style={styles.confettiContainer} width={'100%'} minHeight={'65vh'}>
{showConfetti && <Confetti style={styles.confettiCanvas} />}
<YStack style={{ padding: '16px 32px' }}>
<YStack space={'$3'}>
<YStack style={{ padding: '26px 32px' }}>
<YStack space={'$5'}>
<Text size={27} weight={'semibold'}>
Activation
</Text>
<Stack>
<Text size={27}>
<Text size={19}>
Congratulations! You have successfully setup your Execution and Consensus clients and
are currently syncing your nodes. You need to be sufficiently synced prior to setting
up your validators and making a deposit.
</Text>
</Stack>
<YStack space={'$3'} marginTop={'10px'} width={'33%'}>
<YStack space={'$3'} marginTop={'25px'} width={'33%'}>
<XStack width={'151%'} space={'$3'}>
<ActivationCard
text="Execution Sync Status"

View File

@ -18,13 +18,13 @@ const OsCard = ({ name, icon, onClick, isSelected }: OsCardProps) => {
border: isSelected ? '1px solid #2A4AF566' : '1px solid rgba(0, 0, 0, 0.15);',
borderRadius: '16px',
padding: '12px 16px',
width: '33%',
width: '32%',
}}
space={'$12'}
space={'$8'}
onPress={onClick}
>
<Stack>
<Text size={27} weight={'semibold'}>
<Text size={19} weight={'semibold'}>
{name}
</Text>
</Stack>

View File

@ -13,7 +13,7 @@ const ValidatorSetupInstall = () => {
const selectedClient = useSelector((state: RootState) => state.execClient.selectedClient)
return (
<YStack style={{ width: '100%', padding: '16px 32px' }}>
<YStack style={{ width: '100%', padding: '26px 32px' }}>
<XStack justifyContent={'space-between'} style={{ marginBottom: '10px' }}>
<Text size={27} weight={'semibold'}>
Validator Setup
@ -23,7 +23,7 @@ const ValidatorSetupInstall = () => {
<YStack>
<Stack style={{ marginBottom: '4px' }}>
<YStack space={'$3'}>
<Text size={19} weight={'semibold'}>
<Text size={15} weight={'semibold'}>
{selectedClient}
</Text>
@ -41,12 +41,12 @@ const ValidatorSetupInstall = () => {
style={{
border: '1px solid #00000026',
borderRadius: '16px',
padding: '6px 12px',
padding: '19px 16px',
}}
space={'$3'}
>
<Text size={27}> Installing {selectedClient}</Text>
<Text size={19} color="#647084" weight={'regular'}>
<Text size={19}> Installing {selectedClient}</Text>
<Text size={15} color="#647084" weight={'regular'}>
There are several ways to install Geth, including via a package manager, downloading
a pre-built bundle, running as a docker container or building from downloaded source
code. On this page the various installation options are explained for several major
@ -59,10 +59,10 @@ const ValidatorSetupInstall = () => {
are also provided in each section.
</Text>
<XStack justifyContent={'space-between'} space={'$2'} margin={'50px 0px'}>
<XStack justifyContent={'space-between'} my={'15px'}>
<OsCard
icon="/icons/MAC.png"
name="Mac"
name="MacOS"
isSelected={selectedOs === 'Mac'}
onClick={() => setSelectedOs('Mac')}
/>
@ -80,17 +80,17 @@ const ValidatorSetupInstall = () => {
/>
</XStack>
<YStack space={'$2'}>
<Text size={27}> Package Managers</Text>
<Text size={19}> Package Managers</Text>
<Text size={15} weight={'semibold'}>
MacOS via Homebrew
</Text>
<Text size={19} color="#647084" weight={'regular'}>
<Text size={15} color="#647084">
The easiest way to install go-ethereum is to use the Geth Homebrew tap. The first
step is to check that Homebrew is installed. The following command should return a
version number.
</Text>
<SyntaxHighlighterBox rows={['brew -v']} />
<Text size={19} color="#647084" weight={'regular'}>
<Text size={15} color="#647084">
If a version number is returned, then Homebrew is installed. If not, Homebrew can
be installed by following the instructions here. With Homebrew installed, the
following commands add the Geth tap and install Geth:

View File

@ -24,19 +24,19 @@ const ExecClientCard = ({ name, icon, isComingSoon }: ExecClientCardProps) => {
border: isComingSoon
? '1px solid #F5F6F8'
: isSelected
? '1px solid #2A4AF5'
: '1px solid #DCE0E5',
? '1px solid #2A4AF5'
: '1px solid #DCE0E5',
borderRadius: '16px',
padding: '12px 16px',
width: '19%',
}}
space={'$12'}
space={'$8'}
onClick={() => {
dispatch(selectClient(name))
}}
>
<Stack>
<Text size={27} weight={'semibold'} color={isComingSoon ? '#DCE0E5' : ''}>
<Text size={19} weight={'semibold'} color={isComingSoon ? '#DCE0E5' : ''}>
{name}
</Text>
{isComingSoon && (
@ -46,9 +46,9 @@ const ExecClientCard = ({ name, icon, isComingSoon }: ExecClientCardProps) => {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '3px',
padding: '3px 6px',
borderRadius: '67px',
width: '50%',
width: 'fit-content',
}}
>
<Text size={11} color="#fff">

View File

@ -7,8 +7,12 @@ import LinkWithArrow from '../../../../components/General/LinkWithArrow'
const ValidatorSetup = () => {
return (
<YStack style={{ width: '100%', padding: '16px 32px' }}>
<XStack justifyContent={'space-between'}>
<YStack
style={{ width: '100%', padding: '26px 32px' }}
minHeight={'65vh'}
justifyContent={'space-between'}
>
<XStack justifyContent={'space-between'} alignItems={'center'}>
<Text size={27} weight={'semibold'}>
Validator Setup
</Text>
@ -21,7 +25,7 @@ const ValidatorSetup = () => {
Execution Client Detection
</Text>
</Stack>
<Text size={15} weight={'regular'}>
<Text size={15}>
No existing execution client installations have been detected on paired device.
</Text>
<Text size={13} color="#828282">
@ -30,7 +34,7 @@ const ValidatorSetup = () => {
</Text>
</YStack>
<TextTam fontSize={27} style={{ margin: '5px', marginLeft: 0 }}>
<TextTam fontSize={27} style={{ marginTop: '15px', marginLeft: 0, marginBottom: '15px' }}>
Select Execution client
</TextTam>
<XStack justifyContent={'space-between'}>
@ -43,7 +47,7 @@ const ValidatorSetup = () => {
<LinkWithArrow
to="/"
text="View Execution client comparison chart"
style={{ marginTop: '3%' }}
style={{ marginTop: '6%', fontWeight: 'bold' }}
/>
</YStack>
)

View File

@ -8,11 +8,11 @@
.gradient-wrapper:after {
display: block;
content: "";
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(113, 64, 253, 0.075) 15%, rgba(255,255,255,0) 50%);
}
background: linear-gradient(to bottom, rgba(113, 64, 253, 0.075) 15%, rgba(255, 255, 255, 0) 50%);
}

View File

@ -0,0 +1,46 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface SidebarButton {
id: string
isDotOn: boolean
isSelected: boolean
isDisabled?: boolean
}
interface LeftSidebarState {
buttons: SidebarButton[]
}
const initialState: LeftSidebarState = {
buttons: [
{ id: 'dashboard', isDotOn: false, isSelected: true },
{ id: 'speed', isDotOn: false, isSelected: false },
{ id: 'chart', isDotOn: false, isSelected: false, isDisabled: true },
{ id: 'heart', isDotOn: false, isSelected: false },
{ id: 'codeBlock', isDotOn: false, isSelected: false },
{ id: 'communities', isDotOn: false, isSelected: false },
{ id: 'activityCenter', isDotOn: true, isSelected: false },
{ id: 'settings', isDotOn: false, isSelected: false },
],
}
const leftSidebarSlice = createSlice({
name: 'leftSidebar',
initialState,
reducers: {
toggleButtonSelection: (state, action: PayloadAction<string>) => {
state.buttons.forEach(button => {
button.isSelected = button.id === action.payload
})
},
toggleDot: (state, action: PayloadAction<string>) => {
const button = state.buttons.find(button => button.id === action.payload)
if (button) button.isDotOn = !button.isDotOn
},
},
})
export const { toggleButtonSelection, toggleDot } = leftSidebarSlice.actions
export default leftSidebarSlice.reducer

View File

@ -4,6 +4,7 @@ import pinnedMessageReducer from './PinnedMessage/slice'
import execClientReducer from './ValidatorOnboarding/ValidatorSetup/slice'
import themeReducer from './theme/slice'
import keyGenerationReducer from './ValidatorOnboarding/KeyGeneration/slice'
import leftSidebarReducer from './Sidebars/slice'
const store = configureStore({
reducer: {
@ -12,6 +13,7 @@ const store = configureStore({
execClient: execClientReducer,
theme: themeReducer,
keyGeneration: keyGenerationReducer,
leftSidebar: leftSidebarReducer,
},
})

View File

@ -26,13 +26,13 @@ export const convertSecondsToTimerFormat = (seconds: number) => {
}
export const formatNumbersWithComa = (n: number): string => {
const parts = n.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
};
const parts = n.toString().split('.')
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return parts.join('.')
}
export const getMonthIndicesFromRange = (range: DateRange) => {
if (!range.from || !range.to) return [0, 11]
return [range.from.getMonth(), range.to.getMonth()]
}
}