From 69cd939160b493649610ca2a1d562b5b42839cb2 Mon Sep 17 00:00:00 2001 From: Choco Date: Wed, 5 Jul 2023 13:52:30 +1000 Subject: [PATCH 01/87] Hookup apis --- frontend/package.json | 4 +- frontend/src/App.tsx | 34 +++- frontend/src/components/Blocks.tsx | 55 +++-- .../src/components/blocks-info/BlocksInfo.tsx | 190 +++--------------- .../blocks-info-item/BlocksInfoItem.tsx | 116 +++++++++++ .../src/components/blocks-info/constants.ts | 16 ++ .../master-node-title/MasterNodeTitle.tsx | 28 +++ frontend/src/components/images/BlockImage.tsx | 16 +- .../components/images/block-image.module.scss | 8 + .../components/infinite-list/InfiniteList.tsx | 4 +- .../src/components/info-cards/InfoCards.tsx | 147 +++++++------- .../src/components/info-list/InfoList.tsx | 4 +- frontend/src/constants/urls.ts | 1 + frontend/src/contexts/timeContext.tsx | 11 + frontend/src/main.tsx | 30 +++ frontend/src/pages/Home.tsx | 4 +- frontend/src/utils/formatter.ts | 6 + 17 files changed, 392 insertions(+), 282 deletions(-) create mode 100644 frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx create mode 100644 frontend/src/components/blocks-info/constants.ts create mode 100644 frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx create mode 100644 frontend/src/constants/urls.ts create mode 100644 frontend/src/contexts/timeContext.tsx create mode 100644 frontend/src/utils/formatter.ts diff --git a/frontend/package.json b/frontend/package.json index 713e407..24dd2af 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "cryptoexplorer", + "name": "crypto-explorer", "private": true, "version": "0.0.0", "type": "module", @@ -32,4 +32,4 @@ "typescript": "^5.0.2", "vite": "^4.3.9" } -} +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 397e9db..b16b773 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,22 +1,38 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Outlet } from 'react-router-dom'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; import { ThemeContext } from '@/contexts/themeContext'; +import { TimeContext } from '@/contexts/timeContext'; + +function getUnixTime() { + return Math.floor(Date.now() / 1000); +} function App() { const [theme, setTheme] = useState('light'); + const [currentUnixTime, setCurrentUnixTime] = useState(getUnixTime()); + + useEffect(() => { + const intervalId = setInterval(() => { + setCurrentUnixTime(getUnixTime()); + }, 5 * 1000); + + return () => clearInterval(intervalId); + }, []); return ( - -
-
-
+ + +
+
+
+
); } diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 07f6176..41b221b 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -1,49 +1,70 @@ -import { Fragment, useEffect, useRef, useState } from 'react'; +import axios from 'axios'; +import { Fragment, useContext, useEffect, useRef, useState } from 'react'; import BlockConnectLine from '@/components/BlockConnectLine'; import BlockImage from '@/components/images/BlockImage'; +import { baseUrl } from '@/constants/urls'; +import { TimeContext } from '@/contexts/timeContext'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; import styles from './blocks.module.scss'; +import { useLoaderData } from 'react-router'; export interface Block { - id: number; + number: number; confirmed: boolean; } const addBlockPerClick = 3; -function getInitData(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { - const initData = []; +function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { + const blocks = []; // confirmed blocks for (let blockHeight = lastBlock - blockNumber + 1; blockHeight <= lastConfirmedBlock; blockHeight++) { - initData.push({ id: blockHeight, confirmed: true }); + blocks.push({ number: blockHeight, confirmed: true }); } // unconfirmed blocks for (let blockHeight = lastConfirmedBlock + 1; blockHeight <= lastBlock; blockHeight++) { - initData.push({ id: blockHeight, confirmed: false }); + blocks.push({ number: blockHeight, confirmed: false }); } - return initData; + return blocks; } export default function Blocks() { const isDesktopL = useIsDesktopL(); // use 13 blocks(desktop), otherwise use 20 blocks(XL desktop) const blockNumber = isDesktopL ? 20 : 13; + const loaderData: any = useLoaderData(); - const [lastBlock, setLastBlock] = useState(12010); - const [lastConfirmedBlock, setLastConfirmedBlock] = useState(12007); - const [blocks, setBlocks] = useState(getInitData(lastBlock, lastConfirmedBlock, blockNumber)); + const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); + const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); + const [blocks, setBlocks] = useState(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); const initialLastBlock = useRef(null); + const { currentUnixTime } = useContext(TimeContext); useEffect(() => { if (initialLastBlock.current === null) { - initialLastBlock.current = 12010; + initialLastBlock.current = loaderData.blocks.latestMinedBlock.number; } - }); + }, []); + + // Move this up and send down to two blocks animations + useEffect(() => { + async function getData() { + const { data: { latestMinedBlock, latestSubnetCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); + setLastBlock(latestMinedBlock.number); + setLastConfirmedBlock(latestSubnetCommittedBlock.number); + + const newBlockNumber = latestMinedBlock.number - (initialLastBlock.current ?? 0) + blockNumber; + const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); + setBlocks(blocks); + } + + getData(); + }, [currentUnixTime]); function confirmBlocksExceptLastTwo() { const newBlocks = blocks.map((block, index) => { @@ -67,7 +88,7 @@ export default function Blocks() { function addBlock() { const newBlocks = []; for (let i = 1; i <= addBlockPerClick; i++) { - newBlocks.push({ id: (lastBlock + i), confirmed: false }); + newBlocks.push({ number: (lastBlock + i), confirmed: false }); } setBlocks([ @@ -93,13 +114,13 @@ export default function Blocks() {
{ blocks.map((block, index) => { - const isFirstVisibleConfirmed = block.id === (lastBlock - blockNumber + 1); - const isLastConfirmed = block.id === lastConfirmedBlock; - const isFirstUnConfirmed = block.id === (lastConfirmedBlock + 1); + const isFirstVisibleConfirmed = block.number === (lastBlock - blockNumber + 1); + const isLastConfirmed = block.number === lastConfirmedBlock; + const isFirstUnConfirmed = block.number === (lastConfirmedBlock + 1); const isLast = index === blocks.length - 1; return ( - + ; @@ -56,57 +41,7 @@ export default function BlocksInfo({ title, data, fetchMoreData }: BlocksInfoPro ); } -interface MasterNodeTitleProps { - title: string; -} - -function MasterNodeTitle({ title }: MasterNodeTitleProps) { - return ( -
- - <div className='flex flex-col dark:text-text-dark-400'> - <div className='flex'> - <Svg svgName={SvgNames.Miner} /> - <span className="pl-2.5">Miner</span> - </div> - <div className='pt-2.5 flex'> - <Svg svgName={SvgNames.Penalty} /> - <span className="pl-2.5">Penalty</span> - </div> - <div className='pt-2.5 flex'> - <Svg svgName={SvgNames.Standby} /> - <span className="pl-2.5">Standby</span> - </div> - </div> - </div> - ); -} - -interface BlocksInfoItemsProps { - data: BlocksInfoItem[]; -} - -function BlocksInfoItems({ data }: BlocksInfoItemsProps) { - function getKey(d: BlocksInfoItem) { - if (d.type === 'recent-block') { - return d.height; - } - - return d.number; - } - - return ( - <> - { - data.map((d, index) => ( - <div className={`flex border-b-2 border-text-white-400 dark:border-opacity-40 dark:border-text-dark-400 ${index === 0 ? 'border-t-2' : ''}`} key={getKey(d)}> - <BlocksInfoItem {...d} /> - </div> - )) - } - </> - ); -} +type ItemTypes = 'recent-block' | 'master-node'; interface BlocksInfoHeadingProps { type: ItemTypes; @@ -115,7 +50,7 @@ interface BlocksInfoHeadingProps { function BlocksInfoHeading({ type }: BlocksInfoHeadingProps) { if (type === 'recent-block') { return ( - <div className='flex dark:bg-bg-dark-800 sticky top-0'> + <div className='flex dark:bg-bg-dark-800 bg-white sticky top-0'> <BlockCell className={cellWith.recentBlocks.height}>Height</BlockCell> <BlockCell className={cellWith.recentBlocks.hash}>Hash</BlockCell> <BlockCell className={cellWith.recentBlocks.proposedBy}>Proposed By</BlockCell> @@ -136,102 +71,25 @@ function BlocksInfoHeading({ type }: BlocksInfoHeadingProps) { ); } -export type BlocksInfoItem = RecentBlock | MasterNode; -type BlocksInfoItemProps = BlocksInfoItem; - -type ItemTypes = 'recent-block' | 'master-node'; - -interface RecentBlock { - type: 'recent-block'; - height: number; - hash: string; - proposedBy: string; - subnetConfirmed: boolean; - parentConfirmed: boolean; - time: number; -} - -interface MasterNode { - type: 'master-node'; - number: number; - account: string; - role: MasterNodeRoles; - activity: boolean; - latestParticipateBlock: number; -} - -type MasterNodeRoles = 'miner' | 'standby' | 'penalty'; - -function BlocksInfoItem(data: BlocksInfoItemProps) { - if (data.type === 'recent-block') { - return ( - <div className='flex'> - <BlockCell className={cellWith.recentBlocks.height}>{data.height}</BlockCell> - <BlockCell className={cellWith.recentBlocks.hash}>{data.hash}</BlockCell> - <BlockCell className={cellWith.recentBlocks.proposedBy}>{data.proposedBy}</BlockCell> - <BlockImageCell className={cellWith.recentBlocks.status}> - <BlockConfirmStatus subnetConfirmed={data.subnetConfirmed} parentConfirmed={data.parentConfirmed} /> - </BlockImageCell> - <BlockCell className={cellWith.recentBlocks.time}>{data.time}s</BlockCell> - </div> - ); - } - - return ( - <div className='flex'> - <BlockCell className={cellWith.masterNodes.number}>{data.number}</BlockCell> - <BlockCell className={cellWith.masterNodes.account}>{data.account}</BlockCell> - <BlockImageCell className={cellWith.masterNodes.role}><MasterNodeRole role={data.role} /></BlockImageCell> - {/* <BlockCell className={cellWith.masterNodes.activity}>{data.activity ? 'Active' : 'Inactive'}</BlockCell> - <BlockCell className={cellWith.masterNodes.lastedParticipatedBlock}>{data.latestParticipateBlock}</BlockCell> */} - </div> - ); - - return <>type not found</>; -} - -interface BlockCellProps extends PropsWithChildren { - className: string; -} - -function BlockCell({ className, children }: BlockCellProps) { - return ( - <div className={`px-2 py-2.5 leading-tight ${className}`}>{children}</div> - ); -} - -function BlockImageCell({ className, children }: BlockCellProps) { - return ( - <div className={`flex items-center px-2 py-[7px] leading-tight ${className}`}>{children}</div> - ); -} - -interface BlockConfirmStatusProps { - subnetConfirmed: boolean; - parentConfirmed: boolean; +interface BlocksInfoItemsProps { + data: BlocksInfoItem[]; } -function BlockConfirmStatus({ subnetConfirmed, parentConfirmed }: BlockConfirmStatusProps) { +function BlocksInfoItems({ data }: BlocksInfoItemsProps) { return ( - <div className='flex items-center'> - {subnetConfirmed ? <Svg svgName={SvgNames.Check} /> : <Svg svgName={SvgNames.Cross} />} - / - {parentConfirmed ? <Svg svgName={SvgNames.Check} /> : <Svg svgName={SvgNames.Cross} />} - </div> + <> + { + data.map((d, index) => ( + <div + className={`flex border-b-2 border-text-white-400 dark:border-opacity-40 dark:border-text-dark-400 + ${index === 0 ? 'border-t-2' : ''}` + } + key={d.number} + > + <BlocksInfoItem {...d} /> + </div> + )) + } + </> ); } - -interface MasterNodeRoleProps { - role: MasterNodeRoles; -} - -function MasterNodeRole({ role }: MasterNodeRoleProps) { - if (role === 'standby') { - return <Svg svgName={SvgNames.Standby} />; - } - else if (role === 'penalty') { - return <Svg svgName={SvgNames.Penalty} />; - } - - return <Svg svgName={SvgNames.Miner} />; -} diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx new file mode 100644 index 0000000..fd54934 --- /dev/null +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -0,0 +1,116 @@ +import { PropsWithChildren, useContext } from 'react'; + +import { cellWith } from '@/components/blocks-info/constants'; +import Svg, { SvgNames } from '@/components/images/Svg'; +import { formatHash } from '@/utils/formatter'; +import { TimeContext } from '@/contexts/timeContext'; + +export type BlocksInfoItem = RecentBlock | MasterNode; +type BlocksInfoItemProps = BlocksInfoItem; + +interface RecentBlock { + type: 'recent-block'; + number: number; + hash: string; + miner: string; + committedInSubnet: boolean; + committedInParentChain: boolean; + timestamp: number; +} + +interface MasterNode { + type: 'master-node'; + number: number; + account: string; + role: MasterNodeRoles; + activity: boolean; + latestParticipateBlock: number; +} + +type MasterNodeRoles = 'master-node' | 'candidate' | 'penalty'; + +export function BlocksInfoItem(data: BlocksInfoItemProps) { + const { currentUnixTime } = useContext(TimeContext); + + function getTimeDiff(timestamp: number): string { + const timeDiff = Math.floor(currentUnixTime - timestamp); + if (timeDiff < 60) { + return `${timeDiff}s`; + } else if (timeDiff < 60 * 60) { + return `${Math.floor(timeDiff / 60)}m`; + } + + return '>1hr'; + } + + if (data.type === 'recent-block') { + + return ( + <div className='flex'> + <BlockCell className={cellWith.recentBlocks.height}>{data.number}</BlockCell> + <BlockCell className={cellWith.recentBlocks.hash}>{formatHash(data.hash)}</BlockCell> + <BlockCell className={cellWith.recentBlocks.proposedBy}>{formatHash(data.miner)}</BlockCell> + <BlockImageCell className={cellWith.recentBlocks.status}> + <BlockConfirmStatus committedInSubnet={data.committedInSubnet} committedInParentChain={data.committedInParentChain} /> + </BlockImageCell> + <BlockCell className={cellWith.recentBlocks.time}>{getTimeDiff(data.timestamp)}</BlockCell> + </div> + ); + } + + return ( + <div className='flex'> + <BlockCell className={cellWith.masterNodes.number}>{data.number}</BlockCell> + <BlockCell className={cellWith.masterNodes.account}>{data.account}</BlockCell> + <BlockImageCell className={cellWith.masterNodes.role}><MasterNodeRole role={data.role} /></BlockImageCell> + {/* <BlockCell className={cellWith.masterNodes.activity}>{data.activity ? 'Active' : 'Inactive'}</BlockCell> + <BlockCell className={cellWith.masterNodes.lastedParticipatedBlock}>{data.latestParticipateBlock}</BlockCell> */} + </div> + ); +} + +interface BlockCellProps extends PropsWithChildren { + className: string; +} + +export function BlockCell({ className, children }: BlockCellProps) { + return ( + <div className={`px-2 py-2.5 leading-tight ${className}`}>{children}</div> + ); +} + +export function BlockImageCell({ className, children }: BlockCellProps) { + return ( + <div className={`flex items-center px-2 py-[7px] leading-tight ${className}`}>{children}</div> + ); +} + +interface BlockConfirmStatusProps { + committedInSubnet: boolean; + committedInParentChain: boolean; +} + +function BlockConfirmStatus({ committedInSubnet, committedInParentChain }: BlockConfirmStatusProps) { + return ( + <div className='flex items-center'> + {committedInSubnet ? <Svg svgName={SvgNames.Check} /> : <Svg svgName={SvgNames.Cross} />} + / + {committedInParentChain ? <Svg svgName={SvgNames.Check} /> : <Svg svgName={SvgNames.Cross} />} + </div> + ); +} + +interface MasterNodeRoleProps { + role: MasterNodeRoles; +} + +function MasterNodeRole({ role }: MasterNodeRoleProps) { + if (role === 'candidate') { + return <Svg svgName={SvgNames.Standby} />; + } + else if (role === 'penalty') { + return <Svg svgName={SvgNames.Penalty} />; + } + + return <Svg svgName={SvgNames.Miner} />; +} \ No newline at end of file diff --git a/frontend/src/components/blocks-info/constants.ts b/frontend/src/components/blocks-info/constants.ts new file mode 100644 index 0000000..9173e98 --- /dev/null +++ b/frontend/src/components/blocks-info/constants.ts @@ -0,0 +1,16 @@ +export const cellWith = { + recentBlocks: { + 'height': 'w-[85px]', + 'hash': 'w-[144px]', + 'proposedBy': 'w-[144px]', + 'status': 'w-[75px]', + 'time': 'w-[50px]', + }, + masterNodes: { + 'number': 'w-[64px]', + 'account': 'w-[144px]', + 'role': 'w-[290px]', + 'activity': 'w-[100px]', + 'lastedParticipatedBlock': 'w-[144px]', + } +}; \ No newline at end of file diff --git a/frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx b/frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx new file mode 100644 index 0000000..299e904 --- /dev/null +++ b/frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx @@ -0,0 +1,28 @@ +import Svg, { SvgNames } from '@/components/images/Svg'; +import Title from '@/components/title/Title'; + +interface MasterNodeTitleProps { + title: string; +} + +export function MasterNodeTitle({ title }: MasterNodeTitleProps) { + return ( + <div className='flex justify-between'> + <Title title={title} /> + <div className='flex flex-col dark:text-text-dark-400'> + <div className='flex'> + <Svg svgName={SvgNames.Miner} /> + <span className="pl-2.5">Master Node</span> + </div> + <div className='pt-2.5 flex'> + <Svg svgName={SvgNames.Penalty} /> + <span className="pl-2.5">Penalty</span> + </div> + <div className='pt-2.5 flex'> + <Svg svgName={SvgNames.Standby} /> + <span className="pl-2.5">Candidate</span> + </div> + </div> + </div> + ); +} diff --git a/frontend/src/components/images/BlockImage.tsx b/frontend/src/components/images/BlockImage.tsx index 898f4f2..c157a2c 100644 --- a/frontend/src/components/images/BlockImage.tsx +++ b/frontend/src/components/images/BlockImage.tsx @@ -2,8 +2,6 @@ import { useContext } from 'react'; import BlueBlock from '@/assets/blocks/blue-block.svg'; import GreyBlock from '@/assets/blocks/grey-block.svg'; -import DarkGreyBlock from '@/assets/blocks/dark-grey-block.png'; -import DarkBlueBlock from '@/assets/blocks/dark-blue-block.png'; import { Block } from '@/components/Blocks'; import { ThemeContext } from '@/contexts/themeContext'; @@ -26,14 +24,8 @@ export default function BlockImage(props: BlockImageProps) { <div className='shrink-0 relative w-[35px] h-[37.82px] text-lg leading-none'> {isDarkMode ? ( <> - <div - className={`z-30 absolute left-0 top-0 ${styles.darkBlock} ${styles.darkGreyBlock}`} - style={{ backgroundImage: `url(${DarkGreyBlock})` }} - /> - <div - className={`z-40 absolute left-0 top-0 ${styles.darkBlock} ${styles.darkBlueBlock} ${styles.animate} ${block.confirmed ? styles.show : styles.hide}`} - style={{ backgroundImage: `url(${DarkBlueBlock})` }} - /> + <div className={`z-30 absolute left-0 top-0 ${styles.darkBlock} ${styles.darkGreyBlock}`} /> + <div className={`z-40 absolute left-0 top-0 ${styles.darkBlock} ${styles.darkBlueBlock} ${styles.animate} ${block.confirmed ? styles.show : styles.hide}`} /> </> ) : ( <> @@ -77,14 +69,14 @@ function BlockNumber({ block, isLastConfirmed, isLast }: BlockImageProps) { return ( <div className="absolute top-[64px] right-0 text-primary flex"> <div>Block</div> - <div className='pl-1'>{block.id}</div> + <div className='pl-1'>{block.number}</div> </div> ); } else if (isLast) { return ( <div className="absolute top-[64px] right-0 text-primary flex"> <div>Block</div> - <div className='pl-1'>{block.id}</div> + <div className='pl-1'>{block.number}</div> </div> ); } diff --git a/frontend/src/components/images/block-image.module.scss b/frontend/src/components/images/block-image.module.scss index 22717c9..c5ac6d1 100644 --- a/frontend/src/components/images/block-image.module.scss +++ b/frontend/src/components/images/block-image.module.scss @@ -17,3 +17,11 @@ background-size: cover; margin: 0 -8px; } + +.darkBlueBlock { + background-image: url('src/assets/blocks/dark-blue-block.png'); +} + +.darkGreyBlock { + background-image: url('src/assets/blocks/dark-grey-block.png'); +} diff --git a/frontend/src/components/infinite-list/InfiniteList.tsx b/frontend/src/components/infinite-list/InfiniteList.tsx index 0235fb2..c9affcf 100644 --- a/frontend/src/components/infinite-list/InfiniteList.tsx +++ b/frontend/src/components/infinite-list/InfiniteList.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren, useEffect, useRef } from 'react'; -import { BlocksInfoItem } from '@/components/blocks-info/BlocksInfo'; +import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; interface InfiniteListProps extends PropsWithChildren { data: BlocksInfoItem[]; @@ -38,7 +38,7 @@ export default function InfiniteList({ fetchData, children }: InfiniteListProps) {children} <div ref={observerTarget}></div> {/* An extra div is essential for infinitely scrolling */} - <div>End of list</div> + <div className='dark:text-bg-dark-800 text-white'>End of list</div> </ > ); } \ No newline at end of file diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 3fd5ace..2f39dd2 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -1,116 +1,123 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; +import { useLoaderData } from 'react-router-dom'; -import BlocksInfo, { BlocksInfoItem } from '@/components/blocks-info/BlocksInfo'; +import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; +import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; -import InfoList from '@/components/info-list/InfoList'; +import InfoList, { InfoListHealth } from '@/components/info-list/InfoList'; +import { formatHash } from '@/utils/formatter'; -const mockDataItem: BlocksInfoItem = { - type: 'recent-block', - height: 10000001, - hash: '0xdFrsdf...Dsa31ld7', - proposedBy: '0xdFrsdf...Dsa31ld7', - subnetConfirmed: true, - parentConfirmed: false, - time: 2 -}; +export default function InfoCards() { + const loaderData: any = useLoaderData(); + const { network, relayer, masterNodes, blocks } = loaderData; -const mockMasterNodeItem: BlocksInfoItem = { - type: 'master-node', - number: 1, - account: '0xdFrsdf...Dsa31ld7', - role: 'miner', - activity: true, - latestParticipateBlock: 10000001 -}; + const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); -const mockData = Array(20).fill('').map((_v, i) => { - return { ...mockDataItem, height: mockDataItem.height + i }; -}); + function getNetworkStatus(): InfoListHealth { + if (network.health.status === true) { + return 'Normal'; + } -const mockMasterNodes = Array(7).fill('').map<BlocksInfoItem>((_v, i) => { - if (i === 3) { - return { ...mockMasterNodeItem, role: 'penalty', number: (mockMasterNodeItem).number + i }; + return 'Abnormal'; } - else if (i === 5) { - return { ...mockMasterNodeItem, role: 'standby', number: (mockMasterNodeItem).number + i }; + + function getRelayerStatus(): InfoListHealth { + if (relayer.health.status === 'UP') { + return 'Normal'; + } + + return 'Abnormal'; } - return { ...mockMasterNodeItem, role: 'miner', number: (mockMasterNodeItem).number + i }; -}); -export default function InfoCards() { - const [blocksInfo, setBlocksInfo] = useState<BlocksInfoItem[]>([]); + function getInitRecentBlocks() { + return blocks.blocks.sort((a: any, b: any) => b.number - a.number).map((block: any) => ({ + type: 'recent-block', + ...block + })); + } - const mockInfo = { - info1: [ - { name: 'Block Time', value: '2s' }, - { name: 'TX Throughput', value: '10 txs/s' }, - { name: 'Checkpointed to', value: 'XDC Devnet' }, - ], - info2: [ - { name: 'Smart Contract', value: 'Shorten Hash' }, - { name: 'Backlog', value: '10 Subnet Headers' }, - { name: 'Ave. tx fee', value: '0.001XDC/hour' }, - { name: 'Remaining Balance', value: '10XDC\two weeks' }, - ], - info3: [ - { name: 'Current committee size', value: '30' }, - { name: 'Activity', value: '0xdFrsdf...Dsa31ld7' }, - { name: 'Number of stanby nodes', value: '10' }, - ], + const mappedInfo = { + // `recentBlocks` is handled by a state since it would get updated when load more page + network: { + health: getNetworkStatus(), + data: [ + { name: 'Block Time', value: `${network.subnet.block.averageBlockTime}s` }, + { name: 'TX Throughput', value: `${Math.round(network.subnet.block.txThroughput * 100) / 100} txs/s` }, + { name: 'Checkpointed to', value: network.parentChain.name }, + ] + }, + relayer: { + health: getRelayerStatus(), + data: [ + { name: 'Smart Contract', value: formatHash(relayer.account.walletAddress) }, + { name: 'Backlog', value: `${relayer.backlog} Subnet Headers` }, + // { name: 'Ave. tx fee', value: '0.001XDC/hour' }, + { name: 'Ave. tx fee', value: 'api TODO' }, + { name: 'Remaining Balance', value: relayer.account.balance }, + ] + }, + masterNodes: { + health: 'Normal' as InfoListHealth, + data: [ + { name: 'Current committee size', value: masterNodes.summary.masterNode }, + // { name: 'Activity', value: '0xdFrsdf...Dsa31ld7' }, + { name: 'Activity', value: 'TODO: fisher/liam' }, + { name: 'Number of candidate nodes', value: masterNodes.summary.candidate }, + ], + blocks: masterNodes.nodes.map((v: any, i: number) => ({ + ...v, + type: 'master-node', + account: formatHash(v.address), + number: i + 1 + })) + }, }; - // mock function - const fetchBlocksData = () => { - if (!blocksInfo) { - console.log('no info'); + const fetchMoreRecentBlocks = () => { + if (!recentBlocks) { return; } - setBlocksInfo(blocksInfo => { - const data = Array(20).fill('').map((_v, i) => { - return { ...mockDataItem, height: (blocksInfo[blocksInfo.length - 1] as any).height + 1 + i }; - }); + // TODO: From api + const data: any = []; - return [...blocksInfo, ...data]; + setRecentBlocks(recentBlocks => { + return [...recentBlocks, ...data]; }); }; - useEffect(() => { - setBlocksInfo(mockData); - }, []); - return ( <> <div className="grid grid-cols-2 llg:grid-cols-3 gap-6"> <Card> <InfoList title='Network Info' - status='Normal' - info={mockInfo.info1} + status={mappedInfo.network.health} + info={mappedInfo.network.data} /> </Card> <Card> <InfoList title='Relayer Info' - status='Abnormal' - info={mockInfo.info2} + status={mappedInfo.relayer.health} + info={mappedInfo.relayer.data} /> </Card> <Card> <InfoList title='Master Nodes' - status='Normal' - info={mockInfo.info3} + status={mappedInfo.masterNodes.health} + info={mappedInfo.masterNodes.data} /> </Card> </div> <div className="grid grid-cols-1 llg:grid-cols-2 gap-6"> <Card className='max-w-[565px]'> - <BlocksInfo title='Recent Blocks' data={blocksInfo} fetchMoreData={fetchBlocksData} enableInfinite /> + <BlocksInfo title='Recent Blocks' data={recentBlocks} fetchMoreData={fetchMoreRecentBlocks} enableInfinite /> </Card> <Card className='max-w-[565px]'> - <BlocksInfo title='Master Nodes' data={mockMasterNodes} /> + <BlocksInfo title='Master Nodes' data={mappedInfo.masterNodes.blocks} /> </Card> </div> </> diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 1b08c4f..0310c12 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -1,11 +1,11 @@ import Svg, { SvgNames } from "@/components/images/Svg"; import Title from "@/components/title/Title"; -type InfoListStatus = 'Normal' | 'Abnormal'; +export type InfoListHealth = 'Normal' | 'Abnormal'; interface InfoListProps { title: string; - status: InfoListStatus; + status: InfoListHealth; info: InfoItemBaseProps[]; } diff --git a/frontend/src/constants/urls.ts b/frontend/src/constants/urls.ts new file mode 100644 index 0000000..8bb3fda --- /dev/null +++ b/frontend/src/constants/urls.ts @@ -0,0 +1 @@ +export const baseUrl = 'https://devnetstats.apothem.network/stats/information'; \ No newline at end of file diff --git a/frontend/src/contexts/timeContext.tsx b/frontend/src/contexts/timeContext.tsx new file mode 100644 index 0000000..8088f58 --- /dev/null +++ b/frontend/src/contexts/timeContext.tsx @@ -0,0 +1,11 @@ +import { createContext } from 'react'; + +interface TimeContextType { + currentUnixTime: number; +} + +const initialContext: TimeContextType = { + currentUnixTime: 0 +}; + +export const TimeContext = createContext<TimeContextType>(initialContext); \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 320a07e..52440d5 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,7 +1,9 @@ +import axios from 'axios'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom'; +import { baseUrl } from '@/constants/urls.ts'; import Checker from '@/pages/Checker.tsx'; import Home from '@/pages/Home.tsx'; import Management from '@/pages/Management.tsx'; @@ -10,12 +12,40 @@ import App from './App.tsx'; import '@/index.css'; +export async function loader() { + async function getData() { + const urls = [ + `${baseUrl}/masternodes`, + `${baseUrl}/relayer`, + `${baseUrl}/network`, + `${baseUrl}/blocks`, + ]; + + const apiResponses = await axios.all(urls.map(url => axios.get(url))); + return apiResponses.map(response => response.data); + } + + const data = await getData(); + + if (!data || data.length < 4) { + return; + } + + return { + masterNodes: data[0], + relayer: data[1], + network: data[2], + blocks: data[3], + }; +} + const router = createBrowserRouter([ { path: "/", element: <App />, children: [{ path: "home", + loader, element: <Home />, }, { path: "checker", diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 3c52dba..94019a9 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -9,10 +9,10 @@ export default function Home() { <h1 className='pb-6 text-3xl font-bold'>Subnet Blockchain</h1> <Blocks /> </Card> - <Card> + {/* <Card> <h1 className='pb-6 text-3xl font-bold'>Copy at the parent chain</h1> <Blocks /> - </Card> + </Card> */} <InfoCards /> </div> diff --git a/frontend/src/utils/formatter.ts b/frontend/src/utils/formatter.ts new file mode 100644 index 0000000..af51800 --- /dev/null +++ b/frontend/src/utils/formatter.ts @@ -0,0 +1,6 @@ +export function formatHash(hash: string): string { + if (hash?.length >= 15) { + return `${hash.slice(0, 6)}...${hash.slice(-6)}`; + } + return ''; +} \ No newline at end of file From 06d8c2b70bc837ab0370bc3336fcb2c450cf8ccb Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 6 Jul 2023 08:43:12 +1000 Subject: [PATCH 02/87] Hookup parent chain animation with api --- frontend/src/components/Blocks.tsx | 100 +---------------------------- frontend/src/pages/Home.tsx | 91 +++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 105 deletions(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 41b221b..c683c12 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -1,103 +1,14 @@ -import axios from 'axios'; -import { Fragment, useContext, useEffect, useRef, useState } from 'react'; - import BlockConnectLine from '@/components/BlockConnectLine'; import BlockImage from '@/components/images/BlockImage'; -import { baseUrl } from '@/constants/urls'; -import { TimeContext } from '@/contexts/timeContext'; -import { useIsDesktopL } from '@/hooks/useMediaQuery'; - +import { Fragment } from 'react'; import styles from './blocks.module.scss'; -import { useLoaderData } from 'react-router'; export interface Block { number: number; confirmed: boolean; } -const addBlockPerClick = 3; - -function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { - const blocks = []; - - // confirmed blocks - for (let blockHeight = lastBlock - blockNumber + 1; blockHeight <= lastConfirmedBlock; blockHeight++) { - blocks.push({ number: blockHeight, confirmed: true }); - } - - // unconfirmed blocks - for (let blockHeight = lastConfirmedBlock + 1; blockHeight <= lastBlock; blockHeight++) { - blocks.push({ number: blockHeight, confirmed: false }); - } - - return blocks; -} - -export default function Blocks() { - const isDesktopL = useIsDesktopL(); - // use 13 blocks(desktop), otherwise use 20 blocks(XL desktop) - const blockNumber = isDesktopL ? 20 : 13; - const loaderData: any = useLoaderData(); - - const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); - const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); - const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); - const initialLastBlock = useRef<number | null>(null); - const { currentUnixTime } = useContext(TimeContext); - - useEffect(() => { - if (initialLastBlock.current === null) { - initialLastBlock.current = loaderData.blocks.latestMinedBlock.number; - } - }, []); - - // Move this up and send down to two blocks animations - useEffect(() => { - async function getData() { - const { data: { latestMinedBlock, latestSubnetCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); - setLastBlock(latestMinedBlock.number); - setLastConfirmedBlock(latestSubnetCommittedBlock.number); - - const newBlockNumber = latestMinedBlock.number - (initialLastBlock.current ?? 0) + blockNumber; - const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); - setBlocks(blocks); - } - - getData(); - }, [currentUnixTime]); - - function confirmBlocksExceptLastTwo() { - const newBlocks = blocks.map((block, index) => { - if (index === blocks.length - 1 || index === blocks.length - 2) { - return { ...block, confirmed: false }; - } - - return { ...block, confirmed: true }; - }); - - setBlocks(newBlocks); - setLastConfirmedBlock(lastBlock - 2); - } - - // TODO: replace addBlockPerClick to api data - /** - * 1. get increase block number by api(block number) - * 2. push new blocks by loop & block id from api - * 3. set new lastBlock state, set new translateX length state - */ - function addBlock() { - const newBlocks = []; - for (let i = 1; i <= addBlockPerClick; i++) { - newBlocks.push({ number: (lastBlock + i), confirmed: false }); - } - - setBlocks([ - ...blocks, - ...newBlocks - ]); - setLastBlock(lastBlock => lastBlock + 3); - } - +export default function Blocks({initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks}: any) { {/* n * block-width + (n - 1) * spacing */ } const blockSize = 35 + 17.99; const translateAmount = initialLastBlock.current ? -((lastBlock - initialLastBlock.current) * blockSize) : 0; @@ -173,11 +84,6 @@ export default function Blocks() { {/* Left brace layer mask */} <div className='absolute top-[0px] w-[40px] left-[-3px] h-[40px] dark:bg-bg-dark-800 bg-white ' /> </div> - - <div> - <button className="bg-blue-500 text-white py-2 px-4 mt-5 rounded text-base" onClick={addBlock}>Add block</button> - <button className="bg-blue-500 text-white py-2 px-4 mt-5 rounded text-base ml-2" onClick={confirmBlocksExceptLastTwo}>Confirm blocks</button> - </div> </> ); -} \ No newline at end of file +} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 94019a9..9c8a88a 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,20 +1,95 @@ -import Blocks from "@/components/Blocks"; -import Card from "@/components/card/Card"; -import InfoCards from "@/components/info-cards/InfoCards"; +import axios from 'axios'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { useLoaderData } from 'react-router-dom'; +import Blocks, { Block } from '@/components/Blocks'; +import Card from '@/components/card/Card'; +import InfoCards from '@/components/info-cards/InfoCards'; +import { baseUrl } from '@/constants/urls'; +import { TimeContext } from '@/contexts/timeContext'; +import { useIsDesktopL } from '@/hooks/useMediaQuery'; + +function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { + const blocks = []; + + // confirmed blocks + for (let blockHeight = lastBlock - blockNumber + 1; blockHeight <= lastConfirmedBlock; blockHeight++) { + blocks.push({ number: blockHeight, confirmed: true }); + } + + // unconfirmed blocks + for (let blockHeight = lastConfirmedBlock + 1; blockHeight <= lastBlock; blockHeight++) { + blocks.push({ number: blockHeight, confirmed: false }); + } + + return blocks; +} export default function Home() { + const isDesktopL = useIsDesktopL(); + // use 13 blocks(desktop), otherwise use 20 blocks(XL desktop) + const blockNumber = isDesktopL ? 20 : 13; + const loaderData: any = useLoaderData(); + + const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); + const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); + const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); + const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); + const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); + const initialLastBlock = useRef<number | null>(null); + const { currentUnixTime } = useContext(TimeContext); + + useEffect(() => { + if (initialLastBlock.current === null) { + initialLastBlock.current = loaderData.blocks.latestMinedBlock.number; + } + }, []); + + // Move this up and send down to two blocks animations + useEffect(() => { + async function getData() { + const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); + setLastBlock(latestMinedBlock.number); + setLastConfirmedBlock(latestSubnetCommittedBlock.number); + // Mocked + // setLatestParentChainCommittedBlock(latestParentChainCommittedBlock.number) + latestParentChainCommittedBlock; + setLastParentConfirmedBlock(latestSubnetCommittedBlock.number - 5); + + const newBlockNumber = latestMinedBlock.number - (initialLastBlock.current ?? 0) + blockNumber; + const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); + // mocked + const parentChainBlocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number - 5, newBlockNumber); + setBlocks(blocks); + setParentChainBlocks(parentChainBlocks); + } + + getData(); + }, [currentUnixTime]); + return ( <div className='grid gap-6 grid-col-1'> <Card> <h1 className='pb-6 text-3xl font-bold'>Subnet Blockchain</h1> - <Blocks /> + <Blocks + initialLastBlock={initialLastBlock} + lastBlock={lastBlock} + lastConfirmedBlock={lastConfirmedBlock} + blockNumber={blockNumber} + blocks={blocks} + /> </Card> - {/* <Card> + <Card> <h1 className='pb-6 text-3xl font-bold'>Copy at the parent chain</h1> - <Blocks /> - </Card> */} + <Blocks + initialLastBlock={initialLastBlock} + lastBlock={lastBlock} + lastConfirmedBlock={lastParentConfirmedBlock} + blockNumber={blockNumber} + blocks={parentChainBlocks} + /> + </Card> <InfoCards /> </div> ); -} \ No newline at end of file +} From ec414110dada3b9c3a4da4a6f746ee5028c3b84b Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 6 Jul 2023 10:11:28 +1000 Subject: [PATCH 03/87] Add loader types --- backend/src/storage/block.ts | 2 +- frontend/package.json | 2 +- frontend/src/App.tsx | 9 +- frontend/src/components/Blocks.tsx | 12 +- .../src/components/info-cards/InfoCards.tsx | 15 +-- .../src/components/info-list/InfoList.tsx | 2 +- frontend/src/main.tsx | 8 +- frontend/src/pages/Home.tsx | 12 +- frontend/src/types/loaderData.d.ts | 108 ++++++++++++++++++ frontend/src/utils/time.ts | 6 + 10 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 frontend/src/types/loaderData.d.ts create mode 100644 frontend/src/utils/time.ts diff --git a/backend/src/storage/block.ts b/backend/src/storage/block.ts index 5ca3e2e..0fd1c4c 100644 --- a/backend/src/storage/block.ts +++ b/backend/src/storage/block.ts @@ -8,7 +8,7 @@ const STATUS_TTL = 60; const LATEST_COMMITTEDBLOCK_KEY = 'LATEST_COMMITTED_BLOCK'; -/** +/** This class is created so that we can easily swap with real DB without making changes to any other files. We using Promise for all the method eventho there is no need for that. This is also just to make it convenient when we move to a proper cache/db */ diff --git a/frontend/package.json b/frontend/package.json index 24dd2af..26523cf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,4 +32,4 @@ "typescript": "^5.0.2", "vite": "^4.3.9" } -} \ No newline at end of file +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b16b773..5e25cac 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,10 +5,7 @@ import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; import { ThemeContext } from '@/contexts/themeContext'; import { TimeContext } from '@/contexts/timeContext'; - -function getUnixTime() { - return Math.floor(Date.now() / 1000); -} +import { getUnixTime, pollingPeriod } from '@/utils/time'; function App() { const [theme, setTheme] = useState<ThemeModes>('light'); @@ -17,7 +14,7 @@ function App() { useEffect(() => { const intervalId = setInterval(() => { setCurrentUnixTime(getUnixTime()); - }, 5 * 1000); + }, pollingPeriod); return () => clearInterval(intervalId); }, []); @@ -36,4 +33,4 @@ function App() { ); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index c683c12..a520266 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -8,10 +8,18 @@ export interface Block { confirmed: boolean; } -export default function Blocks({initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks}: any) { +interface BlocksProps { + initialLastBlock: number; + lastBlock: number; + lastConfirmedBlock: number; + blockNumber: number; + blocks: Block[]; +} + +export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks }: BlocksProps) { {/* n * block-width + (n - 1) * spacing */ } const blockSize = 35 + 17.99; - const translateAmount = initialLastBlock.current ? -((lastBlock - initialLastBlock.current) * blockSize) : 0; + const translateAmount = initialLastBlock ? -((lastBlock - initialLastBlock) * blockSize) : 0; const unConfirmedNumber = lastBlock - lastConfirmedBlock; const confirmedNumber = blockNumber - unConfirmedNumber; // Definition: From left to right, the first visible index is 0 diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 2f39dd2..1c95a35 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -6,15 +6,16 @@ import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList, { InfoListHealth } from '@/components/info-list/InfoList'; import { formatHash } from '@/utils/formatter'; +import { LoaderData } from '@/types/loaderData'; export default function InfoCards() { - const loaderData: any = useLoaderData(); + const loaderData = useLoaderData() as LoaderData; const { network, relayer, masterNodes, blocks } = loaderData; const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); function getNetworkStatus(): InfoListHealth { - if (network.health.status === true) { + if (network.health.status === 'UP') { return 'Normal'; } @@ -29,8 +30,8 @@ export default function InfoCards() { return 'Abnormal'; } - function getInitRecentBlocks() { - return blocks.blocks.sort((a: any, b: any) => b.number - a.number).map((block: any) => ({ + function getInitRecentBlocks(): BlocksInfoItem[] { + return blocks.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ type: 'recent-block', ...block })); @@ -59,10 +60,10 @@ export default function InfoCards() { masterNodes: { health: 'Normal' as InfoListHealth, data: [ - { name: 'Current committee size', value: masterNodes.summary.masterNode }, + { name: 'Current committee size', value: masterNodes.summary.activeNodes }, // { name: 'Activity', value: '0xdFrsdf...Dsa31ld7' }, { name: 'Activity', value: 'TODO: fisher/liam' }, - { name: 'Number of candidate nodes', value: masterNodes.summary.candidate }, + { name: 'Number of candidate nodes', value: masterNodes.summary.inActiveNodes }, ], blocks: masterNodes.nodes.map((v: any, i: number) => ({ ...v, @@ -122,4 +123,4 @@ export default function InfoCards() { </div> </> ); -} \ No newline at end of file +} diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 0310c12..eaa300e 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -35,7 +35,7 @@ export default function InfoList({ title, status, info }: InfoListProps) { interface InfoItemBaseProps { name: string; - value: string; + value: number | string; } interface InfoItemProps extends InfoItemBaseProps { diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 52440d5..4843871 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -27,17 +27,13 @@ export async function loader() { const data = await getData(); - if (!data || data.length < 4) { - return; - } - return { masterNodes: data[0], relayer: data[1], network: data[2], blocks: data[3], }; -} +}; const router = createBrowserRouter([ { @@ -67,4 +63,4 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( <React.StrictMode> <RouterProvider router={router} /> </React.StrictMode>, -); \ No newline at end of file +); diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 9c8a88a..b4239dc 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -9,6 +9,8 @@ import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/timeContext'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; +import type { LoaderData } from '@/types/loaderData'; + function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { const blocks = []; @@ -28,7 +30,7 @@ export default function Home() { const isDesktopL = useIsDesktopL(); // use 13 blocks(desktop), otherwise use 20 blocks(XL desktop) const blockNumber = isDesktopL ? 20 : 13; - const loaderData: any = useLoaderData(); + const loaderData = useLoaderData() as LoaderData; const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); @@ -66,12 +68,16 @@ export default function Home() { getData(); }, [currentUnixTime]); + if (!initialLastBlock.current) { + return <></>; + } + return ( <div className='grid gap-6 grid-col-1'> <Card> <h1 className='pb-6 text-3xl font-bold'>Subnet Blockchain</h1> <Blocks - initialLastBlock={initialLastBlock} + initialLastBlock={initialLastBlock.current} lastBlock={lastBlock} lastConfirmedBlock={lastConfirmedBlock} blockNumber={blockNumber} @@ -81,7 +87,7 @@ export default function Home() { <Card> <h1 className='pb-6 text-3xl font-bold'>Copy at the parent chain</h1> <Blocks - initialLastBlock={initialLastBlock} + initialLastBlock={initialLastBlock.current} lastBlock={lastBlock} lastConfirmedBlock={lastParentConfirmedBlock} blockNumber={blockNumber} diff --git a/frontend/src/types/loaderData.d.ts b/frontend/src/types/loaderData.d.ts new file mode 100644 index 0000000..5c2de76 --- /dev/null +++ b/frontend/src/types/loaderData.d.ts @@ -0,0 +1,108 @@ +export interface LoaderData { + masterNodes: MasterNodes; + blocks: Blocks; + network: Network; + relayer: Relayer; +} + +interface MasterNodes { + summary: { + committee: number; + activeNodes: number; + /** Number of penalised nodes */ + inActiveNodes: number; + }; + /** A list of existing master nodes. Sorted by nodeId */ + nodes: MasterNodes.Node[]; +} + +namespace MasterNodes { + interface Node { + address: string; + type: 'CANDIDATE', 'MASTERNODE', 'PENALTY'; + } +} + +interface Blocks { + /** A list of recently mined blocks. Sorted by block number */ + blocks: Blocks.Block[]; + /** The block that was most recently mined in the subnet. regardless its confirmation status */ + latestMinedBlock: { + hash: string; + number: number; + }; + /** The block that was most recently confirm to be committed in the subnet. (Ignoring its confirmation status from parent chain) */ + latestSubnetCommittedBlock: { + hash: string; + number: number; + }; + /** The block that was most recently confirm to be committed in the parent chain. */ + latestParentChainCommittedBlock: { + hash: string; + number: number; + }; + /** A simple enum to indicate whether the subnet chain is operational. i.e if blocks are mined */ + chainHealth: "UP" | "DOWN"; +} + +namespace Blocks { + interface Block { + /** The subnet block hash */ + hash: string; + /** The subnet block number */ + number: number; + /** The subnet block's parentHash */ + parentHash: string; + /** The masternode address who mined this block */ + miner: string; + /** This boolean value is to indicate whether this block has been confirmed in the subnet itself */ + committedInSubnet: boolean; + /** This boolean value is to indicate whether this block has been confirmed in the parent chain smart contract */ + committedInParentChain: boolean; + + timestamp: number; + } +} + +interface Relayer { + /** The admin/super account information */ + account: { + /** The super/admin account remaining balance in XDC */ + balance: string; + /** The wallet address of the account */ + walletAddress: string; + }; + /** The current gap between audited block in smartcontract (parent chain) and latest minded block in subnet */ + backlog: number; + contractAddress: string; + health: { + /** An enum value to indicate the current relayer status. */ + status: "UP" | "DOWN"; + /** A short description about the current running status when there is an issue. E.g System is running but very low */ + details: string; + }; +} + +interface Network { + subnet: { + /** block metadata, such as mining frequency */ + block: { + /** The block mining time per X second. */ + averageBlockTime: number; + /** The subnet transaction throughput, we only keep track of the last 10 txs and take average per second. */ + txThroughput: number; + }; + }; + parentChain: { + /** A string value which is used to identify the target parent chain. It can be a URL, IP address or a name. */ + url: string; + /** Parent Chain name */ + name: string; + }; + health: { + /** An enum value to indicate the current relayer status. */ + status: "UP" | "DOWN"; + /** A short description about the current running status when there is an issue. E.g System is running but very low */ + details: string; + }; +} diff --git a/frontend/src/utils/time.ts b/frontend/src/utils/time.ts new file mode 100644 index 0000000..7321b4a --- /dev/null +++ b/frontend/src/utils/time.ts @@ -0,0 +1,6 @@ +export function getUnixTime() { + // unix time in second + return Math.floor(Date.now() / 1000); +} + +export const pollingPeriod = 5 * 1000; From b62aac3f85715bdc70cfb7270cf6d400177e2a9c Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 6 Jul 2023 16:11:16 +1000 Subject: [PATCH 04/87] Remove assets --- frontend/src/assets/miner2.svg | 6 ------ frontend/src/assets/miner3.svg | 7 ------- frontend/src/assets/penalty1.svg | 2 -- frontend/src/assets/standby1.svg | 7 ------- 4 files changed, 22 deletions(-) delete mode 100644 frontend/src/assets/miner2.svg delete mode 100644 frontend/src/assets/miner3.svg delete mode 100644 frontend/src/assets/penalty1.svg delete mode 100644 frontend/src/assets/standby1.svg diff --git a/frontend/src/assets/miner2.svg b/frontend/src/assets/miner2.svg deleted file mode 100644 index 9b2b6f4..0000000 --- a/frontend/src/assets/miner2.svg +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"> -<title>mining-diamonds - - \ No newline at end of file diff --git a/frontend/src/assets/miner3.svg b/frontend/src/assets/miner3.svg deleted file mode 100644 index 6fe5aaa..0000000 --- a/frontend/src/assets/miner3.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/frontend/src/assets/penalty1.svg b/frontend/src/assets/penalty1.svg deleted file mode 100644 index ad86a16..0000000 --- a/frontend/src/assets/penalty1.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/frontend/src/assets/standby1.svg b/frontend/src/assets/standby1.svg deleted file mode 100644 index f5bb181..0000000 --- a/frontend/src/assets/standby1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From 1555a4ef66e65796b46cbeb54c19fcc6bd77e8cd Mon Sep 17 00:00:00 2001 From: Choco Date: Thu, 6 Jul 2023 16:51:54 +1000 Subject: [PATCH 05/87] Checker page --- frontend/package.json | 1 + frontend/src/assets/search.svg | 21 +++++ .../ConfirmationStatus.tsx | 31 +++++++ frontend/src/components/images/Svg.tsx | 18 ++-- .../src/components/info-cards/InfoCards.tsx | 47 +++++------ .../src/components/info-list/InfoList.tsx | 29 ++++--- .../src/components/search-bar/SearchBar.tsx | 29 +++++++ frontend/src/components/title/Title.tsx | 7 +- frontend/src/pages/Checker.tsx | 82 ++++++++++++++++++- frontend/src/types/info.d.ts | 15 ++++ frontend/yarn.lock | 5 ++ 11 files changed, 238 insertions(+), 47 deletions(-) create mode 100644 frontend/src/assets/search.svg create mode 100644 frontend/src/components/confirmation-status/ConfirmationStatus.tsx create mode 100644 frontend/src/components/search-bar/SearchBar.tsx create mode 100644 frontend/src/types/info.d.ts diff --git a/frontend/package.json b/frontend/package.json index 26523cf..cafcd0c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "react-dom": "^18.2.0", "react-router-dom": "^6.14.0", "sass": "^1.63.6", + "tailwind-merge": "^1.13.2", "tailwindcss": "^3.3.2", "vite-tsconfig-paths": "^4.2.0" }, diff --git a/frontend/src/assets/search.svg b/frontend/src/assets/search.svg new file mode 100644 index 0000000..e3374cc --- /dev/null +++ b/frontend/src/assets/search.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx new file mode 100644 index 0000000..35d6f89 --- /dev/null +++ b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx @@ -0,0 +1,31 @@ +import { twMerge } from 'tailwind-merge'; + +interface ConfirmationStatusProps { + className?: string; +} + +export default function ConfirmationStatus(props: ConfirmationStatusProps) { + return ( +
+ +

Confirmation Status

+ + +
+ ); +} + +interface ConfirmationStatusItemProps { + name: string; + value: string; + className?: string; +} + +function ConfirmationStatusItem(props: ConfirmationStatusItemProps) { + return ( +
+ {props.name} + {props.value} +
+ ) +} diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index b1d3e6c..1d1cde2 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -1,9 +1,10 @@ import Check from '@/assets/check.svg'; import Cross from '@/assets/cross.svg'; -import Penalty from '@/assets/penalty.svg'; -import Standby from '@/assets/standby.svg'; import Miner from '@/assets/miner.svg'; +import Penalty from '@/assets/penalty.svg'; import Rhombus from '@/assets/rhombus.svg'; +import Search from '@/assets/search.svg'; +import Standby from '@/assets/standby.svg'; interface GeneralSvgProps { colour: string; @@ -92,9 +93,10 @@ type SvgSizes = 'sm'; interface SvgProps { svgName: SvgNames; size?: SvgSizes; + sizeClass?: string; } -export default function Svg({ svgName, size }: SvgProps) { +export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass }: SvgProps) { function getSizeClass(size?: SvgSizes) { switch (size) { case 'sm': @@ -105,7 +107,7 @@ export default function Svg({ svgName, size }: SvgProps) { } } - const sizeClass = getSizeClass(size); + const sizeClass = userDefinedSizeClass ?? getSizeClass(size); let SvgComponent = ''; let alt = ''; @@ -139,6 +141,11 @@ export default function Svg({ svgName, size }: SvgProps) { SvgComponent = Rhombus; alt = 'Rhombus'; break; + + case SvgNames.Search: + SvgComponent = Search; + alt = 'Search'; + break; } return ( @@ -163,4 +170,5 @@ export enum SvgNames { Penalty = 'Penalty', Standby = 'Standby', Rhombus = 'Rhombus', -} \ No newline at end of file + Search = 'Search', +} diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 1c95a35..82787fd 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -4,18 +4,18 @@ import { useLoaderData } from 'react-router-dom'; import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; -import InfoList, { InfoListHealth } from '@/components/info-list/InfoList'; -import { formatHash } from '@/utils/formatter'; +import InfoList from '@/components/info-list/InfoList'; +import { Info, InfoListHealth } from '@/types/info'; import { LoaderData } from '@/types/loaderData'; +import { formatHash } from '@/utils/formatter'; export default function InfoCards() { const loaderData = useLoaderData() as LoaderData; - const { network, relayer, masterNodes, blocks } = loaderData; const [recentBlocks, setRecentBlocks] = useState(getInitRecentBlocks()); function getNetworkStatus(): InfoListHealth { - if (network.health.status === 'UP') { + if (loaderData.network.health.status === 'UP') { return 'Normal'; } @@ -23,7 +23,7 @@ export default function InfoCards() { } function getRelayerStatus(): InfoListHealth { - if (relayer.health.status === 'UP') { + if (loaderData.relayer.health.status === 'UP') { return 'Normal'; } @@ -31,49 +31,50 @@ export default function InfoCards() { } function getInitRecentBlocks(): BlocksInfoItem[] { - return blocks.blocks.sort((a, b) => b.number - a.number).map(block => ({ + return loaderData.blocks.blocks.sort((a, b) => b.number - a.number).map(block => ({ type: 'recent-block', ...block })); } - const mappedInfo = { + const mappedInfo: Info = { // `recentBlocks` is handled by a state since it would get updated when load more page network: { health: getNetworkStatus(), data: [ - { name: 'Block Time', value: `${network.subnet.block.averageBlockTime}s` }, - { name: 'TX Throughput', value: `${Math.round(network.subnet.block.txThroughput * 100) / 100} txs/s` }, - { name: 'Checkpointed to', value: network.parentChain.name }, + { name: 'Block Time', value: `${loaderData.network.subnet.block.averageBlockTime}s` }, + { name: 'TX Throughput', value: `${Math.round(loaderData.network.subnet.block.txThroughput * 100) / 100} txs/s` }, + { name: 'Checkpointed to', value: loaderData.network.parentChain.name }, ] }, relayer: { health: getRelayerStatus(), data: [ - { name: 'Smart Contract', value: formatHash(relayer.account.walletAddress) }, - { name: 'Backlog', value: `${relayer.backlog} Subnet Headers` }, + { name: 'Smart Contract', value: formatHash(loaderData.relayer.account.walletAddress) }, + { name: 'Backlog', value: `${loaderData.relayer.backlog} Subnet Headers` }, // { name: 'Ave. tx fee', value: '0.001XDC/hour' }, { name: 'Ave. tx fee', value: 'api TODO' }, - { name: 'Remaining Balance', value: relayer.account.balance }, + { name: 'Remaining Balance', value: loaderData.relayer.account.balance }, ] }, masterNodes: { health: 'Normal' as InfoListHealth, data: [ - { name: 'Current committee size', value: masterNodes.summary.activeNodes }, + { name: 'Current committee size', value: loaderData.masterNodes.summary.activeNodes }, // { name: 'Activity', value: '0xdFrsdf...Dsa31ld7' }, { name: 'Activity', value: 'TODO: fisher/liam' }, - { name: 'Number of candidate nodes', value: masterNodes.summary.inActiveNodes }, + { name: 'Number of candidate nodes', value: loaderData.masterNodes.summary.inActiveNodes }, ], - blocks: masterNodes.nodes.map((v: any, i: number) => ({ - ...v, - type: 'master-node', - account: formatHash(v.address), - number: i + 1 - })) }, }; + const masterNodes = loaderData.masterNodes.nodes.map((v: any, i: number) => ({ + ...v, + type: 'master-node', + account: formatHash(v.address), + number: i + 1 + })) + const fetchMoreRecentBlocks = () => { if (!recentBlocks) { return; @@ -106,7 +107,7 @@ export default function InfoCards() { @@ -118,7 +119,7 @@ export default function InfoCards() { - +
diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index eaa300e..b53bc95 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -1,30 +1,29 @@ -import Svg, { SvgNames } from "@/components/images/Svg"; -import Title from "@/components/title/Title"; - -export type InfoListHealth = 'Normal' | 'Abnormal'; +import Svg, { SvgNames } from '@/components/images/Svg'; +import Title from '@/components/title/Title'; +import { InfoListHealth } from '@/types/info'; interface InfoListProps { title: string; - status: InfoListHealth; info: InfoItemBaseProps[]; + status?: InfoListHealth; } export default function InfoList({ title, status, info }: InfoListProps) { return ( <>
-
- - </div> - <div className='inline-flex items-center'> - <span>Status</span> - <span - className={`ml-1 px-3 py-2.5 bg-opacity-20 rounded-3xl font-bold leading-none + <Title title={title} /> + {status && ( + <div className='inline-flex items-center'> + <span>Status</span> + <span + className={`ml-1 px-3 py-2.5 bg-opacity-20 rounded-3xl font-bold leading-none ${status === 'Normal' ? 'bg-sky-500 text-sky-500' : 'bg-warning text-warning'} `}> - {status} - </span> - </div> + {status} + </span> + </div> + )} </div> {info.map((item, index) => { return <InfoItem key={index} {...item} isFirst={index === 0} />; diff --git a/frontend/src/components/search-bar/SearchBar.tsx b/frontend/src/components/search-bar/SearchBar.tsx new file mode 100644 index 0000000..9a64a3e --- /dev/null +++ b/frontend/src/components/search-bar/SearchBar.tsx @@ -0,0 +1,29 @@ +import { useState } from 'react'; + +import Svg, { SvgNames } from '@/components/images/Svg'; + +export default function SearchBar() { + const [searchText, setSearchText] = useState(''); + + function search() { + console.log(`Searching with text: ${searchText}`); + } + + return ( + <div className="flex justify-between border-2 border-text-white-400 dark:border-none rounded-full pl-4 pr-2.5 py-2.5 dark:bg-bg-dark-800"> + <input + type='text' + className={`pl-3 grow text-base leading-tight outline-none + text-text-dark-800 placeholder:text-text-dark-600 + dark:text-text-white-400 dark:bg-bg-dark-800 dark:placeholder:text-text-dark-500` + } + value={searchText} + onChange={e => setSearchText(e.target.value)} + placeholder="Block Height, Block Hash, TX Hash" + /> + <button className='-m-1.5' onClick={search}> + <Svg svgName={SvgNames.Search} sizeClass='w-[48px] h-[48px]' /> + </button> + </div> + ); +} diff --git a/frontend/src/components/title/Title.tsx b/frontend/src/components/title/Title.tsx index 4d2c018..1a5156d 100644 --- a/frontend/src/components/title/Title.tsx +++ b/frontend/src/components/title/Title.tsx @@ -4,6 +4,9 @@ interface TitleProps { export default function Title({ title }: TitleProps) { return ( - <div className='text-2xl font-bold leading-loose'>{title}</div> + <div className="h-[62px] flex items-center"> + <div className='text-2xl font-bold leading-tight'>{title}</div> + </div> ); -} \ No newline at end of file +} + \ No newline at end of file diff --git a/frontend/src/pages/Checker.tsx b/frontend/src/pages/Checker.tsx index 2238fb5..0cf67b1 100644 --- a/frontend/src/pages/Checker.tsx +++ b/frontend/src/pages/Checker.tsx @@ -1,5 +1,83 @@ +import Card from '@/components/card/Card'; +import ConfirmationStatus from '@/components/confirmation-status/ConfirmationStatus'; +import InfoList from '@/components/info-list/InfoList'; +import SearchBar from '@/components/search-bar/SearchBar'; +import { Info } from '@/types/info'; + export default function Checker() { + + const mappedInfo: Info = { + transaction: { + data: [{ + name: 'Block time', + value: '2' + }, { + name: 'TX Throughput', + value: '10 texts/s' + }, { + name: 'Checkpointed to', + value: 'XDC' + }, { + name: 'Gas', + value: 'Date Time' + }] + }, + subnetBlock: { + data: [{ + name: 'Block time', + value: '2' + }, { + name: 'TX Throughput', + value: '10 texts/s' + }, { + name: 'Checkpointed to', + value: 'XDC' + }, { + name: 'TX idx', + value: 'Integer' + }] + }, + parentChain: { + data: [{ + name: 'Block time', + value: '2' + }, { + name: 'TX Throughput', + value: '10 texts/s' + }, { + name: 'Checkpointed to', + value: 'XDC' + }, { + name: 'TX idx', + value: 'Integer' + }] + }, + }; + return ( - <div className='text-white'>Checker</div> + <> + <SearchBar /> + <ConfirmationStatus className='pt-8' /> + <div className="pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6"> + <Card> + <InfoList + title='Transaction Info' + info={mappedInfo.transaction.data} + /> + </Card> + <Card> + <InfoList + title='Subnet Block Info ' + info={mappedInfo.subnetBlock.data} + /> + </Card> + <Card> + <InfoList + title='Checkpointing parent chain block' + info={mappedInfo.parentChain.data} + /> + </Card> + </div> + </> ); -} \ No newline at end of file +} diff --git a/frontend/src/types/info.d.ts b/frontend/src/types/info.d.ts new file mode 100644 index 0000000..6d27602 --- /dev/null +++ b/frontend/src/types/info.d.ts @@ -0,0 +1,15 @@ +export interface Info { + [x: string]: { + data: Info.Data[]; + health?: InfoListHealth; + }; +} + +namespace Info { + interface Data { + name: string; + value: string | number; + } +} + +export type InfoListHealth = 'Normal' | 'Abnormal'; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c3e6854..d3b3818 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1805,6 +1805,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tailwind-merge@^1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.13.2.tgz#1d06c9e95ffda2320efc50ed33c65be0cda23091" + integrity sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ== + tailwindcss@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3" From f2602e4553f3b767bbf5e74314f8293c46b16143 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 6 Jul 2023 17:14:21 +1000 Subject: [PATCH 06/87] Hookup more api data --- .../src/components/info-cards/InfoCards.tsx | 16 ++++++---------- frontend/src/components/nav/Nav.tsx | 12 +++++++++--- frontend/src/main.tsx | 18 ++++++++++++++++-- frontend/src/pages/Home.tsx | 13 ++++--------- frontend/src/types/loaderData.d.ts | 8 +++++++- 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 82787fd..bacc11d 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -6,11 +6,11 @@ import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; import { Info, InfoListHealth } from '@/types/info'; -import { LoaderData } from '@/types/loaderData'; +import { HomeLoaderData } from '@/types/loaderData'; import { formatHash } from '@/utils/formatter'; export default function InfoCards() { - const loaderData = useLoaderData() as LoaderData; + const loaderData = useLoaderData() as HomeLoaderData; const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); @@ -38,7 +38,6 @@ export default function InfoCards() { } const mappedInfo: Info = { - // `recentBlocks` is handled by a state since it would get updated when load more page network: { health: getNetworkStatus(), data: [ @@ -52,18 +51,15 @@ export default function InfoCards() { data: [ { name: 'Smart Contract', value: formatHash(loaderData.relayer.account.walletAddress) }, { name: 'Backlog', value: `${loaderData.relayer.backlog} Subnet Headers` }, - // { name: 'Ave. tx fee', value: '0.001XDC/hour' }, - { name: 'Ave. tx fee', value: 'api TODO' }, + { name: 'Ave. tx fee', value: loaderData.relayer.averageTXfee }, { name: 'Remaining Balance', value: loaderData.relayer.account.balance }, ] }, masterNodes: { - health: 'Normal' as InfoListHealth, data: [ - { name: 'Current committee size', value: loaderData.masterNodes.summary.activeNodes }, - // { name: 'Activity', value: '0xdFrsdf...Dsa31ld7' }, - { name: 'Activity', value: 'TODO: fisher/liam' }, - { name: 'Number of candidate nodes', value: loaderData.masterNodes.summary.inActiveNodes }, + { name: 'Current committee size', value: loaderData.masterNodes.summary.committee }, + { name: 'Activity', value: `${loaderData.masterNodes.summary.activeNodes}(active) ${loaderData.masterNodes.summary.inActiveNodes}(inactive)` }, + { name: 'Number of standby nodes', value: loaderData.masterNodes.summary.inActiveNodes }, ], }, }; diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index b735488..7f753eb 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -1,21 +1,27 @@ +import { useLoaderData } from 'react-router-dom'; + import CheckerImage from '@/components/images/CheckerImage'; import HouseImage from '@/components/images/HouseImage'; import ManagementImage from '@/components/images/ManagementImage'; import NavItem from '@/components/nav-item/NavItem'; import ThemeSwitch from '@/components/theme-switch/ThemeSwitch'; +import type { AppLoaderData } from '@/types/loaderData'; + export type Pages = 'home' | 'checker' | 'management'; export default function Nav(): JSX.Element { + const loaderData = useLoaderData() as AppLoaderData; + return ( <nav id='nav' className='sticky top-0 dark:bg-bg-dark-1000 w-[246px] h-[1024px] shrink-0 shadow-grey flex flex-col justify-between'> <div> <div className="flex items-center flex-col border-b-[1px] border-text-white dark:border-border-light"> <div className='pt-12 font-bold text-[26px]'> - Logo + Logo: TODO </div> <div className="py-6 dark:text-sky-300"> - {'{Subnet ID}'} + {loaderData.name} </div> </div> <NavItem Image={HouseImage} text='Home' className='pt-[72px]' page='home' /> @@ -25,4 +31,4 @@ export default function Nav(): JSX.Element { <ThemeSwitch /> </nav> ); -} \ No newline at end of file +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 4843871..354b31e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,7 +12,20 @@ import App from './App.tsx'; import '@/index.css'; -export async function loader() { +export async function appLoader() { + async function getData() { + const response = await axios.get(`${baseUrl}/network`); + return response.data; + } + + const data = await getData(); + + return { + name: data.subnet.name + }; +}; + +export async function homeLoader() { async function getData() { const urls = [ `${baseUrl}/masternodes`, @@ -39,9 +52,10 @@ const router = createBrowserRouter([ { path: "/", element: <App />, + loader: appLoader, children: [{ path: "home", - loader, + loader: homeLoader, element: <Home />, }, { path: "checker", diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index b4239dc..97548fa 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -9,7 +9,7 @@ import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/timeContext'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; -import type { LoaderData } from '@/types/loaderData'; +import type { HomeLoaderData } from '@/types/loaderData'; function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { const blocks = []; @@ -30,7 +30,7 @@ export default function Home() { const isDesktopL = useIsDesktopL(); // use 13 blocks(desktop), otherwise use 20 blocks(XL desktop) const blockNumber = isDesktopL ? 20 : 13; - const loaderData = useLoaderData() as LoaderData; + const loaderData = useLoaderData() as HomeLoaderData; const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); @@ -46,21 +46,16 @@ export default function Home() { } }, []); - // Move this up and send down to two blocks animations useEffect(() => { async function getData() { const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); setLastBlock(latestMinedBlock.number); setLastConfirmedBlock(latestSubnetCommittedBlock.number); - // Mocked - // setLatestParentChainCommittedBlock(latestParentChainCommittedBlock.number) - latestParentChainCommittedBlock; - setLastParentConfirmedBlock(latestSubnetCommittedBlock.number - 5); + setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); const newBlockNumber = latestMinedBlock.number - (initialLastBlock.current ?? 0) + blockNumber; const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); - // mocked - const parentChainBlocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number - 5, newBlockNumber); + const parentChainBlocks = getBlocks(latestMinedBlock.number, latestParentChainCommittedBlock.number, newBlockNumber); setBlocks(blocks); setParentChainBlocks(parentChainBlocks); } diff --git a/frontend/src/types/loaderData.d.ts b/frontend/src/types/loaderData.d.ts index 5c2de76..2eeccd0 100644 --- a/frontend/src/types/loaderData.d.ts +++ b/frontend/src/types/loaderData.d.ts @@ -1,4 +1,8 @@ -export interface LoaderData { +export interface AppLoaderData { + name: string; +} + +export interface HomeLoaderData { masterNodes: MasterNodes; blocks: Blocks; network: Network; @@ -81,10 +85,12 @@ interface Relayer { /** A short description about the current running status when there is an issue. E.g System is running but very low */ details: string; }; + averageTXfee: number; } interface Network { subnet: { + name: string; /** block metadata, such as mining frequency */ block: { /** The block mining time per X second. */ From 85c1d46e18b156eb1a970f98882e09b94dd9a333 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 6 Jul 2023 17:39:08 +1000 Subject: [PATCH 07/87] Rename and add error page --- frontend/src/main.tsx | 14 +++++++------ .../pages/{Checker.tsx => CheckerPage.tsx} | 2 +- frontend/src/pages/ErrorPage.tsx | 20 +++++++++++++++++++ frontend/src/pages/{Home.tsx => HomePage.tsx} | 8 +++++++- .../{Management.tsx => ManagementPage.tsx} | 4 ++-- 5 files changed, 38 insertions(+), 10 deletions(-) rename frontend/src/pages/{Checker.tsx => CheckerPage.tsx} (97%) create mode 100644 frontend/src/pages/ErrorPage.tsx rename frontend/src/pages/{Home.tsx => HomePage.tsx} (97%) rename frontend/src/pages/{Management.tsx => ManagementPage.tsx} (59%) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 354b31e..c13d584 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -4,9 +4,10 @@ import ReactDOM from 'react-dom/client'; import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom'; import { baseUrl } from '@/constants/urls.ts'; -import Checker from '@/pages/Checker.tsx'; -import Home from '@/pages/Home.tsx'; -import Management from '@/pages/Management.tsx'; +import CheckerPage from '@/pages/CheckerPage.tsx'; +import ErrorPage from '@/pages/ErrorPage.tsx'; +import HomePage from '@/pages/HomePage.tsx'; +import ManagementPage from '@/pages/ManagementPage.tsx'; import App from './App.tsx'; @@ -53,16 +54,17 @@ const router = createBrowserRouter([ path: "/", element: <App />, loader: appLoader, + errorElement: <ErrorPage />, children: [{ path: "home", loader: homeLoader, - element: <Home />, + element: <HomePage />, }, { path: "checker", - element: <Checker />, + element: <CheckerPage />, }, { path: "management", - element: <Management />, + element: <ManagementPage />, }, { path: "*", element: <Navigate to="/home" replace />, diff --git a/frontend/src/pages/Checker.tsx b/frontend/src/pages/CheckerPage.tsx similarity index 97% rename from frontend/src/pages/Checker.tsx rename to frontend/src/pages/CheckerPage.tsx index 0cf67b1..f07ed97 100644 --- a/frontend/src/pages/Checker.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -4,7 +4,7 @@ import InfoList from '@/components/info-list/InfoList'; import SearchBar from '@/components/search-bar/SearchBar'; import { Info } from '@/types/info'; -export default function Checker() { +export default function CheckerPage() { const mappedInfo: Info = { transaction: { diff --git a/frontend/src/pages/ErrorPage.tsx b/frontend/src/pages/ErrorPage.tsx new file mode 100644 index 0000000..68c54f9 --- /dev/null +++ b/frontend/src/pages/ErrorPage.tsx @@ -0,0 +1,20 @@ +import { useRouteError } from 'react-router-dom'; + +interface ErrorType { + statusText: string; + message: string; +} + +export default function ErrorPage() { + const error = useRouteError() as ErrorType; + + return ( + <div> + <h1>Oops!</h1> + <p>Sorry, an unexpected error has occurred.</p> + <p> + <i>{error.statusText || error.message}</i> + </p> + </div> + ); +} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/HomePage.tsx similarity index 97% rename from frontend/src/pages/Home.tsx rename to frontend/src/pages/HomePage.tsx index 97548fa..c3c9603 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -26,7 +26,7 @@ function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: n return blocks; } -export default function Home() { +export default function HomePage() { const isDesktopL = useIsDesktopL(); // use 13 blocks(desktop), otherwise use 20 blocks(XL desktop) const blockNumber = isDesktopL ? 20 : 13; @@ -63,6 +63,12 @@ export default function Home() { getData(); }, [currentUnixTime]); + + if (!loaderData) { + console.log('no data') + return <>...</> + } + if (!initialLastBlock.current) { return <></>; } diff --git a/frontend/src/pages/Management.tsx b/frontend/src/pages/ManagementPage.tsx similarity index 59% rename from frontend/src/pages/Management.tsx rename to frontend/src/pages/ManagementPage.tsx index 78ab4d3..b1ac344 100644 --- a/frontend/src/pages/Management.tsx +++ b/frontend/src/pages/ManagementPage.tsx @@ -1,5 +1,5 @@ -export default function Management() { +export default function ManagementPage() { return ( <div className='text-white'>Management</div> ); -} \ No newline at end of file +} From 466abdfe39bb3ea56a8496bc0770dca01a2bfeb7 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 7 Jul 2023 13:31:12 +1000 Subject: [PATCH 08/87] Loading state --- frontend/src/App.tsx | 12 +++++-- frontend/src/assets/loading.svg | 43 ++++++++++++++++++++++++++ frontend/src/components/images/Svg.tsx | 7 +++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 frontend/src/assets/loading.svg diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5e25cac..5db558d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; -import { Outlet } from 'react-router-dom'; +import { Outlet, useNavigation } from 'react-router-dom'; +import Svg, { SvgNames } from '@/components/images/Svg'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; import { ThemeContext } from '@/contexts/themeContext'; @@ -10,6 +11,7 @@ import { getUnixTime, pollingPeriod } from '@/utils/time'; function App() { const [theme, setTheme] = useState<ThemeModes>('light'); const [currentUnixTime, setCurrentUnixTime] = useState(getUnixTime()); + const navigation = useNavigation(); useEffect(() => { const intervalId = setInterval(() => { @@ -25,7 +27,13 @@ function App() { <div className='relative max-w-[1440px] m-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900'> <Nav /> <main className="mx-6 my-8 grow w-[1146px]"> - <Outlet /> + {navigation.state === 'loading' ? ( + <div className='flex justify-center items-center h-screen'> + <Svg svgName={SvgNames.Loading} sizeClass='w-[100px]' /> + </div> + ) : ( + <Outlet /> + )} </main> </div> </ThemeContext.Provider> diff --git a/frontend/src/assets/loading.svg b/frontend/src/assets/loading.svg new file mode 100644 index 0000000..e2a0816 --- /dev/null +++ b/frontend/src/assets/loading.svg @@ -0,0 +1,43 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + style="margin: auto; display: block; shape-rendering: auto;" + width="195px" height="195px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> + <rect x="12.5" y="12.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="0s" calcMode="discrete"></animate> + </rect> + <rect x="38.5" y="12.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="0.23584905660377356s" + calcMode="discrete"></animate> + </rect> + <rect x="64.5" y="12.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="0.4716981132075471s" + calcMode="discrete"></animate> + </rect> + <rect x="12.5" y="38.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="1.650943396226415s" + calcMode="discrete"></animate> + </rect> + <rect x="64.5" y="38.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="0.7075471698113207s" + calcMode="discrete"></animate> + </rect> + <rect x="12.5" y="64.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="1.4150943396226414s" + calcMode="discrete"></animate> + </rect> + <rect x="38.5" y="64.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="1.1792452830188678s" + calcMode="discrete"></animate> + </rect> + <rect x="64.5" y="64.5" width="23" height="23" fill="#64646f"> + <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" + dur="1.8867924528301885s" repeatCount="indefinite" begin="0.9433962264150942s" + calcMode="discrete"></animate> + </rect> +</svg> diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index 1d1cde2..4265afe 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -1,5 +1,6 @@ import Check from '@/assets/check.svg'; import Cross from '@/assets/cross.svg'; +import Loading from '@/assets/loading.svg'; import Miner from '@/assets/miner.svg'; import Penalty from '@/assets/penalty.svg'; import Rhombus from '@/assets/rhombus.svg'; @@ -146,6 +147,11 @@ export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass }: SvgComponent = Search; alt = 'Search'; break; + + case SvgNames.Loading: + SvgComponent = Loading; + alt = 'Loading data'; + break; } return ( @@ -171,4 +177,5 @@ export enum SvgNames { Standby = 'Standby', Rhombus = 'Rhombus', Search = 'Search', + Loading = 'Loading', } From 498297a81704b8245b6b4bcd62101bf1831a5354 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 7 Jul 2023 14:24:41 +1000 Subject: [PATCH 09/87] Format quotes --- frontend/src/App.tsx | 2 +- frontend/src/assets/blocks/blue-block.svg | 14 ++-- frontend/src/assets/blocks/grey-block.svg | 14 ++-- frontend/src/assets/check.svg | 10 +-- frontend/src/assets/cross.svg | 6 +- frontend/src/assets/loading.svg | 82 +++++++++---------- frontend/src/assets/miner.svg | 19 +++-- frontend/src/assets/penalty.svg | 58 ++++++++----- frontend/src/assets/rhombus.svg | 10 +-- frontend/src/assets/search.svg | 24 +++--- frontend/src/assets/standby.svg | 4 +- frontend/src/components/BlockConnectLine.tsx | 4 +- frontend/src/components/Blocks.tsx | 6 +- .../src/components/blocks-info/BlocksInfo.tsx | 4 +- .../master-node-title/MasterNodeTitle.tsx | 6 +- frontend/src/components/images/BlockImage.tsx | 10 +-- .../src/components/images/CheckerImage.tsx | 8 +- frontend/src/components/images/HouseImage.tsx | 8 +- .../src/components/images/ManagementImage.tsx | 12 +-- frontend/src/components/images/Svg.tsx | 14 ++-- .../src/components/info-cards/InfoCards.tsx | 4 +- .../src/components/info-list/InfoList.tsx | 2 +- frontend/src/components/nav-item/NavItem.tsx | 2 +- frontend/src/components/nav/Nav.tsx | 4 +- .../src/components/search-bar/SearchBar.tsx | 4 +- .../components/theme-switch/ThemeSwitch.tsx | 6 +- frontend/src/components/title/Title.tsx | 2 +- frontend/src/main.tsx | 20 ++--- frontend/src/pages/CheckerPage.tsx | 2 +- frontend/src/pages/HomePage.tsx | 4 +- frontend/src/types/loaderData.d.ts | 6 +- frontend/src/vite-env.d.ts | 2 +- 32 files changed, 199 insertions(+), 174 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5db558d..7402e92 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -26,7 +26,7 @@ function App() { <ThemeContext.Provider value={{ theme, setTheme }}> <div className='relative max-w-[1440px] m-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900'> <Nav /> - <main className="mx-6 my-8 grow w-[1146px]"> + <main className='mx-6 my-8 grow w-[1146px]'> {navigation.state === 'loading' ? ( <div className='flex justify-center items-center h-screen'> <Svg svgName={SvgNames.Loading} sizeClass='w-[100px]' /> diff --git a/frontend/src/assets/blocks/blue-block.svg b/frontend/src/assets/blocks/blue-block.svg index c65a38f..3c244f3 100644 --- a/frontend/src/assets/blocks/blue-block.svg +++ b/frontend/src/assets/blocks/blue-block.svg @@ -1,9 +1,9 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="16 3 37.43 40.45"> - <g clip-path="url(#clip0_364_47333)" filter="url(#filter0_dddddd_364_47333)"> - <path d="M52.2578 12.2987L35.7906 3.27581C35.4601 3.09485 35.0895 3 34.7127 3C34.336 3 33.9654 3.09485 33.6349 3.27581L17.1677 12.2987C16.8144 12.4921 16.5196 12.7771 16.3142 13.1237C16.1088 13.4703 16.0002 13.8658 16 14.2688V32.1797C16.0002 32.5827 16.1088 32.9782 16.3142 33.3248C16.5196 33.6714 16.8144 33.9563 17.1677 34.1498L33.6349 43.1726C33.9651 43.3544 34.3359 43.4498 34.7127 43.4498C35.0896 43.4498 35.4604 43.3544 35.7906 43.1726L52.2578 34.1498C52.6111 33.9563 52.9059 33.6714 53.1113 33.3248C53.3167 32.9782 53.4252 32.5827 53.4255 32.1797V14.2688C53.4252 13.8658 53.3167 13.4703 53.1113 13.1237C52.9059 12.7771 52.6111 12.4921 52.2578 12.2987ZM34.3535 4.59048C34.4633 4.52914 34.587 4.49694 34.7127 4.49694C34.8385 4.49694 34.9622 4.52914 35.072 4.59048L51.3053 13.486L34.7127 22.5725L18.1202 13.486L34.3535 4.59048ZM17.8862 32.837C17.7684 32.7725 17.6701 32.6774 17.6016 32.5617C17.5331 32.4461 17.497 32.3141 17.497 32.1797V14.8512L33.9642 23.8703V41.6445L17.8862 32.837ZM51.5392 32.837L35.4612 41.6389V23.8703L51.9285 14.8512V32.1797C51.9282 32.3138 51.8919 32.4454 51.8234 32.5607C51.7549 32.676 51.6568 32.7707 51.5392 32.8351V32.837Z" fill="#B9E5FF"/> - <path d="M34.3544 4.59037C34.4642 4.52903 34.5879 4.49683 34.7136 4.49683C34.8394 4.49683 34.9631 4.52903 35.0729 4.59037L51.3062 13.4859L34.7136 22.5724L18.1211 13.4859L34.3544 4.59037Z" fill="#B9E5FF"/> - <path d="M17.8853 32.8371C17.7675 32.7726 17.6692 32.6775 17.6007 32.5618C17.5322 32.4462 17.4961 32.3142 17.4961 32.1798V14.8513L33.9633 23.8704V41.6446L17.8853 32.8371Z" fill="#B9E5FF"/> - <path d="M51.5389 32.8371L35.4609 41.639V23.8704L51.9282 14.8513V32.1798C51.9279 32.3139 51.8916 32.4455 51.8231 32.5608C51.7546 32.6761 51.6565 32.7708 51.5389 32.8352V32.8371Z" fill="#B9E5FF"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M33.4415 3.32898C33.831 3.11316 34.2683 3 34.7127 3C35.1572 3 35.5944 3.11316 35.9839 3.32898L51.8555 12.1239C52.3748 12.4117 52.7913 12.8566 53.046 13.3959L53.1692 13.6566L53.1599 13.6616C53.3351 14.1158 53.4254 14.5996 53.4254 15.0886V31.5808C53.4254 31.9968 53.3467 32.409 53.1935 32.7952C52.9156 33.4954 52.4061 34.0773 51.7514 34.4422L36.3591 43.0217C35.8551 43.3026 35.2886 43.45 34.7127 43.45H34.4125V43.4366C33.941 43.3943 33.4823 43.2526 33.0667 43.0196L17.7139 34.4115C17.1002 34.0674 16.6099 33.5368 16.3129 32.8952C16.1068 32.4501 16 31.9648 16 31.4736V15.1326C16 14.5385 16.1292 13.9516 16.3784 13.4132C16.6329 12.8634 17.0544 12.4096 17.5819 12.1173L33.4415 3.32898ZM35.0129 42.8285C35.3825 42.7884 35.7415 42.6744 36.0686 42.4921L51.4609 33.9126C51.9944 33.6152 52.4096 33.141 52.636 32.5704C52.7609 32.2557 52.825 31.9198 52.825 31.5808V15.0886C52.825 14.6994 52.7574 14.314 52.626 13.9498L35.0129 23.4567V42.8285ZM34.7126 22.9322L52.3646 13.4043C52.1652 13.0927 51.8913 12.8344 51.5663 12.6543L35.6947 3.85937C35.3938 3.69265 35.056 3.60524 34.7127 3.60524C34.3694 3.60524 34.0316 3.69265 33.7308 3.85937L17.8711 12.6477C17.5387 12.8319 17.2597 13.0975 17.0586 13.418L34.7126 22.9322ZM16.8015 13.9657C16.6687 14.3394 16.6004 14.7341 16.6004 15.1326V31.4736C16.6004 31.8764 16.688 32.2743 16.857 32.6393C17.1005 33.1653 17.5025 33.6004 18.0058 33.8826L33.3585 42.4907C33.6848 42.6736 34.0433 42.7881 34.4125 42.8284V23.4568L16.8015 13.9657Z" fill="#00A2FF"/> +<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='16 3 37.43 40.45'> + <g clip-path='url(#clip0_364_47333)' filter='url(#filter0_dddddd_364_47333)'> + <path d='M52.2578 12.2987L35.7906 3.27581C35.4601 3.09485 35.0895 3 34.7127 3C34.336 3 33.9654 3.09485 33.6349 3.27581L17.1677 12.2987C16.8144 12.4921 16.5196 12.7771 16.3142 13.1237C16.1088 13.4703 16.0002 13.8658 16 14.2688V32.1797C16.0002 32.5827 16.1088 32.9782 16.3142 33.3248C16.5196 33.6714 16.8144 33.9563 17.1677 34.1498L33.6349 43.1726C33.9651 43.3544 34.3359 43.4498 34.7127 43.4498C35.0896 43.4498 35.4604 43.3544 35.7906 43.1726L52.2578 34.1498C52.6111 33.9563 52.9059 33.6714 53.1113 33.3248C53.3167 32.9782 53.4252 32.5827 53.4255 32.1797V14.2688C53.4252 13.8658 53.3167 13.4703 53.1113 13.1237C52.9059 12.7771 52.6111 12.4921 52.2578 12.2987ZM34.3535 4.59048C34.4633 4.52914 34.587 4.49694 34.7127 4.49694C34.8385 4.49694 34.9622 4.52914 35.072 4.59048L51.3053 13.486L34.7127 22.5725L18.1202 13.486L34.3535 4.59048ZM17.8862 32.837C17.7684 32.7725 17.6701 32.6774 17.6016 32.5617C17.5331 32.4461 17.497 32.3141 17.497 32.1797V14.8512L33.9642 23.8703V41.6445L17.8862 32.837ZM51.5392 32.837L35.4612 41.6389V23.8703L51.9285 14.8512V32.1797C51.9282 32.3138 51.8919 32.4454 51.8234 32.5607C51.7549 32.676 51.6568 32.7707 51.5392 32.8351V32.837Z' fill='#B9E5FF'/> + <path d='M34.3544 4.59037C34.4642 4.52903 34.5879 4.49683 34.7136 4.49683C34.8394 4.49683 34.9631 4.52903 35.0729 4.59037L51.3062 13.4859L34.7136 22.5724L18.1211 13.4859L34.3544 4.59037Z' fill='#B9E5FF'/> + <path d='M17.8853 32.8371C17.7675 32.7726 17.6692 32.6775 17.6007 32.5618C17.5322 32.4462 17.4961 32.3142 17.4961 32.1798V14.8513L33.9633 23.8704V41.6446L17.8853 32.8371Z' fill='#B9E5FF'/> + <path d='M51.5389 32.8371L35.4609 41.639V23.8704L51.9282 14.8513V32.1798C51.9279 32.3139 51.8916 32.4455 51.8231 32.5608C51.7546 32.6761 51.6565 32.7708 51.5389 32.8352V32.8371Z' fill='#B9E5FF'/> + <path fill-rule='evenodd' clip-rule='evenodd' d='M33.4415 3.32898C33.831 3.11316 34.2683 3 34.7127 3C35.1572 3 35.5944 3.11316 35.9839 3.32898L51.8555 12.1239C52.3748 12.4117 52.7913 12.8566 53.046 13.3959L53.1692 13.6566L53.1599 13.6616C53.3351 14.1158 53.4254 14.5996 53.4254 15.0886V31.5808C53.4254 31.9968 53.3467 32.409 53.1935 32.7952C52.9156 33.4954 52.4061 34.0773 51.7514 34.4422L36.3591 43.0217C35.8551 43.3026 35.2886 43.45 34.7127 43.45H34.4125V43.4366C33.941 43.3943 33.4823 43.2526 33.0667 43.0196L17.7139 34.4115C17.1002 34.0674 16.6099 33.5368 16.3129 32.8952C16.1068 32.4501 16 31.9648 16 31.4736V15.1326C16 14.5385 16.1292 13.9516 16.3784 13.4132C16.6329 12.8634 17.0544 12.4096 17.5819 12.1173L33.4415 3.32898ZM35.0129 42.8285C35.3825 42.7884 35.7415 42.6744 36.0686 42.4921L51.4609 33.9126C51.9944 33.6152 52.4096 33.141 52.636 32.5704C52.7609 32.2557 52.825 31.9198 52.825 31.5808V15.0886C52.825 14.6994 52.7574 14.314 52.626 13.9498L35.0129 23.4567V42.8285ZM34.7126 22.9322L52.3646 13.4043C52.1652 13.0927 51.8913 12.8344 51.5663 12.6543L35.6947 3.85937C35.3938 3.69265 35.056 3.60524 34.7127 3.60524C34.3694 3.60524 34.0316 3.69265 33.7308 3.85937L17.8711 12.6477C17.5387 12.8319 17.2597 13.0975 17.0586 13.418L34.7126 22.9322ZM16.8015 13.9657C16.6687 14.3394 16.6004 14.7341 16.6004 15.1326V31.4736C16.6004 31.8764 16.688 32.2743 16.857 32.6393C17.1005 33.1653 17.5025 33.6004 18.0058 33.8826L33.3585 42.4907C33.6848 42.6736 34.0433 42.7881 34.4125 42.8284V23.4568L16.8015 13.9657Z' fill='#00A2FF'/> </g> </svg> \ No newline at end of file diff --git a/frontend/src/assets/blocks/grey-block.svg b/frontend/src/assets/blocks/grey-block.svg index 91a867c..38fd75b 100644 --- a/frontend/src/assets/blocks/grey-block.svg +++ b/frontend/src/assets/blocks/grey-block.svg @@ -1,9 +1,9 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="16 3 37.43 40.45"> - <g clip-path="url(#clip0_364_47333)" filter="url(#filter0_dddddd_364_47333)"> - <path d="M52.2578 12.2987L35.7906 3.27581C35.4601 3.09485 35.0895 3 34.7127 3C34.336 3 33.9654 3.09485 33.6349 3.27581L17.1677 12.2987C16.8144 12.4921 16.5196 12.7771 16.3142 13.1237C16.1088 13.4703 16.0002 13.8658 16 14.2688V32.1797C16.0002 32.5827 16.1088 32.9782 16.3142 33.3248C16.5196 33.6714 16.8144 33.9563 17.1677 34.1498L33.6349 43.1726C33.9651 43.3544 34.3359 43.4498 34.7127 43.4498C35.0896 43.4498 35.4604 43.3544 35.7906 43.1726L52.2578 34.1498C52.6111 33.9563 52.9059 33.6714 53.1113 33.3248C53.3167 32.9782 53.4252 32.5827 53.4255 32.1797V14.2688C53.4252 13.8658 53.3167 13.4703 53.1113 13.1237C52.9059 12.7771 52.6111 12.4921 52.2578 12.2987ZM34.3535 4.59048C34.4633 4.52914 34.587 4.49694 34.7127 4.49694C34.8385 4.49694 34.9622 4.52914 35.072 4.59048L51.3053 13.486L34.7127 22.5725L18.1202 13.486L34.3535 4.59048ZM17.8862 32.837C17.7684 32.7725 17.6701 32.6774 17.6016 32.5617C17.5331 32.4461 17.497 32.3141 17.497 32.1797V14.8512L33.9642 23.8703V41.6445L17.8862 32.837ZM51.5392 32.837L35.4612 41.6389V23.8703L51.9285 14.8512V32.1797C51.9282 32.3138 51.8919 32.4454 51.8234 32.5607C51.7549 32.676 51.6568 32.7707 51.5392 32.8351V32.837Z" fill="#DBE4E9"/> - <path d="M34.3544 4.59037C34.4642 4.52903 34.5879 4.49683 34.7136 4.49683C34.8394 4.49683 34.9631 4.52903 35.0729 4.59037L51.3062 13.4859L34.7136 22.5724L18.1211 13.4859L34.3544 4.59037Z" fill="#DBE4E9"/> - <path d="M17.8853 32.8371C17.7675 32.7726 17.6692 32.6775 17.6007 32.5618C17.5322 32.4462 17.4961 32.3142 17.4961 32.1798V14.8513L33.9633 23.8704V41.6446L17.8853 32.8371Z" fill="#DBE4E9"/> - <path d="M51.5389 32.8371L35.4609 41.639V23.8704L51.9282 14.8513V32.1798C51.9279 32.3139 51.8916 32.4455 51.8231 32.5608C51.7546 32.6761 51.6565 32.7708 51.5389 32.8352V32.8371Z" fill="#DBE4E9"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M33.4415 3.32898C33.831 3.11316 34.2683 3 34.7127 3C35.1572 3 35.5944 3.11316 35.9839 3.32898L51.8555 12.1239C52.3748 12.4117 52.7913 12.8566 53.046 13.3959L53.1692 13.6566L53.1599 13.6616C53.3351 14.1158 53.4254 14.5996 53.4254 15.0886V31.5808C53.4254 31.9968 53.3467 32.409 53.1935 32.7952C52.9156 33.4954 52.4061 34.0773 51.7514 34.4422L36.3591 43.0217C35.8551 43.3026 35.2886 43.45 34.7127 43.45H34.4125V43.4366C33.941 43.3943 33.4823 43.2526 33.0667 43.0196L17.7139 34.4115C17.1002 34.0674 16.6099 33.5368 16.3129 32.8952C16.1068 32.4501 16 31.9648 16 31.4736V15.1326C16 14.5385 16.1292 13.9516 16.3784 13.4132C16.6329 12.8634 17.0544 12.4096 17.5819 12.1173L33.4415 3.32898ZM35.0129 42.8285C35.3825 42.7884 35.7415 42.6744 36.0686 42.4921L51.4609 33.9126C51.9944 33.6152 52.4096 33.141 52.636 32.5704C52.7609 32.2557 52.825 31.9198 52.825 31.5808V15.0886C52.825 14.6994 52.7574 14.314 52.626 13.9498L35.0129 23.4567V42.8285ZM34.7126 22.9322L52.3646 13.4043C52.1652 13.0927 51.8913 12.8344 51.5663 12.6543L35.6947 3.85937C35.3938 3.69265 35.056 3.60524 34.7127 3.60524C34.3694 3.60524 34.0316 3.69265 33.7308 3.85937L17.8711 12.6477C17.5387 12.8319 17.2597 13.0975 17.0586 13.418L34.7126 22.9322ZM16.8015 13.9657C16.6687 14.3394 16.6004 14.7341 16.6004 15.1326V31.4736C16.6004 31.8764 16.688 32.2743 16.857 32.6393C17.1005 33.1653 17.5025 33.6004 18.0058 33.8826L33.3585 42.4907C33.6848 42.6736 34.0433 42.7881 34.4125 42.8284V23.4568L16.8015 13.9657Z" fill="#5188CB"/> +<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='16 3 37.43 40.45'> + <g clip-path='url(#clip0_364_47333)' filter='url(#filter0_dddddd_364_47333)'> + <path d='M52.2578 12.2987L35.7906 3.27581C35.4601 3.09485 35.0895 3 34.7127 3C34.336 3 33.9654 3.09485 33.6349 3.27581L17.1677 12.2987C16.8144 12.4921 16.5196 12.7771 16.3142 13.1237C16.1088 13.4703 16.0002 13.8658 16 14.2688V32.1797C16.0002 32.5827 16.1088 32.9782 16.3142 33.3248C16.5196 33.6714 16.8144 33.9563 17.1677 34.1498L33.6349 43.1726C33.9651 43.3544 34.3359 43.4498 34.7127 43.4498C35.0896 43.4498 35.4604 43.3544 35.7906 43.1726L52.2578 34.1498C52.6111 33.9563 52.9059 33.6714 53.1113 33.3248C53.3167 32.9782 53.4252 32.5827 53.4255 32.1797V14.2688C53.4252 13.8658 53.3167 13.4703 53.1113 13.1237C52.9059 12.7771 52.6111 12.4921 52.2578 12.2987ZM34.3535 4.59048C34.4633 4.52914 34.587 4.49694 34.7127 4.49694C34.8385 4.49694 34.9622 4.52914 35.072 4.59048L51.3053 13.486L34.7127 22.5725L18.1202 13.486L34.3535 4.59048ZM17.8862 32.837C17.7684 32.7725 17.6701 32.6774 17.6016 32.5617C17.5331 32.4461 17.497 32.3141 17.497 32.1797V14.8512L33.9642 23.8703V41.6445L17.8862 32.837ZM51.5392 32.837L35.4612 41.6389V23.8703L51.9285 14.8512V32.1797C51.9282 32.3138 51.8919 32.4454 51.8234 32.5607C51.7549 32.676 51.6568 32.7707 51.5392 32.8351V32.837Z' fill='#DBE4E9'/> + <path d='M34.3544 4.59037C34.4642 4.52903 34.5879 4.49683 34.7136 4.49683C34.8394 4.49683 34.9631 4.52903 35.0729 4.59037L51.3062 13.4859L34.7136 22.5724L18.1211 13.4859L34.3544 4.59037Z' fill='#DBE4E9'/> + <path d='M17.8853 32.8371C17.7675 32.7726 17.6692 32.6775 17.6007 32.5618C17.5322 32.4462 17.4961 32.3142 17.4961 32.1798V14.8513L33.9633 23.8704V41.6446L17.8853 32.8371Z' fill='#DBE4E9'/> + <path d='M51.5389 32.8371L35.4609 41.639V23.8704L51.9282 14.8513V32.1798C51.9279 32.3139 51.8916 32.4455 51.8231 32.5608C51.7546 32.6761 51.6565 32.7708 51.5389 32.8352V32.8371Z' fill='#DBE4E9'/> + <path fill-rule='evenodd' clip-rule='evenodd' d='M33.4415 3.32898C33.831 3.11316 34.2683 3 34.7127 3C35.1572 3 35.5944 3.11316 35.9839 3.32898L51.8555 12.1239C52.3748 12.4117 52.7913 12.8566 53.046 13.3959L53.1692 13.6566L53.1599 13.6616C53.3351 14.1158 53.4254 14.5996 53.4254 15.0886V31.5808C53.4254 31.9968 53.3467 32.409 53.1935 32.7952C52.9156 33.4954 52.4061 34.0773 51.7514 34.4422L36.3591 43.0217C35.8551 43.3026 35.2886 43.45 34.7127 43.45H34.4125V43.4366C33.941 43.3943 33.4823 43.2526 33.0667 43.0196L17.7139 34.4115C17.1002 34.0674 16.6099 33.5368 16.3129 32.8952C16.1068 32.4501 16 31.9648 16 31.4736V15.1326C16 14.5385 16.1292 13.9516 16.3784 13.4132C16.6329 12.8634 17.0544 12.4096 17.5819 12.1173L33.4415 3.32898ZM35.0129 42.8285C35.3825 42.7884 35.7415 42.6744 36.0686 42.4921L51.4609 33.9126C51.9944 33.6152 52.4096 33.141 52.636 32.5704C52.7609 32.2557 52.825 31.9198 52.825 31.5808V15.0886C52.825 14.6994 52.7574 14.314 52.626 13.9498L35.0129 23.4567V42.8285ZM34.7126 22.9322L52.3646 13.4043C52.1652 13.0927 51.8913 12.8344 51.5663 12.6543L35.6947 3.85937C35.3938 3.69265 35.056 3.60524 34.7127 3.60524C34.3694 3.60524 34.0316 3.69265 33.7308 3.85937L17.8711 12.6477C17.5387 12.8319 17.2597 13.0975 17.0586 13.418L34.7126 22.9322ZM16.8015 13.9657C16.6687 14.3394 16.6004 14.7341 16.6004 15.1326V31.4736C16.6004 31.8764 16.688 32.2743 16.857 32.6393C17.1005 33.1653 17.5025 33.6004 18.0058 33.8826L33.3585 42.4907C33.6848 42.6736 34.0433 42.7881 34.4125 42.8284V23.4568L16.8015 13.9657Z' fill='#5188CB'/> </g> </svg> \ No newline at end of file diff --git a/frontend/src/assets/check.svg b/frontend/src/assets/check.svg index c6109d8..e2e5f00 100644 --- a/frontend/src/assets/check.svg +++ b/frontend/src/assets/check.svg @@ -1,8 +1,8 @@ -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'> <path - d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" - fill="#147B4A" /> + d='M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z' + fill='#147B4A' /> <path - d="M8 12L9.45139 13.4514C10.1755 14.1755 10.5375 14.5375 10.9781 14.5176C11.4187 14.4976 11.7465 14.1042 12.402 13.3175L16 9" - stroke="#F2F3F3" stroke-width="2" stroke-linecap="round" /> + d='M8 12L9.45139 13.4514C10.1755 14.1755 10.5375 14.5375 10.9781 14.5176C11.4187 14.4976 11.7465 14.1042 12.402 13.3175L16 9' + stroke='#F2F3F3' stroke-width='2' stroke-linecap='round' /> </svg> \ No newline at end of file diff --git a/frontend/src/assets/cross.svg b/frontend/src/assets/cross.svg index 01d5118..2e3980f 100644 --- a/frontend/src/assets/cross.svg +++ b/frontend/src/assets/cross.svg @@ -1,4 +1,4 @@ -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" fill="#A03A1A"/> -<path d="M9 15L15 9M15 15L9 9" stroke="#F2F3F3" stroke-width="2" stroke-linecap="round"/> +<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'> +<path d='M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z' fill='#A03A1A'/> +<path d='M9 15L15 9M15 15L9 9' stroke='#F2F3F3' stroke-width='2' stroke-linecap='round'/> </svg> diff --git a/frontend/src/assets/loading.svg b/frontend/src/assets/loading.svg index e2a0816..eb37760 100644 --- a/frontend/src/assets/loading.svg +++ b/frontend/src/assets/loading.svg @@ -1,43 +1,43 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - style="margin: auto; display: block; shape-rendering: auto;" - width="195px" height="195px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> - <rect x="12.5" y="12.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="0s" calcMode="discrete"></animate> - </rect> - <rect x="38.5" y="12.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="0.23584905660377356s" - calcMode="discrete"></animate> - </rect> - <rect x="64.5" y="12.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="0.4716981132075471s" - calcMode="discrete"></animate> - </rect> - <rect x="12.5" y="38.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="1.650943396226415s" - calcMode="discrete"></animate> - </rect> - <rect x="64.5" y="38.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="0.7075471698113207s" - calcMode="discrete"></animate> - </rect> - <rect x="12.5" y="64.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="1.4150943396226414s" - calcMode="discrete"></animate> - </rect> - <rect x="38.5" y="64.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="1.1792452830188678s" - calcMode="discrete"></animate> - </rect> - <rect x="64.5" y="64.5" width="23" height="23" fill="#64646f"> - <animate attributeName="fill" values="#6ac3f8;#64646f;#64646f" keyTimes="0;0.125;1" - dur="1.8867924528301885s" repeatCount="indefinite" begin="0.9433962264150942s" - calcMode="discrete"></animate> +<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' + style='margin: auto; display: block; shape-rendering: auto;' + width='195px' height='195px' viewBox='0 0 100 100' preserveAspectRatio='xMidYMid'> + <rect x='12.5' y='12.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='0s' calcMode='discrete'></animate> + </rect> + <rect x='38.5' y='12.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='0.23584905660377356s' + calcMode='discrete'></animate> + </rect> + <rect x='64.5' y='12.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='0.4716981132075471s' + calcMode='discrete'></animate> + </rect> + <rect x='12.5' y='38.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='1.650943396226415s' + calcMode='discrete'></animate> + </rect> + <rect x='64.5' y='38.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='0.7075471698113207s' + calcMode='discrete'></animate> + </rect> + <rect x='12.5' y='64.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='1.4150943396226414s' + calcMode='discrete'></animate> + </rect> + <rect x='38.5' y='64.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='1.1792452830188678s' + calcMode='discrete'></animate> + </rect> + <rect x='64.5' y='64.5' width='23' height='23' fill='#64646f'> + <animate attributeName='fill' values='#6ac3f8;#64646f;#64646f' keyTimes='0;0.125;1' + dur='1.8867924528301885s' repeatCount='indefinite' begin='0.9433962264150942s' + calcMode='discrete'></animate> </rect> </svg> diff --git a/frontend/src/assets/miner.svg b/frontend/src/assets/miner.svg index 7de5086..d23be94 100644 --- a/frontend/src/assets/miner.svg +++ b/frontend/src/assets/miner.svg @@ -1,7 +1,12 @@ -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> - <!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools --> -<svg fill="#0ea5e9" width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke="#0ea5e9"> - <g id="SVGRepo_bgCarrier" stroke-width="0"/> - <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> - <g id="SVGRepo_iconCarrier"> <g id="Mining_Cart"> <g id="XMLID_888_"> <path d="M358.371,398.16c-18.273,0-33.14,14.867-33.14,33.141c0,18.273,14.867,33.14,33.14,33.14 c18.274,0,33.141-14.867,33.141-33.14C391.512,413.027,376.645,398.16,358.371,398.16z M365.419,438.345 c-1.863,1.852-4.424,2.919-7.054,2.919c-2.62,0-5.181-1.066-7.044-2.919c-1.854-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.424-2.919,7.044-2.919c2.63,0,5.19,1.066,7.054,2.919 c1.852,1.853,2.909,4.424,2.909,7.044C368.328,433.921,367.272,436.492,365.419,438.345z" id="XMLID_891_"/> <polygon id="XMLID_892_" points="412.531,152 325.531,47.571 260.958,115.1 297.843,152 "/> <polygon id="XMLID_893_" points="180.569,62.905 91.474,152 269.664,152 "/> <path d="M153.629,398.16c-18.274,0-33.141,14.867-33.141,33.141c0,18.273,14.867,33.14,33.141,33.14 c18.273,0,33.14-14.867,33.14-33.14C186.77,413.027,171.902,398.16,153.629,398.16z M160.667,438.345 c-1.852,1.852-4.413,2.919-7.033,2.919c-2.631,0-5.2-1.066-7.054-2.919c-1.853-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.423-2.919,7.054-2.919c2.62,0,5.181,1.066,7.033,2.919 c1.863,1.853,2.929,4.424,2.929,7.044C163.597,433.921,162.53,436.492,160.667,438.345z" id="XMLID_896_"/> <polygon id="XMLID_897_" points="45.254,271 466.746,271 475.526,227 36.474,227 "/> <path d="M433.82,172c-0.004,0-0.008,0-0.011,0c-0.003,0-0.006,0-0.009,0H67.428c-0.005,0-0.01,0-0.015,0H2v34h508 v-34H433.82z" id="XMLID_898_"/> <path d="M75.586,422h25.833c4.466-25,26.169-43.632,52.21-43.632c26.04,0,47.743,18.632,52.209,43.632h100.324 c4.466-25,26.169-43.632,52.209-43.632c26.04,0,47.744,18.632,52.21,43.632h25.833l26.341-131H49.245L75.586,422z" id="XMLID_899_"/> </g> </g> <g id="Layer_1"/> </g> - </svg> \ No newline at end of file +<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'> + +<!-- Uploa'ed to: 'VG Repo' www.'vgrepo.c'm, Tr'nsformed 'y: SVG Repo'Mixer Too's -'>'''''''' +<svg fi'l="#0ea5e9" width'"800px" height'"'00px" viewBox="0 0 512 512" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke="#0ea5e9"> +'''''' +<g id="'VGRepo_bgCarrier" s'roke-wid'h="0"/>''''''''''''''''''''''''''''''''' + +<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> + +<g id="SVGRepo_iconCarrier"> <g id="Mining_Cart"> <g id="XMLID_888_"> <path d="M358.371,398.16c-18.273,0-33.14,14.867-33.14,33.141c0,18.273,14.867,33.14,33.14,33.14 c18.274,0,33.141-14.867,33.141-33.14C391.512,413.027,376.645,398.16,358.371,398.16z M365.419,438.345 c-1.863,1.852-4.424,2.919-7.054,2.919c-2.62,0-5.181-1.066-7.044-2.919c-1.854-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.424-2.919,7.044-2.919c2.63,0,5.19,1.066,7.054,2.919 c1.852,1.853,2.909,4.424,2.909,7.044C368.328,433.921,367.272,436.492,365.419,438.345z" id="XMLID_891_"/> <polygon id="XMLID_892_" points="412.531,152 325.531,47.571 260.958,115.1 297.843,152 "/> <polygon id="XMLID_893_" points="180.569,62.905 91.474,152 269.664,152 "/> <path d="M153.629,398.16c-18.274,0-33.141,14.867-33.141,33.141c0,18.273,14.867,33.14,33.141,33.14 c18.273,0,33.14-14.867,33.14-33.14C186.77,413.027,171.902,398.16,153.629,398.16z M160.667,438.345 c-1.852,1.852-4.413,2.919-7.033,2.919c-2.631,0-5.2-1.066-7.054-2.919c-1.853-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.423-2.919,7.054-2.919c2.62,0,5.181,1.066,7.033,2.919 c1.863,1.853,2.929,4.424,2.929,7.044C163.597,433.921,162.53,436.492,160.667,438.345z" id="XMLID_896_"/> <polygon id="XMLID_897_" points="45.254,271 466.746,271 475.526,227 36.474,227 "/> <path d="M433.82,172c-0.004,0-0.008,0-0.011,0c-0.003,0-0.006,0-0.009,0H67.428c-0.005,0-0.01,0-0.015,0H2v34h508 v-34H433.82z" id="XMLID_898_"/> <path d="M75.586,422h25.833c4.466-25,26.169-43.632,52.21-43.632c26.04,0,47.743,18.632,52.209,43.632h100.324 c4.466-25,26.169-43.632,52.209-43.632c26.04,0,47.744,18.632,52.21,43.632h25.833l26.341-131H49.245L75.586,422z" id="XMLID_899_"/> </g> </g> <g id="Layer_1"/> </g> + +</svg> \ No newline at end of file diff --git a/frontend/src/assets/penalty.svg b/frontend/src/assets/penalty.svg index 458cd04..c017898 100644 --- a/frontend/src/assets/penalty.svg +++ b/frontend/src/assets/penalty.svg @@ -1,20 +1,38 @@ -<?xml version="1.0" encoding="utf-8"?> - <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg width="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet"> - <path d="M69.8 88.1l1.8-80c.1-2.2-1.8-4.1-4-4.1H55.9c-2.3 0-4.1 1.9-4 4.1l1.8 80c.1 2.2 1.8 3.9 4 3.9h8c2.2 0 4-1.7 4.1-3.9z" fill="#f44336"> - </path> - <g fill="#ffffff"> - <path d="M64 50c.3-1.9-.5-26.2-.5-26.2s.1-3.5-2.8-3.8c-2.8-.3-3.8 2.3-3.8 4.1c0 1.8.4 21.5.6 23.3c.1 1.8 1.9 3.7 3.6 4.2s2.7-.2 2.9-1.6z" opacity=".2"> - </path> - <circle cx="59.6" cy="13.4" r="3.3" opacity=".2"> - </circle> - </g> - <circle cx="64.1" cy="112" r="12" fill="#c33"> - </circle> - <circle cx="62.4" cy="112" r="10.3" fill="#f44336"> - </circle> - <path d="M56.5 108.4c1.2-1.8 3.8-3.3 6.5-3.7c.7-.1 1.3-.1 1.9.1c.4.2.8.6.5 1c-.2.4-.7.5-1.1.6c-2.5.7-4.8 2.4-6.2 4.4c-.5.8-1.4 2.9-2.4 2.4c-1-.7-.7-2.6.8-4.8z" opacity=".2" fill="#ffffff"> - </path> - <path d="M71.9 4h-4.3c2.3 0 4.1 1.9 4 4.1l-1.8 80c-.1 2.2-1.8 3.9-4 3.9H70c2.2 0 4-1.7 4-3.9l1.8-80C76 5.9 74.1 4 71.9 4z" fill="#c33"> - </path> - </svg> \ No newline at end of file +<?xml version='1.0' encoding='utf-8'?> + +<!-- Upload'd to:'SVG Repo' www.'vgrepo.co', Generator' SVG Re'o Mixer Tools -->''''''''''' +<svg widt'="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/199'/xlink' aria-h'dden="true" role="img" class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet"> + +<path d="'69.8 88'1l1.8-80c.1-2.2-1.8-4.1-4-4.1H55.9c-2.3 0-4.1 1.9-4 4.1l1.8 80c.1 2.2 1.8 3.9 4 3.9h8c2.2 0 4-1.7 4.1-3.9z" fill="#f44336"> +'''' +</path> +'''''''' +<g fill="#ffffff"> + +<path d="M64'50c.'-1.9'.5-'6.2'.5'26.2s.'-3.5'2.8-3.8c-2.8-.3-3.8 2.3-3.8 4.1c0 1.8.4 21.5.6 23.3c.1 1.8 1.9 3.7 3.6 4.2s2.7-.2 2.9-1.6z" opacity=".2"> + +</path>'''''''' + +<circle c'="59.6" cy="13.4" r="3.3" opacity=".2">''''' + +</circle>'''' + +</g> + +<circle cx="64.1" cy="112" r="12" fill="#c33"> + +</circle> + +<circle cx="62.4" cy="112" r="10.3" fill="#f44336"> + +</circle> + +<path d="M56.5 108.4c1.2-1.8 3.8-3.3 6.5-3.7c.7-.1 1.3-.1 1.9.1c.4.2.8.6.5 1c-.2.4-.7.5-1.1.6c-2.5.7-4.8 2.4-6.2 4.4c-.5.8-1.4 2.9-2.4 2.4c-1-.7-.7-2.6.8-4.8z" opacity=".2" fill="#ffffff"> + +</path> + +<path d="M71.9 4h-4.3c2.3 0 4.1 1.9 4 4.1l-1.8 80c-.1 2.2-1.8 3.9-4 3.9H70c2.2 0 4-1.7 4-3.9l1.8-80C76 5.9 74.1 4 71.9 4z" fill="#c33"> + +</path> + +</svg> \ No newline at end of file diff --git a/frontend/src/assets/rhombus.svg b/frontend/src/assets/rhombus.svg index 0d6a1a9..1e030ea 100644 --- a/frontend/src/assets/rhombus.svg +++ b/frontend/src/assets/rhombus.svg @@ -1,10 +1,10 @@ -<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> -<g clip-path="url(#clip0_364_47344)"> -<path d="M1.41391 8.41418C0.632863 7.63313 0.632863 6.3668 1.41391 5.58575L5.65655 1.34311C6.4376 0.562062 7.70393 0.562062 8.48498 1.34311L12.7276 5.58575C13.5087 6.3668 13.5087 7.63313 12.7276 8.41418L8.48498 12.6568C7.70393 13.4379 6.4376 13.4379 5.65655 12.6568L1.41391 8.41418Z" fill="#6A85EE"/> +<svg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'> +<g clip-path='url(#clip0_364_47344)'> +<path d='M1.41391 8.41418C0.632863 7.63313 0.632863 6.3668 1.41391 5.58575L5.65655 1.34311C6.4376 0.562062 7.70393 0.562062 8.48498 1.34311L12.7276 5.58575C13.5087 6.3668 13.5087 7.63313 12.7276 8.41418L8.48498 12.6568C7.70393 13.4379 6.4376 13.4379 5.65655 12.6568L1.41391 8.41418Z' fill='#6A85EE'/> </g> <defs> -<clipPath id="clip0_364_47344"> -<rect width="14" height="14" fill="white"/> +<clipPath id='clip0_364_47344'> +<rect width='14' height='14' fill='white'/> </clipPath> </defs> </svg> diff --git a/frontend/src/assets/search.svg b/frontend/src/assets/search.svg index e3374cc..b7dcc1d 100644 --- a/frontend/src/assets/search.svg +++ b/frontend/src/assets/search.svg @@ -1,21 +1,21 @@ -<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> - <g clip-path="url(#clip0_364_47529)"> +<svg width='48' height='48' viewBox='0 0 48 48' fill='none' xmlns='http://www.w3.org/2000/svg'> + <g clip-path='url(#clip0_364_47529)'> <path - d="M0 24C0 10.7452 10.7452 0 24 0C37.2548 0 48 10.7452 48 24C48 37.2548 37.2548 48 24 48C10.7452 48 0 37.2548 0 24Z" - fill="#00A2FF" /> - <mask id="mask0_364_47529" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="12" y="12" - width="24" height="24"> - <path d="M36 12H12V36H36V12Z" fill="white" /> + d='M0 24C0 10.7452 10.7452 0 24 0C37.2548 0 48 10.7452 48 24C48 37.2548 37.2548 48 24 48C10.7452 48 0 37.2548 0 24Z' + fill='#00A2FF' /> + <mask id='mask0_364_47529' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='12' y='12' + width='24' height='24'> + <path d='M36 12H12V36H36V12Z' fill='white' /> </mask> - <g mask="url(#mask0_364_47529)"> + <g mask='url(#mask0_364_47529)'> <path - d="M27.5006 25.9999H26.7106L26.4306 25.7299C27.6306 24.3299 28.2506 22.4199 27.9106 20.3899C27.4406 17.6099 25.1206 15.3899 22.3206 15.0499C18.0906 14.5299 14.5306 18.0899 15.0506 22.3199C15.3906 25.1199 17.6106 27.4399 20.3906 27.9099C22.4206 28.2499 24.3306 27.6299 25.7306 26.4299L26.0006 26.7099V27.4999L30.2506 31.7499C30.6606 32.1599 31.3306 32.1599 31.7406 31.7499C32.1506 31.3399 32.1506 30.6699 31.7406 30.2599L27.5006 25.9999ZM21.5006 25.9999C19.0106 25.9999 17.0006 23.9899 17.0006 21.4999C17.0006 19.0099 19.0106 16.9999 21.5006 16.9999C23.9906 16.9999 26.0006 19.0099 26.0006 21.4999C26.0006 23.9899 23.9906 25.9999 21.5006 25.9999Z" - fill="white" /> + d='M27.5006 25.9999H26.7106L26.4306 25.7299C27.6306 24.3299 28.2506 22.4199 27.9106 20.3899C27.4406 17.6099 25.1206 15.3899 22.3206 15.0499C18.0906 14.5299 14.5306 18.0899 15.0506 22.3199C15.3906 25.1199 17.6106 27.4399 20.3906 27.9099C22.4206 28.2499 24.3306 27.6299 25.7306 26.4299L26.0006 26.7099V27.4999L30.2506 31.7499C30.6606 32.1599 31.3306 32.1599 31.7406 31.7499C32.1506 31.3399 32.1506 30.6699 31.7406 30.2599L27.5006 25.9999ZM21.5006 25.9999C19.0106 25.9999 17.0006 23.9899 17.0006 21.4999C17.0006 19.0099 19.0106 16.9999 21.5006 16.9999C23.9906 16.9999 26.0006 19.0099 26.0006 21.4999C26.0006 23.9899 23.9906 25.9999 21.5006 25.9999Z' + fill='white' /> </g> </g> <defs> - <clipPath id="clip0_364_47529"> - <rect width="48" height="48" fill="white" /> + <clipPath id='clip0_364_47529'> + <rect width='48' height='48' fill='white' /> </clipPath> </defs> </svg> \ No newline at end of file diff --git a/frontend/src/assets/standby.svg b/frontend/src/assets/standby.svg index f0fb4be..8ad746e 100644 --- a/frontend/src/assets/standby.svg +++ b/frontend/src/assets/standby.svg @@ -1,3 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version='1.0' encoding='utf-8'?> <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M511.3 512.1m-483.5 0a483.5 483.5 0 1 0 967 0 483.5 483.5 0 1 0-967 0Z" fill="#89B7F5" /><path d="M511.3 1010.7c-67.3 0-132.6-13.2-194.1-39.2-59.4-25.1-112.7-61.1-158.5-106.8C113 818.9 77.1 765.6 52 706.2c-26-61.5-39.2-126.8-39.2-194.1S26 379.5 52 318.1c25.1-59.4 61.1-112.7 106.8-158.5s99.1-81.7 158.5-106.8c61.5-26 126.8-39.2 194.1-39.2S644 26.8 705.5 52.8c59.4 25.1 112.7 61.1 158.5 106.8 45.8 45.8 81.7 99.1 106.8 158.5 26 61.5 39.2 126.8 39.2 194.1s-13.2 132.6-39.2 194.1C945.7 765.7 909.7 819 864 864.8c-45.8 45.8-99.1 81.7-158.5 106.8-61.6 25.9-126.9 39.1-194.2 39.1z m0-967.1C448.1 43.6 386.7 56 329 80.4c-55.8 23.6-105.9 57.4-149 100.4s-76.8 93.2-100.4 149c-24.4 57.8-36.8 119.1-36.8 182.4s12.4 124.6 36.8 182.4C103.2 750.3 137 800.4 180 843.4c43 43 93.1 76.8 148.9 100.4 57.8 24.4 119.1 36.8 182.4 36.8 63.3 0 124.6-12.4 182.4-36.8 55.8-23.6 105.9-57.4 148.9-100.4 43-43 76.8-93.1 100.4-148.9 24.4-57.8 36.8-119.1 36.8-182.4S967.4 387.5 943 329.7c-23.6-55.8-57.4-105.9-100.4-148.9-43-43-93.1-76.8-148.9-100.4C635.9 56 574.6 43.6 511.3 43.6z" fill="#0F53A8" /><path d="M511.3 512.1m-396.3 0a396.3 396.3 0 1 0 792.6 0 396.3 396.3 0 1 0-792.6 0Z" fill="#B6CDEF" /><path d="M511.3 923.4c-55.5 0-109.4-10.9-160.1-32.3-49-20.7-93-50.4-130.7-88.1s-67.4-81.8-88.1-130.7C110.9 621.5 100 567.6 100 512.1s10.9-109.4 32.3-160.1c20.7-49 50.4-93 88.1-130.7 37.8-37.8 81.8-67.4 130.7-88.1 50.7-21.5 104.6-32.3 160.1-32.3s109.4 10.9 160.1 32.3c49 20.7 93 50.4 130.7 88.1 37.8 37.8 67.4 81.8 88.1 130.7 21.5 50.7 32.3 104.6 32.3 160.1s-10.9 109.4-32.3 160.1c-20.7 49-50.4 93-88.1 130.7s-81.8 67.4-130.7 88.1c-50.6 21.6-104.4 32.4-160 32.4z m0-792.6c-51.5 0-101.4 10.1-148.4 30-45.4 19.2-86.2 46.7-121.2 81.7s-62.5 75.8-81.7 121.2c-19.9 47-30 96.9-30 148.4s10.1 101.4 30 148.4c19.2 45.4 46.7 86.2 81.7 121.2s75.8 62.5 121.2 81.7c47 19.9 96.9 30 148.4 30s101.4-10.1 148.4-30c45.4-19.2 86.2-46.7 121.2-81.7s62.5-75.8 81.7-121.2c19.9-47 30-96.9 30-148.4s-10.1-101.4-30-148.4c-19.2-45.4-46.7-86.2-81.7-121.2s-75.8-62.5-121.2-81.7c-47-19.9-96.9-30-148.4-30z" fill="#0F53A8" /><path d="M511.3 246.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM511.3 854.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM709.6 328.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 3-6.7 4.4-10.6 4.4zM279.7 758.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 2.9-6.8 4.4-10.6 4.4zM838.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15zM230.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15z" fill="#0F53A8" /><path d="M743 758.8c-3.8 0-7.7-1.5-10.6-4.4L699 721c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 2.9-6.8 4.4-10.6 4.4zM313 328.8c-3.8 0-7.7-1.5-10.6-4.4L269 291c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.7 4.4-10.6 4.4z" fill="#0F53A8" /><path d="M493 512.1l18.3-239.4 18.4 239.4z" fill="#B6CDEF" /><path d="M529.7 527.1H493c-4.2 0-8.2-1.7-11-4.8s-4.3-7.2-4-11.3l18.3-239.5c0.6-7.8 7.1-13.9 15-13.9 7.8 0 14.4 6 15 13.9L544.6 511c0.3 4.2-1.1 8.3-4 11.3s-6.8 4.8-10.9 4.8z m-20.5-30h4.3l-2.1-27.9-2.2 27.9z" fill="#0F53A8" /><path d="M517.5 499.7l138.9 162-162-138.9z" fill="#B6CDEF" /><path d="M656.4 676.7c-3.5 0-6.9-1.2-9.8-3.6l-162-138.9c-3.2-2.7-5.1-6.6-5.2-10.8-0.2-4.2 1.4-8.2 4.4-11.2l23-23c3-3 7-4.5 11.2-4.4 4.2 0.2 8.1 2.1 10.8 5.2l138.9 162c5.1 6 4.8 14.8-0.8 20.4-2.8 2.8-6.6 4.3-10.5 4.3zM516.5 521.9l0.9 0.8-0.8-0.9-0.1 0.1z" fill="#0F53A8" /><path d="M242 616.5l266-104.4" fill="#B6CDEF" /><path d="M242 631.5c-6 0-11.6-3.6-14-9.5-3-7.7 0.8-16.4 8.5-19.4l266-104.3c7.7-3 16.4 0.8 19.4 8.5s-0.8 16.4-8.5 19.4l-266 104.3c-1.7 0.6-3.6 1-5.4 1z" fill="#0F53A8" /><path d="M511.3 512.1m-30.1 0a30.1 30.1 0 1 0 60.2 0 30.1 30.1 0 1 0-60.2 0Z" fill="#B6CDEF" /><path d="M511.3 557.3c-24.9 0-45.1-20.2-45.1-45.1 0-24.9 20.2-45.1 45.1-45.1s45.1 20.2 45.1 45.1c0.1 24.8-20.2 45.1-45.1 45.1z m0-60.3c-8.3 0-15.1 6.8-15.1 15.1s6.8 15.1 15.1 15.1 15.1-6.8 15.1-15.1c0.1-8.3-6.7-15.1-15.1-15.1z" fill="#0F53A8" /></svg> \ No newline at end of file +<svg width='800px' height='800px' viewBox='0 0 1024 1024' class='icon' version='1.1' xmlns='http://www.w3.org/2000/svg'><path d='M511.3 512.1m-483.5 0a483.5 483.5 0 1 0 967 0 483.5 483.5 0 1 0-967 0Z' fill='#89B7F5' /><path d='M511.3 1010.7c-67.3 0-132.6-13.2-194.1-39.2-59.4-25.1-112.7-61.1-158.5-106.8C113 818.9 77.1 765.6 52 706.2c-26-61.5-39.2-126.8-39.2-194.1S26 379.5 52 318.1c25.1-59.4 61.1-112.7 106.8-158.5s99.1-81.7 158.5-106.8c61.5-26 126.8-39.2 194.1-39.2S644 26.8 705.5 52.8c59.4 25.1 112.7 61.1 158.5 106.8 45.8 45.8 81.7 99.1 106.8 158.5 26 61.5 39.2 126.8 39.2 194.1s-13.2 132.6-39.2 194.1C945.7 765.7 909.7 819 864 864.8c-45.8 45.8-99.1 81.7-158.5 106.8-61.6 25.9-126.9 39.1-194.2 39.1z m0-967.1C448.1 43.6 386.7 56 329 80.4c-55.8 23.6-105.9 57.4-149 100.4s-76.8 93.2-100.4 149c-24.4 57.8-36.8 119.1-36.8 182.4s12.4 124.6 36.8 182.4C103.2 750.3 137 800.4 180 843.4c43 43 93.1 76.8 148.9 100.4 57.8 24.4 119.1 36.8 182.4 36.8 63.3 0 124.6-12.4 182.4-36.8 55.8-23.6 105.9-57.4 148.9-100.4 43-43 76.8-93.1 100.4-148.9 24.4-57.8 36.8-119.1 36.8-182.4S967.4 387.5 943 329.7c-23.6-55.8-57.4-105.9-100.4-148.9-43-43-93.1-76.8-148.9-100.4C635.9 56 574.6 43.6 511.3 43.6z' fill='#0F53A8' /><path d='M511.3 512.1m-396.3 0a396.3 396.3 0 1 0 792.6 0 396.3 396.3 0 1 0-792.6 0Z' fill='#B6CDEF' /><path d='M511.3 923.4c-55.5 0-109.4-10.9-160.1-32.3-49-20.7-93-50.4-130.7-88.1s-67.4-81.8-88.1-130.7C110.9 621.5 100 567.6 100 512.1s10.9-109.4 32.3-160.1c20.7-49 50.4-93 88.1-130.7 37.8-37.8 81.8-67.4 130.7-88.1 50.7-21.5 104.6-32.3 160.1-32.3s109.4 10.9 160.1 32.3c49 20.7 93 50.4 130.7 88.1 37.8 37.8 67.4 81.8 88.1 130.7 21.5 50.7 32.3 104.6 32.3 160.1s-10.9 109.4-32.3 160.1c-20.7 49-50.4 93-88.1 130.7s-81.8 67.4-130.7 88.1c-50.6 21.6-104.4 32.4-160 32.4z m0-792.6c-51.5 0-101.4 10.1-148.4 30-45.4 19.2-86.2 46.7-121.2 81.7s-62.5 75.8-81.7 121.2c-19.9 47-30 96.9-30 148.4s10.1 101.4 30 148.4c19.2 45.4 46.7 86.2 81.7 121.2s75.8 62.5 121.2 81.7c47 19.9 96.9 30 148.4 30s101.4-10.1 148.4-30c45.4-19.2 86.2-46.7 121.2-81.7s62.5-75.8 81.7-121.2c19.9-47 30-96.9 30-148.4s-10.1-101.4-30-148.4c-19.2-45.4-46.7-86.2-81.7-121.2s-75.8-62.5-121.2-81.7c-47-19.9-96.9-30-148.4-30z' fill='#0F53A8' /><path d='M511.3 246.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM511.3 854.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM709.6 328.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 3-6.7 4.4-10.6 4.4zM279.7 758.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 2.9-6.8 4.4-10.6 4.4zM838.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15zM230.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15z' fill='#0F53A8' /><path d='M743 758.8c-3.8 0-7.7-1.5-10.6-4.4L699 721c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 2.9-6.8 4.4-10.6 4.4zM313 328.8c-3.8 0-7.7-1.5-10.6-4.4L269 291c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.7 4.4-10.6 4.4z' fill='#0F53A8' /><path d='M493 512.1l18.3-239.4 18.4 239.4z' fill='#B6CDEF' /><path d='M529.7 527.1H493c-4.2 0-8.2-1.7-11-4.8s-4.3-7.2-4-11.3l18.3-239.5c0.6-7.8 7.1-13.9 15-13.9 7.8 0 14.4 6 15 13.9L544.6 511c0.3 4.2-1.1 8.3-4 11.3s-6.8 4.8-10.9 4.8z m-20.5-30h4.3l-2.1-27.9-2.2 27.9z' fill='#0F53A8' /><path d='M517.5 499.7l138.9 162-162-138.9z' fill='#B6CDEF' /><path d='M656.4 676.7c-3.5 0-6.9-1.2-9.8-3.6l-162-138.9c-3.2-2.7-5.1-6.6-5.2-10.8-0.2-4.2 1.4-8.2 4.4-11.2l23-23c3-3 7-4.5 11.2-4.4 4.2 0.2 8.1 2.1 10.8 5.2l138.9 162c5.1 6 4.8 14.8-0.8 20.4-2.8 2.8-6.6 4.3-10.5 4.3zM516.5 521.9l0.9 0.8-0.8-0.9-0.1 0.1z' fill='#0F53A8' /><path d='M242 616.5l266-104.4' fill='#B6CDEF' /><path d='M242 631.5c-6 0-11.6-3.6-14-9.5-3-7.7 0.8-16.4 8.5-19.4l266-104.3c7.7-3 16.4 0.8 19.4 8.5s-0.8 16.4-8.5 19.4l-266 104.3c-1.7 0.6-3.6 1-5.4 1z' fill='#0F53A8' /><path d='M511.3 512.1m-30.1 0a30.1 30.1 0 1 0 60.2 0 30.1 30.1 0 1 0-60.2 0Z' fill='#B6CDEF' /><path d='M511.3 557.3c-24.9 0-45.1-20.2-45.1-45.1 0-24.9 20.2-45.1 45.1-45.1s45.1 20.2 45.1 45.1c0.1 24.8-20.2 45.1-45.1 45.1z m0-60.3c-8.3 0-15.1 6.8-15.1 15.1s6.8 15.1 15.1 15.1 15.1-6.8 15.1-15.1c0.1-8.3-6.7-15.1-15.1-15.1z' fill='#0F53A8' /></svg> \ No newline at end of file diff --git a/frontend/src/components/BlockConnectLine.tsx b/frontend/src/components/BlockConnectLine.tsx index 7490199..a1cee49 100644 --- a/frontend/src/components/BlockConnectLine.tsx +++ b/frontend/src/components/BlockConnectLine.tsx @@ -7,8 +7,8 @@ export default function BlockConnectLine({ showConfirmedColour }: BlockConnectLi return ( <div> - <svg className={`${strokeClass} w-[8px] m-[5px]`} viewBox="0 0 8 3" xmlns="http://www.w3.org/2000/svg"> - <path d="M1.54346 1.2251H9.54346" stroke="current" strokeWidth="2" strokeLinecap="round" /> + <svg className={`${strokeClass} w-[8px] m-[5px]`} viewBox='0 0 8 3' xmlns='http://www.w3.org/2000/svg'> + <path d='M1.54346 1.2251H9.54346' stroke='current' strokeWidth='2' strokeLinecap='round' /> </svg> </div> ); diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index a520266..33a63e5 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -1,6 +1,8 @@ +import { Fragment } from 'react'; + import BlockConnectLine from '@/components/BlockConnectLine'; import BlockImage from '@/components/images/BlockImage'; -import { Fragment } from 'react'; + import styles from './blocks.module.scss'; export interface Block { @@ -81,7 +83,7 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock </div> {/* 'Block 1' text */} - <div className="absolute top-[120px] left-0 text-primary flex text-lg"> + <div className='absolute top-[120px] left-0 text-primary flex text-lg'> <div>Block</div> <div className='pl-1'>1</div> </div> diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index fc136c6..e89bb7a 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -1,5 +1,5 @@ import { - BlockCell, BlocksInfoItem + BlockCell, BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import { cellWith } from '@/components/blocks-info/constants'; import { MasterNodeTitle } from '@/components/blocks-info/master-node-title/MasterNodeTitle'; @@ -25,7 +25,7 @@ export default function BlocksInfo({ title, data, fetchMoreData }: BlocksInfoPro ) : ( <Title title={title} /> )} - <div className="mt-6 h-[400px] overflow-hidden hover:overflow-auto relative dark:text-text-dark-100"> + <div className='mt-6 h-[400px] overflow-hidden hover:overflow-auto relative dark:text-text-dark-100'> <> <BlocksInfoHeading type={data[0].type} /> {fetchMoreData ? ( diff --git a/frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx b/frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx index 299e904..071c17a 100644 --- a/frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx +++ b/frontend/src/components/blocks-info/master-node-title/MasterNodeTitle.tsx @@ -12,15 +12,15 @@ export function MasterNodeTitle({ title }: MasterNodeTitleProps) { <div className='flex flex-col dark:text-text-dark-400'> <div className='flex'> <Svg svgName={SvgNames.Miner} /> - <span className="pl-2.5">Master Node</span> + <span className='pl-2.5'>Master Node</span> </div> <div className='pt-2.5 flex'> <Svg svgName={SvgNames.Penalty} /> - <span className="pl-2.5">Penalty</span> + <span className='pl-2.5'>Penalty</span> </div> <div className='pt-2.5 flex'> <Svg svgName={SvgNames.Standby} /> - <span className="pl-2.5">Candidate</span> + <span className='pl-2.5'>Candidate</span> </div> </div> </div> diff --git a/frontend/src/components/images/BlockImage.tsx b/frontend/src/components/images/BlockImage.tsx index c157a2c..2df1411 100644 --- a/frontend/src/components/images/BlockImage.tsx +++ b/frontend/src/components/images/BlockImage.tsx @@ -29,8 +29,8 @@ export default function BlockImage(props: BlockImageProps) { </> ) : ( <> - <img src={GreyBlock} alt="" className={`z-30 absolute left-0 top-0`} /> - <img src={BlueBlock} alt="" className={`z-40 absolute left-0 top-0 ${styles.animate} ${block.confirmed ? styles.show : styles.hide}`} /> + <img src={GreyBlock} alt='' className={`z-30 absolute left-0 top-0`} /> + <img src={BlueBlock} alt='' className={`z-40 absolute left-0 top-0 ${styles.animate} ${block.confirmed ? styles.show : styles.hide}`} /> </> )} <StatusBrace {...props} /> @@ -48,7 +48,7 @@ function StatusBrace({ isLastConfirmed, isFirstUnConfirmed, isLast }: BlockImage } return ( - <div className="absolute -top-[54px] -right-[10px] -left-[10px] border-t-2 dark:border-text-white-800" /> + <div className='absolute -top-[54px] -right-[10px] -left-[10px] border-t-2 dark:border-text-white-800' /> ); } @@ -67,14 +67,14 @@ function BraceEnd() { function BlockNumber({ block, isLastConfirmed, isLast }: BlockImageProps) { if (isLastConfirmed) { return ( - <div className="absolute top-[64px] right-0 text-primary flex"> + <div className='absolute top-[64px] right-0 text-primary flex'> <div>Block</div> <div className='pl-1'>{block.number}</div> </div> ); } else if (isLast) { return ( - <div className="absolute top-[64px] right-0 text-primary flex"> + <div className='absolute top-[64px] right-0 text-primary flex'> <div>Block</div> <div className='pl-1'>{block.number}</div> </div> diff --git a/frontend/src/components/images/CheckerImage.tsx b/frontend/src/components/images/CheckerImage.tsx index 0cc7dd0..67d0476 100644 --- a/frontend/src/components/images/CheckerImage.tsx +++ b/frontend/src/components/images/CheckerImage.tsx @@ -1,4 +1,4 @@ -import { NavImageProps } from "@/components/nav-item/NavItem"; +import { NavImageProps } from '@/components/nav-item/NavItem'; export default function CheckerImage({ isActive }: NavImageProps) { const strokeClass = isActive @@ -7,9 +7,9 @@ export default function CheckerImage({ isActive }: NavImageProps) { return ( <div className='w-[24px] h-[24px]'> - <svg className={strokeClass} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path d="M20 12V17C20 18.8856 20 19.8284 19.4142 20.4142C18.8284 21 17.8856 21 16 21H6.5C5.11929 21 4 19.8807 4 18.5V18.5C4 17.1193 5.11929 16 6.5 16H16C17.8856 16 18.8284 16 19.4142 15.4142C20 14.8284 20 13.8856 20 12V7C20 5.11438 20 4.17157 19.4142 3.58579C18.8284 3 17.8856 3 16 3H8C6.11438 3 5.17157 3 4.58579 3.58579C4 4.17157 4 5.11438 4 7V18.5" strokeWidth="2" /> - <path d="M9 10L10.2929 11.2929C10.6834 11.6834 11.3166 11.6834 11.7071 11.2929L15 8" strokeWidth="2" strokeLinecap="round" /> + <svg className={strokeClass} viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'> + <path d='M20 12V17C20 18.8856 20 19.8284 19.4142 20.4142C18.8284 21 17.8856 21 16 21H6.5C5.11929 21 4 19.8807 4 18.5V18.5C4 17.1193 5.11929 16 6.5 16H16C17.8856 16 18.8284 16 19.4142 15.4142C20 14.8284 20 13.8856 20 12V7C20 5.11438 20 4.17157 19.4142 3.58579C18.8284 3 17.8856 3 16 3H8C6.11438 3 5.17157 3 4.58579 3.58579C4 4.17157 4 5.11438 4 7V18.5' strokeWidth='2' /> + <path d='M9 10L10.2929 11.2929C10.6834 11.6834 11.3166 11.6834 11.7071 11.2929L15 8' strokeWidth='2' strokeLinecap='round' /> </svg> </div> ); diff --git a/frontend/src/components/images/HouseImage.tsx b/frontend/src/components/images/HouseImage.tsx index dc6a2e3..7329417 100644 --- a/frontend/src/components/images/HouseImage.tsx +++ b/frontend/src/components/images/HouseImage.tsx @@ -1,4 +1,4 @@ -import { NavImageProps } from "@/components/nav-item/NavItem"; +import { NavImageProps } from '@/components/nav-item/NavItem'; export default function HouseImage({ isActive }: NavImageProps) { const strokeClass = isActive @@ -7,9 +7,9 @@ export default function HouseImage({ isActive }: NavImageProps) { return ( <div className='w-[24px] h-[24px]'> - <svg className={strokeClass} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path d="M5 12.7595C5 11.4018 5 10.7229 5.27446 10.1262C5.54892 9.52943 6.06437 9.08763 7.09525 8.20401L8.09525 7.34687C9.95857 5.74974 10.8902 4.95117 12 4.95117C13.1098 4.95117 14.0414 5.74974 15.9047 7.34687L16.9047 8.20401C17.9356 9.08763 18.4511 9.52943 18.7255 10.1262C19 10.7229 19 11.4018 19 12.7595V16.9999C19 18.8856 19 19.8284 18.4142 20.4142C17.8284 20.9999 16.8856 20.9999 15 20.9999H9C7.11438 20.9999 6.17157 20.9999 5.58579 20.4142C5 19.8284 5 18.8856 5 16.9999V12.7595Z" strokeWidth="2" /> - <path d="M14.5 21V16C14.5 15.4477 14.0523 15 13.5 15H10.5C9.94772 15 9.5 15.4477 9.5 16V21" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> + <svg className={strokeClass} viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'> + <path d='M5 12.7595C5 11.4018 5 10.7229 5.27446 10.1262C5.54892 9.52943 6.06437 9.08763 7.09525 8.20401L8.09525 7.34687C9.95857 5.74974 10.8902 4.95117 12 4.95117C13.1098 4.95117 14.0414 5.74974 15.9047 7.34687L16.9047 8.20401C17.9356 9.08763 18.4511 9.52943 18.7255 10.1262C19 10.7229 19 11.4018 19 12.7595V16.9999C19 18.8856 19 19.8284 18.4142 20.4142C17.8284 20.9999 16.8856 20.9999 15 20.9999H9C7.11438 20.9999 6.17157 20.9999 5.58579 20.4142C5 19.8284 5 18.8856 5 16.9999V12.7595Z' strokeWidth='2' /> + <path d='M14.5 21V16C14.5 15.4477 14.0523 15 13.5 15H10.5C9.94772 15 9.5 15.4477 9.5 16V21' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' /> </svg> </div> ); diff --git a/frontend/src/components/images/ManagementImage.tsx b/frontend/src/components/images/ManagementImage.tsx index 9609a61..c50d58f 100644 --- a/frontend/src/components/images/ManagementImage.tsx +++ b/frontend/src/components/images/ManagementImage.tsx @@ -1,4 +1,4 @@ -import { NavImageProps } from "@/components/nav-item/NavItem"; +import { NavImageProps } from '@/components/nav-item/NavItem'; export default function ManagementImage({ isActive }: NavImageProps) { const strokeClass = isActive @@ -8,16 +8,16 @@ export default function ManagementImage({ isActive }: NavImageProps) { return ( <div className='w-[24px] h-[24px]'> <svg className={strokeClass} - viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" + viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' > <path - d="M11 6.5C11 5.67157 10.3284 5 9.5 5V5C8.67157 5 8 5.67157 8 6.5V19.5C8 20.3284 8.67157 21 9.5 21V21C10.3284 21 11 20.3284 11 19.5V6.5Z" /> + d='M11 6.5C11 5.67157 10.3284 5 9.5 5V5C8.67157 5 8 5.67157 8 6.5V19.5C8 20.3284 8.67157 21 9.5 21V21C10.3284 21 11 20.3284 11 19.5V6.5Z' /> <path - d="M6 14.5C6 13.6716 5.32843 13 4.5 13V13C3.67157 13 3 13.6716 3 14.5V19.5C3 20.3284 3.67157 21 4.5 21V21C5.32843 21 6 20.3284 6 19.5V14.5Z" /> + d='M6 14.5C6 13.6716 5.32843 13 4.5 13V13C3.67157 13 3 13.6716 3 14.5V19.5C3 20.3284 3.67157 21 4.5 21V21C5.32843 21 6 20.3284 6 19.5V14.5Z' /> <path - d="M13 9.5C13 8.67157 13.6716 8 14.5 8V8C15.3284 8 16 8.67157 16 9.5V19.5C16 20.3284 15.3284 21 14.5 21V21C13.6716 21 13 20.3284 13 19.5V9.5Z" /> + d='M13 9.5C13 8.67157 13.6716 8 14.5 8V8C15.3284 8 16 8.67157 16 9.5V19.5C16 20.3284 15.3284 21 14.5 21V21C13.6716 21 13 20.3284 13 19.5V9.5Z' /> <path - d="M21 12.5C21 11.6716 20.3284 11 19.5 11V11C18.6716 11 18 11.6716 18 12.5V19.5C18 20.3284 18.6716 21 19.5 21V21C20.3284 21 21 20.3284 21 19.5V12.5Z" /> + d='M21 12.5C21 11.6716 20.3284 11 19.5 11V11C18.6716 11 18 11.6716 18 12.5V19.5C18 20.3284 18.6716 21 19.5 21V21C20.3284 21 21 20.3284 21 19.5V12.5Z' /> </svg> </div> ); diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index 4265afe..882609b 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -13,8 +13,8 @@ interface GeneralSvgProps { function Fallback({ colour }: GeneralSvgProps) { return ( - <svg width="24" height="24" viewBox="0 0 24 24" className={colour} - xmlns="http://www.w3.org/2000/svg"> + <svg width='24' height='24' viewBox='0 0 24 24' className={colour} + xmlns='http://www.w3.org/2000/svg'> </svg> ); } @@ -24,10 +24,10 @@ function Fallback({ colour }: GeneralSvgProps) { */ function Moon({ colour }: GeneralSvgProps) { return ( - <svg width="24" height="24" viewBox="0 0 24 24" className={colour} - xmlns="http://www.w3.org/2000/svg"> + <svg width='24' height='24' viewBox='0 0 24 24' className={colour} + xmlns='http://www.w3.org/2000/svg'> <path - d="M11.01 3.05001C6.51 3.54001 3 7.36001 3 12C3 16.97 7.03 21 12 21C16.63 21 20.45 17.5 20.95 13C21.04 12.21 20.17 11.58 19.41 12.05C18.57 12.59 17.57 12.9 16.5 12.9C13.52 12.9 11.1 10.48 11.1 7.50001C11.1 6.44001 11.41 5.44001 11.94 4.61001C12.39 3.94001 11.9 2.98001 11.01 3.05001Z" + d='M11.01 3.05001C6.51 3.54001 3 7.36001 3 12C3 16.97 7.03 21 12 21C16.63 21 20.45 17.5 20.95 13C21.04 12.21 20.17 11.58 19.41 12.05C18.57 12.59 17.57 12.9 16.5 12.9C13.52 12.9 11.1 10.48 11.1 7.50001C11.1 6.44001 11.41 5.44001 11.94 4.61001C12.39 3.94001 11.9 2.98001 11.01 3.05001Z' /> </svg> ); @@ -35,8 +35,8 @@ function Moon({ colour }: GeneralSvgProps) { function Sun({ colour }: GeneralSvgProps) { return ( - <svg width="24" height="24" viewBox="0 0 24 24" className={colour} xmlns="http://www.w3.org/2000/svg"> - <path d="M12 7C9.24 7 7 9.24 7 12C7 14.76 9.24 17 12 17C14.76 17 17 14.76 17 12C17 9.24 14.76 7 12 7ZM2 13H4C4.55 13 5 12.55 5 12C5 11.45 4.55 11 4 11H2C1.45 11 1 11.45 1 12C1 12.55 1.45 13 2 13ZM20 13H22C22.55 13 23 12.55 23 12C23 11.45 22.55 11 22 11H20C19.45 11 19 11.45 19 12C19 12.55 19.45 13 20 13ZM11 2V4C11 4.55 11.45 5 12 5C12.55 5 13 4.55 13 4V2C13 1.45 12.55 1 12 1C11.45 1 11 1.45 11 2ZM11 20V22C11 22.55 11.45 23 12 23C12.55 23 13 22.55 13 22V20C13 19.45 12.55 19 12 19C11.45 19 11 19.45 11 20ZM5.99 4.58C5.6 4.19 4.96 4.19 4.58 4.58C4.19 4.97 4.19 5.61 4.58 5.99L5.64 7.05C6.03 7.44 6.67 7.44 7.05 7.05C7.43 6.66 7.44 6.02 7.05 5.64L5.99 4.58ZM18.36 16.95C17.97 16.56 17.33 16.56 16.95 16.95C16.56 17.34 16.56 17.98 16.95 18.36L18.01 19.42C18.4 19.81 19.04 19.81 19.42 19.42C19.81 19.03 19.81 18.39 19.42 18.01L18.36 16.95ZM19.42 5.99C19.81 5.6 19.81 4.96 19.42 4.58C19.03 4.19 18.39 4.19 18.01 4.58L16.95 5.64C16.56 6.03 16.56 6.67 16.95 7.05C17.34 7.43 17.98 7.44 18.36 7.05L19.42 5.99ZM7.05 18.36C7.44 17.97 7.44 17.33 7.05 16.95C6.66 16.56 6.02 16.56 5.64 16.95L4.58 18.01C4.19 18.4 4.19 19.04 4.58 19.42C4.97 19.8 5.61 19.81 5.99 19.42L7.05 18.36Z" /> + <svg width='24' height='24' viewBox='0 0 24 24' className={colour} xmlns='http://www.w3.org/2000/svg'> + <path d='M12 7C9.24 7 7 9.24 7 12C7 14.76 9.24 17 12 17C14.76 17 17 14.76 17 12C17 9.24 14.76 7 12 7ZM2 13H4C4.55 13 5 12.55 5 12C5 11.45 4.55 11 4 11H2C1.45 11 1 11.45 1 12C1 12.55 1.45 13 2 13ZM20 13H22C22.55 13 23 12.55 23 12C23 11.45 22.55 11 22 11H20C19.45 11 19 11.45 19 12C19 12.55 19.45 13 20 13ZM11 2V4C11 4.55 11.45 5 12 5C12.55 5 13 4.55 13 4V2C13 1.45 12.55 1 12 1C11.45 1 11 1.45 11 2ZM11 20V22C11 22.55 11.45 23 12 23C12.55 23 13 22.55 13 22V20C13 19.45 12.55 19 12 19C11.45 19 11 19.45 11 20ZM5.99 4.58C5.6 4.19 4.96 4.19 4.58 4.58C4.19 4.97 4.19 5.61 4.58 5.99L5.64 7.05C6.03 7.44 6.67 7.44 7.05 7.05C7.43 6.66 7.44 6.02 7.05 5.64L5.99 4.58ZM18.36 16.95C17.97 16.56 17.33 16.56 16.95 16.95C16.56 17.34 16.56 17.98 16.95 18.36L18.01 19.42C18.4 19.81 19.04 19.81 19.42 19.42C19.81 19.03 19.81 18.39 19.42 18.01L18.36 16.95ZM19.42 5.99C19.81 5.6 19.81 4.96 19.42 4.58C19.03 4.19 18.39 4.19 18.01 4.58L16.95 5.64C16.56 6.03 16.56 6.67 16.95 7.05C17.34 7.43 17.98 7.44 18.36 7.05L19.42 5.99ZM7.05 18.36C7.44 17.97 7.44 17.33 7.05 16.95C6.66 16.56 6.02 16.56 5.64 16.95L4.58 18.01C4.19 18.4 4.19 19.04 4.58 19.42C4.97 19.8 5.61 19.81 5.99 19.42L7.05 18.36Z' /> </svg> ); } diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index bacc11d..870dd92 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -86,7 +86,7 @@ export default function InfoCards() { return ( <> - <div className="grid grid-cols-2 llg:grid-cols-3 gap-6"> + <div className='grid grid-cols-2 llg:grid-cols-3 gap-6'> <Card> <InfoList title='Network Info' @@ -110,7 +110,7 @@ export default function InfoCards() { </Card> </div> - <div className="grid grid-cols-1 llg:grid-cols-2 gap-6"> + <div className='grid grid-cols-1 llg:grid-cols-2 gap-6'> <Card className='max-w-[565px]'> <BlocksInfo title='Recent Blocks' data={recentBlocks} fetchMoreData={fetchMoreRecentBlocks} enableInfinite /> </Card> diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index b53bc95..5039341 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -11,7 +11,7 @@ interface InfoListProps { export default function InfoList({ title, status, info }: InfoListProps) { return ( <> - <div className="flex justify-between items-center pb-6"> + <div className='flex justify-between items-center pb-6'> <Title title={title} /> {status && ( <div className='inline-flex items-center'> diff --git a/frontend/src/components/nav-item/NavItem.tsx b/frontend/src/components/nav-item/NavItem.tsx index 3c95a17..1cd093b 100644 --- a/frontend/src/components/nav-item/NavItem.tsx +++ b/frontend/src/components/nav-item/NavItem.tsx @@ -27,7 +27,7 @@ export default function NavItem({ text, Image, className, page }: NavItemProps) }}> {({ isActive }) => ( <> - <div className="mt-[-2px]"><Image isActive={isActive} /></div> + <div className='mt-[-2px]'><Image isActive={isActive} /></div> <span className='pl-2.5'>{text}</span> </> )} diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index 7f753eb..151c549 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -16,11 +16,11 @@ export default function Nav(): JSX.Element { return ( <nav id='nav' className='sticky top-0 dark:bg-bg-dark-1000 w-[246px] h-[1024px] shrink-0 shadow-grey flex flex-col justify-between'> <div> - <div className="flex items-center flex-col border-b-[1px] border-text-white dark:border-border-light"> + <div className='flex items-center flex-col border-b-[1px] border-text-white dark:border-border-light'> <div className='pt-12 font-bold text-[26px]'> Logo: TODO </div> - <div className="py-6 dark:text-sky-300"> + <div className='py-6 dark:text-sky-300'> {loaderData.name} </div> </div> diff --git a/frontend/src/components/search-bar/SearchBar.tsx b/frontend/src/components/search-bar/SearchBar.tsx index 9a64a3e..5ee390f 100644 --- a/frontend/src/components/search-bar/SearchBar.tsx +++ b/frontend/src/components/search-bar/SearchBar.tsx @@ -10,7 +10,7 @@ export default function SearchBar() { } return ( - <div className="flex justify-between border-2 border-text-white-400 dark:border-none rounded-full pl-4 pr-2.5 py-2.5 dark:bg-bg-dark-800"> + <div className='flex justify-between border-2 border-text-white-400 dark:border-none rounded-full pl-4 pr-2.5 py-2.5 dark:bg-bg-dark-800'> <input type='text' className={`pl-3 grow text-base leading-tight outline-none @@ -19,7 +19,7 @@ export default function SearchBar() { } value={searchText} onChange={e => setSearchText(e.target.value)} - placeholder="Block Height, Block Hash, TX Hash" + placeholder='Block Height, Block Hash, TX Hash' /> <button className='-m-1.5' onClick={search}> <Svg svgName={SvgNames.Search} sizeClass='w-[48px] h-[48px]' /> diff --git a/frontend/src/components/theme-switch/ThemeSwitch.tsx b/frontend/src/components/theme-switch/ThemeSwitch.tsx index 24767ad..851b6dd 100644 --- a/frontend/src/components/theme-switch/ThemeSwitch.tsx +++ b/frontend/src/components/theme-switch/ThemeSwitch.tsx @@ -12,17 +12,17 @@ export default function ThemeSwitch() { if (selectedTheme === 'dark') { window.document.body.classList.add('dark'); - document.body.style.backgroundColor = "black"; + document.body.style.backgroundColor = 'black'; return; } window.document.body.classList.remove('dark'); - document.body.style.backgroundColor = "white"; + document.body.style.backgroundColor = 'white'; }, [setTheme, selectedTheme]); return ( <div className='shadow-grey m-6 w-[188px] dark:bg-bg-dark-900 dark:border-0 border-2 rounded-full'> - <div className="flex justify-between px-[6px]"> + <div className='flex justify-between px-[6px]'> <ThemeItem selected={selectedTheme === 'light'} changeTheme={() => setSelectedTheme('light')}> <InlineSvg svgName={InlineSvgNames.Sun} colour={selectedTheme === 'light' ? InlineSvgColours.Primary : InlineSvgColours.Grey} /> </ThemeItem> diff --git a/frontend/src/components/title/Title.tsx b/frontend/src/components/title/Title.tsx index 1a5156d..3bdd4b9 100644 --- a/frontend/src/components/title/Title.tsx +++ b/frontend/src/components/title/Title.tsx @@ -4,7 +4,7 @@ interface TitleProps { export default function Title({ title }: TitleProps) { return ( - <div className="h-[62px] flex items-center"> + <div className='h-[62px] flex items-center'> <div className='text-2xl font-bold leading-tight'>{title}</div> </div> ); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c13d584..d2b026a 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -24,7 +24,7 @@ export async function appLoader() { return { name: data.subnet.name }; -}; +} export async function homeLoader() { async function getData() { @@ -47,30 +47,30 @@ export async function homeLoader() { network: data[2], blocks: data[3], }; -}; +} const router = createBrowserRouter([ { - path: "/", + path: '/', element: <App />, loader: appLoader, errorElement: <ErrorPage />, children: [{ - path: "home", + path: 'home', loader: homeLoader, element: <HomePage />, }, { - path: "checker", + path: 'checker', element: <CheckerPage />, }, { - path: "management", + path: 'management', element: <ManagementPage />, }, { - path: "*", - element: <Navigate to="/home" replace />, + path: '*', + element: <Navigate to='/home' replace />, }, { - path: "/", - element: <Navigate to="/home" replace />, + path: '/', + element: <Navigate to='/home' replace />, }] } ]); diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index f07ed97..7587166 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -58,7 +58,7 @@ export default function CheckerPage() { <> <SearchBar /> <ConfirmationStatus className='pt-8' /> - <div className="pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6"> + <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> <Card> <InfoList title='Transaction Info' diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index c3c9603..87ee04c 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -65,8 +65,8 @@ export default function HomePage() { if (!loaderData) { - console.log('no data') - return <>...</> + console.log('no data'); + return <>...</>; } if (!initialLastBlock.current) { diff --git a/frontend/src/types/loaderData.d.ts b/frontend/src/types/loaderData.d.ts index 2eeccd0..776af04 100644 --- a/frontend/src/types/loaderData.d.ts +++ b/frontend/src/types/loaderData.d.ts @@ -46,7 +46,7 @@ interface Blocks { number: number; }; /** A simple enum to indicate whether the subnet chain is operational. i.e if blocks are mined */ - chainHealth: "UP" | "DOWN"; + chainHealth: 'UP' | 'DOWN'; } namespace Blocks { @@ -81,7 +81,7 @@ interface Relayer { contractAddress: string; health: { /** An enum value to indicate the current relayer status. */ - status: "UP" | "DOWN"; + status: 'UP' | 'DOWN'; /** A short description about the current running status when there is an issue. E.g System is running but very low */ details: string; }; @@ -107,7 +107,7 @@ interface Network { }; health: { /** An enum value to indicate the current relayer status. */ - status: "UP" | "DOWN"; + status: 'UP' | 'DOWN'; /** A short description about the current running status when there is an issue. E.g System is running but very low */ details: string; }; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 11f02fe..0482988 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1 +1 @@ -/// <reference types="vite/client" /> +/// <reference types='vite/client' /> From a1fa9ddc265f58af0c1dc1309629621cdf9be06d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 7 Jul 2023 14:41:33 +1000 Subject: [PATCH 10/87] Add loader in home page --- frontend/src/App.tsx | 6 ++---- frontend/src/components/loader/Loader.tsx | 9 +++++++++ frontend/src/pages/HomePage.tsx | 15 ++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/loader/Loader.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7402e92..721ca5f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { Outlet, useNavigation } from 'react-router-dom'; -import Svg, { SvgNames } from '@/components/images/Svg'; +import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; import { ThemeContext } from '@/contexts/themeContext'; @@ -28,9 +28,7 @@ function App() { <Nav /> <main className='mx-6 my-8 grow w-[1146px]'> {navigation.state === 'loading' ? ( - <div className='flex justify-center items-center h-screen'> - <Svg svgName={SvgNames.Loading} sizeClass='w-[100px]' /> - </div> + <Loader /> ) : ( <Outlet /> )} diff --git a/frontend/src/components/loader/Loader.tsx b/frontend/src/components/loader/Loader.tsx new file mode 100644 index 0000000..04b9763 --- /dev/null +++ b/frontend/src/components/loader/Loader.tsx @@ -0,0 +1,9 @@ +import Svg, { SvgNames } from '@/components/images/Svg'; + +export default function Loader(): JSX.Element { + return ( + <div className='flex justify-center items-center h-screen'> + <Svg svgName={SvgNames.Loading} sizeClass='w-[100px]' /> + </div> + ); +} \ No newline at end of file diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 87ee04c..8f04fca 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -5,12 +5,12 @@ import { useLoaderData } from 'react-router-dom'; import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; +import Loader from '@/components/loader/Loader'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/timeContext'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; import type { HomeLoaderData } from '@/types/loaderData'; - function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { const blocks = []; @@ -44,7 +44,7 @@ export default function HomePage() { if (initialLastBlock.current === null) { initialLastBlock.current = loaderData.blocks.latestMinedBlock.number; } - }, []); + }, [loaderData.blocks.latestMinedBlock.number]); useEffect(() => { async function getData() { @@ -61,16 +61,13 @@ export default function HomePage() { } getData(); - }, [currentUnixTime]); - + }, [blockNumber, currentUnixTime]); - if (!loaderData) { - console.log('no data'); - return <>...</>; - } if (!initialLastBlock.current) { - return <></>; + return ( + <Loader /> + ); } return ( From 942f89e9133b1f71aff3f067bd1d1ecd12a13574 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 7 Jul 2023 15:53:55 +1000 Subject: [PATCH 11/87] Use state to replace ref --- frontend/src/pages/HomePage.tsx | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 8f04fca..9ea1c56 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,11 +1,10 @@ import axios from 'axios'; -import { useContext, useEffect, useRef, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useLoaderData } from 'react-router-dom'; import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; -import Loader from '@/components/loader/Loader'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/timeContext'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; @@ -37,15 +36,9 @@ export default function HomePage() { const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); - const initialLastBlock = useRef<number | null>(null); + const [initialLastBlock] = useState<number>(loaderData.blocks.latestMinedBlock.number); const { currentUnixTime } = useContext(TimeContext); - useEffect(() => { - if (initialLastBlock.current === null) { - initialLastBlock.current = loaderData.blocks.latestMinedBlock.number; - } - }, [loaderData.blocks.latestMinedBlock.number]); - useEffect(() => { async function getData() { const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); @@ -53,7 +46,7 @@ export default function HomePage() { setLastConfirmedBlock(latestSubnetCommittedBlock.number); setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); - const newBlockNumber = latestMinedBlock.number - (initialLastBlock.current ?? 0) + blockNumber; + const newBlockNumber = latestMinedBlock.number - initialLastBlock + blockNumber; const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); const parentChainBlocks = getBlocks(latestMinedBlock.number, latestParentChainCommittedBlock.number, newBlockNumber); setBlocks(blocks); @@ -61,21 +54,14 @@ export default function HomePage() { } getData(); - }, [blockNumber, currentUnixTime]); - - - if (!initialLastBlock.current) { - return ( - <Loader /> - ); - } + }, [blockNumber, currentUnixTime, initialLastBlock]); return ( <div className='grid gap-6 grid-col-1'> <Card> <h1 className='pb-6 text-3xl font-bold'>Subnet Blockchain</h1> <Blocks - initialLastBlock={initialLastBlock.current} + initialLastBlock={initialLastBlock} lastBlock={lastBlock} lastConfirmedBlock={lastConfirmedBlock} blockNumber={blockNumber} @@ -85,7 +71,7 @@ export default function HomePage() { <Card> <h1 className='pb-6 text-3xl font-bold'>Copy at the parent chain</h1> <Blocks - initialLastBlock={initialLastBlock.current} + initialLastBlock={initialLastBlock} lastBlock={lastBlock} lastConfirmedBlock={lastParentConfirmedBlock} blockNumber={blockNumber} From a724666cbcc349a4621ca119100fe800f937a10e Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 7 Jul 2023 16:02:16 +1000 Subject: [PATCH 12/87] Save some picture render --- frontend/src/components/Blocks.tsx | 5 ++++- frontend/src/components/images/BlockImage.tsx | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 33a63e5..ceb5ec2 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -42,11 +42,14 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock return ( <Fragment key={block.number}> - <BlockImage block={block} + <BlockImage + block={block} isFirstConfirmed={isFirstVisibleConfirmed} isLastConfirmed={isLastConfirmed} isFirstUnConfirmed={isFirstUnConfirmed} isLast={isLast} + index={index} + blockNumber={blocks.length + 1} /> { !isLast && ( diff --git a/frontend/src/components/images/BlockImage.tsx b/frontend/src/components/images/BlockImage.tsx index 2df1411..62f6086 100644 --- a/frontend/src/components/images/BlockImage.tsx +++ b/frontend/src/components/images/BlockImage.tsx @@ -13,6 +13,8 @@ interface BlockImageProps { isFirstConfirmed: boolean; isFirstUnConfirmed: boolean; isLast: boolean; + index: number; + blockNumber: number; } export default function BlockImage(props: BlockImageProps) { @@ -20,6 +22,13 @@ export default function BlockImage(props: BlockImageProps) { const { theme } = useContext(ThemeContext); const isDarkMode = theme === 'dark'; + // Save some render + if (props.blockNumber > 40 && props.index <= 20) { + return ( + <div className='shrink-0 w-[35px]' /> + ); + } + return ( <div className='shrink-0 relative w-[35px] h-[37.82px] text-lg leading-none'> {isDarkMode ? ( From 01c38f6cd8cfd7b024b035b6fcfae64b292106cf Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 7 Jul 2023 16:54:43 +1000 Subject: [PATCH 13/87] SearchNotFound component --- frontend/src/assets/no-results-dark.svg | 38 ++++++++++++++ frontend/src/assets/no-results.svg | 38 ++++++++++++++ frontend/src/components/images/Svg.tsx | 14 +++++ .../search-not-found/SearchNotFound.tsx | 40 ++++++++++++++ frontend/src/pages/CheckerPage.tsx | 52 +++++++++++-------- frontend/src/types/searchResult.d.ts | 10 ++++ 6 files changed, 171 insertions(+), 21 deletions(-) create mode 100644 frontend/src/assets/no-results-dark.svg create mode 100644 frontend/src/assets/no-results.svg create mode 100644 frontend/src/components/search-not-found/SearchNotFound.tsx create mode 100644 frontend/src/types/searchResult.d.ts diff --git a/frontend/src/assets/no-results-dark.svg b/frontend/src/assets/no-results-dark.svg new file mode 100644 index 0000000..884a3b4 --- /dev/null +++ b/frontend/src/assets/no-results-dark.svg @@ -0,0 +1,38 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="20 17 72 100"> + <g clip-path="url(#clip0_364_47477)" filter="url(#filter0_d_364_47477)"> + <path + d="M86 17H26C22.6863 17 20 19.6863 20 23V111C20 114.314 22.6863 117 26 117H86C89.3137 117 92 114.314 92 111V23C92 19.6863 89.3137 17 86 17Z" + fill="#0071B2"></path> + <path + d="M49.0512 62.695C49.1632 64.2537 47.9912 65.5177 46.4325 65.5177C44.8725 65.5177 43.5178 64.2537 43.4058 62.695C43.2938 61.1363 44.4658 59.8723 46.0258 59.8723C47.5845 59.8723 48.9392 61.1363 49.0512 62.695Z" + fill="#F2F3F3"></path> + <path + d="M67.3637 62.695C67.4757 64.2537 66.3023 65.5177 64.7437 65.5177C63.185 65.5177 61.8303 64.2537 61.7183 62.695C61.6063 61.1363 62.7784 59.8723 64.337 59.8723C65.897 59.8723 67.2517 61.1363 67.3637 62.695Z" + fill="#F2F3F3"></path> + <path + d="M72.5302 80.9999C72.1942 80.9999 71.9102 80.7399 71.8862 80.3999C71.5595 75.8612 64.4409 72.1692 56.0195 72.1692C50.4395 72.1692 45.4289 73.8279 42.9449 76.4986C41.8395 77.6866 41.3275 78.9679 41.4249 80.3066C41.4502 80.6626 41.1822 80.9719 40.8262 80.9972C40.4689 81.0199 40.1609 80.7559 40.1356 80.3999C40.0142 78.7119 40.6582 77.0586 41.9982 75.6186C44.7196 72.6932 50.0929 70.8772 56.0195 70.8772C65.2582 70.8772 72.7942 75.0186 73.1742 80.3066C73.2009 80.6626 72.9329 80.9719 72.5769 80.9972L72.5302 80.9999Z" + fill="#F2F3F3"></path> + <path + d="M38.6459 56.9746C38.4886 56.9746 38.3313 56.9173 38.2073 56.8026C37.9446 56.5586 37.93 56.1506 38.1726 55.8893L41.0046 52.8386C41.2473 52.5759 41.6566 52.5626 41.9166 52.8039C42.1793 53.0466 42.1939 53.456 41.9513 53.7173L39.1193 56.7679C38.9926 56.9053 38.8193 56.9746 38.6459 56.9746Z" + fill="#F2F3F3"></path> + <path + d="M71.2017 56.9746C71.0444 56.9746 70.8857 56.9173 70.7617 56.8013L67.4911 53.7493C67.2297 53.5066 67.2151 53.0973 67.4591 52.8373C67.7017 52.5759 68.1111 52.5613 68.3724 52.8053L71.643 55.8559C71.903 56.0986 71.9177 56.5079 71.6737 56.7693C71.547 56.9053 71.3751 56.9746 71.2017 56.9746Z" + fill="#F2F3F3"></path> + </g> + <defs> + <filter id="filter0_d_364_47477" x="0" y="0" width="120" height="148" + filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood> + <feColorMatrix in="SourceAlpha" type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix> + <feOffset dx="4" dy="7"></feOffset> + <feGaussianBlur stdDeviation="12"></feGaussianBlur> + <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0"></feColorMatrix> + <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_364_47477"></feBlend> + <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_364_47477" result="shape"></feBlend> + </filter> + <clipPath id="clip0_364_47477"> + <rect width="72" height="100" fill="white" transform="translate(20 17)"></rect> + </clipPath> + </defs> +</svg> \ No newline at end of file diff --git a/frontend/src/assets/no-results.svg b/frontend/src/assets/no-results.svg new file mode 100644 index 0000000..d6fa3ed --- /dev/null +++ b/frontend/src/assets/no-results.svg @@ -0,0 +1,38 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="20 17 72 100"> + <g clip-path="url(#clip0_364_47505)" filter="url(#filter0_d_364_47505)"> + <path + d="M86 17H26C22.6863 17 20 19.6863 20 23V111C20 114.314 22.6863 117 26 117H86C89.3137 117 92 114.314 92 111V23C92 19.6863 89.3137 17 86 17Z" + fill="#B9E5FF"></path> + <path + d="M49.0512 62.695C49.1632 64.2537 47.9912 65.5177 46.4325 65.5177C44.8725 65.5177 43.5178 64.2537 43.4058 62.695C43.2938 61.1363 44.4658 59.8723 46.0258 59.8723C47.5845 59.8723 48.9392 61.1363 49.0512 62.695Z" + fill="#020D0B"></path> + <path + d="M67.3637 62.695C67.4757 64.2537 66.3023 65.5177 64.7437 65.5177C63.185 65.5177 61.8303 64.2537 61.7183 62.695C61.6063 61.1363 62.7784 59.8723 64.337 59.8723C65.897 59.8723 67.2517 61.1363 67.3637 62.695Z" + fill="#020D0B"></path> + <path + d="M72.5302 80.9999C72.1942 80.9999 71.9102 80.7399 71.8862 80.3999C71.5595 75.8612 64.4409 72.1692 56.0195 72.1692C50.4395 72.1692 45.4289 73.8279 42.9449 76.4986C41.8395 77.6866 41.3275 78.9679 41.4249 80.3066C41.4502 80.6626 41.1822 80.9719 40.8262 80.9972C40.4689 81.0199 40.1609 80.7559 40.1356 80.3999C40.0142 78.7119 40.6582 77.0586 41.9982 75.6186C44.7196 72.6932 50.0929 70.8772 56.0195 70.8772C65.2582 70.8772 72.7942 75.0186 73.1742 80.3066C73.2009 80.6626 72.9329 80.9719 72.5769 80.9972L72.5302 80.9999Z" + fill="#020D0B"></path> + <path + d="M38.6459 56.9746C38.4886 56.9746 38.3313 56.9173 38.2073 56.8026C37.9446 56.5586 37.93 56.1506 38.1726 55.8893L41.0046 52.8386C41.2473 52.5759 41.6566 52.5626 41.9166 52.8039C42.1793 53.0466 42.1939 53.456 41.9513 53.7173L39.1193 56.7679C38.9926 56.9053 38.8193 56.9746 38.6459 56.9746Z" + fill="#020D0B"></path> + <path + d="M71.2017 56.9746C71.0444 56.9746 70.8857 56.9173 70.7617 56.8013L67.4911 53.7493C67.2297 53.5066 67.2151 53.0973 67.4591 52.8373C67.7017 52.5759 68.1111 52.5613 68.3724 52.8053L71.643 55.8559C71.903 56.0986 71.9177 56.5079 71.6737 56.7693C71.547 56.9053 71.3751 56.9746 71.2017 56.9746Z" + fill="#020D0B"></path> + </g> + <defs> + <filter id="filter0_d_364_47505" x="0" y="0" width="120" height="148" + filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood> + <feColorMatrix in="SourceAlpha" type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix> + <feOffset dx="4" dy="7"></feOffset> + <feGaussianBlur stdDeviation="12"></feGaussianBlur> + <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0"></feColorMatrix> + <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_364_47505"></feBlend> + <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_364_47505" result="shape"></feBlend> + </filter> + <clipPath id="clip0_364_47505"> + <rect width="72" height="100" fill="white" transform="translate(20 17)"></rect> + </clipPath> + </defs> +</svg> \ No newline at end of file diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index 882609b..e77c643 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -2,6 +2,8 @@ import Check from '@/assets/check.svg'; import Cross from '@/assets/cross.svg'; import Loading from '@/assets/loading.svg'; import Miner from '@/assets/miner.svg'; +import NoResultDark from '@/assets/no-results-dark.svg'; +import NoResult from '@/assets/no-results.svg'; import Penalty from '@/assets/penalty.svg'; import Rhombus from '@/assets/rhombus.svg'; import Search from '@/assets/search.svg'; @@ -152,6 +154,16 @@ export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass }: SvgComponent = Loading; alt = 'Loading data'; break; + + case SvgNames.NoResult: + SvgComponent = NoResult; + alt = 'No Result'; + break; + + case SvgNames.NoResultDark: + SvgComponent = NoResultDark; + alt = 'No Result'; + break; } return ( @@ -178,4 +190,6 @@ export enum SvgNames { Rhombus = 'Rhombus', Search = 'Search', Loading = 'Loading', + NoResult = 'NoResult', + NoResultDark = 'NoResultDark', } diff --git a/frontend/src/components/search-not-found/SearchNotFound.tsx b/frontend/src/components/search-not-found/SearchNotFound.tsx new file mode 100644 index 0000000..49911c4 --- /dev/null +++ b/frontend/src/components/search-not-found/SearchNotFound.tsx @@ -0,0 +1,40 @@ +import { useContext } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import Svg, { SvgNames } from '@/components/images/Svg'; +import { ThemeContext } from '@/contexts/themeContext'; + +export default function SearchNotFound() { + const { theme } = useContext(ThemeContext); + const svgName = theme === 'dark' ? SvgNames.NoResultDark : SvgNames.NoResult; + + return ( + <div className='rounded-3xl mt-6 bg-text-dark-100 dark:bg-bg-dark-700'> + <div className='p-6'> + <div className='flex items-center h-10 p-6 rounded-full bg-text-dark-200 dark:bg-bg-dark-900'> + <Dot /> + <Dot className='ml-3' /> + <Dot className='ml-3' /> + </div> + <div className='mt-6 h-[250px] bg-white dark:bg-bg-dark-900 flex flex-col items-center justify-center'> + <Svg svgName={svgName} sizeClass='w-[72px] h-[100px]' /> + <p className='dark:text-text-white text-text-dark text-xl pt-6'>No result for the query, please try again</p> + </div> + </div> + </div> + ); +} + +interface DotProps { + className?: string; +} + +function Dot(props: DotProps) { + + return ( + <div className={`${twMerge( + props.className, + 'rounded-full w-5 h-5 bg-white dark:bg-bg-dark-700' + )}`} /> + ); +} \ No newline at end of file diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 7587166..9511bec 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -1,10 +1,15 @@ +import { useState } from 'react'; + import Card from '@/components/card/Card'; import ConfirmationStatus from '@/components/confirmation-status/ConfirmationStatus'; import InfoList from '@/components/info-list/InfoList'; import SearchBar from '@/components/search-bar/SearchBar'; +import SearchNotFound from '@/components/search-not-found/SearchNotFound'; import { Info } from '@/types/info'; +import { SearchResult } from '@/types/searchResult'; export default function CheckerPage() { + const [searchResult, setSearchResult] = useState<SearchResult>(); const mappedInfo: Info = { transaction: { @@ -57,27 +62,32 @@ export default function CheckerPage() { return ( <> <SearchBar /> - <ConfirmationStatus className='pt-8' /> - <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> - <Card> - <InfoList - title='Transaction Info' - info={mappedInfo.transaction.data} - /> - </Card> - <Card> - <InfoList - title='Subnet Block Info ' - info={mappedInfo.subnetBlock.data} - /> - </Card> - <Card> - <InfoList - title='Checkpointing parent chain block' - info={mappedInfo.parentChain.data} - /> - </Card> - </div> + {searchResult ? ( + <> + <ConfirmationStatus className='pt-8' /> + <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> + <Card> + <InfoList + title='Transaction Info' + info={mappedInfo.transaction.data} + /> + </Card> + <Card> + <InfoList + title='Subnet Block Info ' + info={mappedInfo.subnetBlock.data} + /> + </Card> + <Card> + <InfoList + title='Checkpointing parent chain block' + info={mappedInfo.parentChain.data} + /> + </Card> + </div></> + ) : ( + <SearchNotFound /> + )} </> ); } diff --git a/frontend/src/types/searchResult.d.ts b/frontend/src/types/searchResult.d.ts new file mode 100644 index 0000000..7bd7c6b --- /dev/null +++ b/frontend/src/types/searchResult.d.ts @@ -0,0 +1,10 @@ +export interface SearchResult { + status?: number; + data?: SearchResult.Data[]; +} + +namespace SearchResult { + interface Data { + value: string; + } +} \ No newline at end of file From b13fbf97b92b0d8e55244721699c1b4ab5a01587 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sat, 8 Jul 2023 01:32:01 +1000 Subject: [PATCH 14/87] Adjust nav to fit laptop screen --- frontend/src/components/nav-item/NavItem.tsx | 2 +- frontend/src/components/nav/Nav.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/nav-item/NavItem.tsx b/frontend/src/components/nav-item/NavItem.tsx index 1cd093b..e4751cb 100644 --- a/frontend/src/components/nav-item/NavItem.tsx +++ b/frontend/src/components/nav-item/NavItem.tsx @@ -17,7 +17,7 @@ export default function NavItem({ text, Image, className, page }: NavItemProps) return ( <div className={className ? className : ''}> <NavLink to={`/${page}`} className={({ isActive }) => { - return `group flex align-bottom py-[20px] px-6 font-semibold cursor-pointer + return `group flex align-bottom py-[20px] px-5 font-semibold cursor-pointer ${isActive ? `bg-blue-200 bg-opacity-20 dark:bg-sky-300 dark:bg-opacity-20 dark:border-sky-300 dark:text-sky-300 border-r-4 border-solid border-primary text-primary` diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index 151c549..258d571 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -5,16 +5,17 @@ import HouseImage from '@/components/images/HouseImage'; import ManagementImage from '@/components/images/ManagementImage'; import NavItem from '@/components/nav-item/NavItem'; import ThemeSwitch from '@/components/theme-switch/ThemeSwitch'; +import { useIsDesktopL } from '@/hooks/useMediaQuery'; import type { AppLoaderData } from '@/types/loaderData'; - export type Pages = 'home' | 'checker' | 'management'; export default function Nav(): JSX.Element { const loaderData = useLoaderData() as AppLoaderData; + const isDesktopL = useIsDesktopL(); return ( - <nav id='nav' className='sticky top-0 dark:bg-bg-dark-1000 w-[246px] h-[1024px] shrink-0 shadow-grey flex flex-col justify-between'> + <nav id='nav' className={`sticky top-0 dark:bg-bg-dark-1000 w-[246px] ${isDesktopL ? 'h-[1024px]' : 'h-[600px]'} max-h-screen shrink-0 shadow-grey flex flex-col justify-between`}> <div> <div className='flex items-center flex-col border-b-[1px] border-text-white dark:border-border-light'> <div className='pt-12 font-bold text-[26px]'> From 5cb56114dd15439a95451815a045a080287b8b15 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 00:24:41 +1000 Subject: [PATCH 15/87] Add info list empty state --- frontend/src/assets/info-dark.svg | 34 ++++++++++++++ frontend/src/assets/info.svg | 31 +++++++++++++ frontend/src/components/Blocks.tsx | 2 +- frontend/src/components/card/Card.tsx | 6 ++- frontend/src/components/dot/Dot.tsx | 15 +++++++ frontend/src/components/images/Svg.tsx | 19 +++++++- .../images/{ => block-image}/BlockImage.tsx | 0 .../{ => block-image}/block-image.module.scss | 0 .../src/components/info-list/InfoList.tsx | 45 +++++++++++++++++-- .../info-list/info-list.module.scss | 3 ++ .../search-not-found/SearchNotFound.tsx | 16 +------ .../theme-switch/theme-switch.module.scss | 3 -- frontend/src/pages/CheckerPage.tsx | 36 +++++++-------- frontend/src/types/info.d.ts | 2 +- 14 files changed, 167 insertions(+), 45 deletions(-) create mode 100644 frontend/src/assets/info-dark.svg create mode 100644 frontend/src/assets/info.svg create mode 100644 frontend/src/components/dot/Dot.tsx rename frontend/src/components/images/{ => block-image}/BlockImage.tsx (100%) rename frontend/src/components/images/{ => block-image}/block-image.module.scss (100%) create mode 100644 frontend/src/components/info-list/info-list.module.scss delete mode 100644 frontend/src/components/theme-switch/theme-switch.module.scss diff --git a/frontend/src/assets/info-dark.svg b/frontend/src/assets/info-dark.svg new file mode 100644 index 0000000..c7e72fe --- /dev/null +++ b/frontend/src/assets/info-dark.svg @@ -0,0 +1,34 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="13 13 75 75"> + <mask id="mask0_364_47512" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="13" y="13" + width="75" height="75"> + <path + d="M88 50.5C88 29.7893 71.2107 13 50.5 13C29.7893 13 13 29.7893 13 50.5C13 71.2107 29.7893 88 50.5 88C71.2107 88 88 71.2107 88 50.5Z" + fill="white"></path> + </mask> + <g mask="url(#mask0_364_47512)"> + <g filter="url(#filter0_d_364_47512)"> + <path + d="M66.0273 13H34.9727C22.8375 13 13 22.8375 13 34.9727V66.0273C13 78.1625 22.8375 88 34.9727 88H66.0273C78.1625 88 88 78.1625 88 66.0273V34.9727C88 22.8375 78.1625 13 66.0273 13Z" + fill="#393B41"></path> + <path + d="M54 48.8574C54 47.2794 52.433 46.0002 50.5 46.0002C48.567 46.0002 47 47.2794 47 48.8574V67.1431C47 68.7211 48.567 70.0002 50.5 70.0002C52.433 70.0002 54 68.7211 54 67.1431V48.8574Z" + fill="#667075"></path> + <path + d="M53.0827 32.3622C51.6528 30.9323 49.3344 30.9323 47.9045 32.3622C46.4746 33.7921 46.4746 36.1105 47.9045 37.5404C49.3344 38.9704 51.6528 38.9704 53.0827 37.5404C54.5127 36.1105 54.5127 33.7921 53.0827 32.3622Z" + fill="#667075"></path> + </g> + </g> + <defs> + <filter id="filter0_d_364_47512" x="-7" y="-4" width="123" height="123" + filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood> + <feColorMatrix in="SourceAlpha" type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix> + <feOffset dx="4" dy="7"></feOffset> + <feGaussianBlur stdDeviation="12"></feGaussianBlur> + <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0"></feColorMatrix> + <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_364_47512"></feBlend> + <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_364_47512" result="shape"></feBlend> + </filter> + </defs> +</svg> \ No newline at end of file diff --git a/frontend/src/assets/info.svg b/frontend/src/assets/info.svg new file mode 100644 index 0000000..995c640 --- /dev/null +++ b/frontend/src/assets/info.svg @@ -0,0 +1,31 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="13 13 75 75"> + <g clip-path="url(#clip0_364_47520)"> + <g filter="url(#filter0_d_364_47520)"> + <path + d="M66.0273 13H34.9727C22.8375 13 13 22.8375 13 34.9727V66.0273C13 78.1625 22.8375 88 34.9727 88H66.0273C78.1625 88 88 78.1625 88 66.0273V34.9727C88 22.8375 78.1625 13 66.0273 13Z" + fill="white"></path> + <path + d="M54 48.8574C54 47.2794 52.433 46.0002 50.5 46.0002C48.567 46.0002 47 47.2794 47 48.8574V67.1431C47 68.7211 48.567 70.0002 50.5 70.0002C52.433 70.0002 54 68.7211 54 67.1431V48.8574Z" + fill="#020D0B"></path> + <path + d="M53.0837 32.3623C51.6538 30.9324 49.3354 30.9324 47.9055 32.3623C46.4756 33.7922 46.4756 36.1106 47.9055 37.5405C49.3354 38.9705 51.6538 38.9705 53.0837 37.5405C54.5137 36.1106 54.5137 33.7922 53.0837 32.3623Z" + fill="#020D0B"></path> + </g> + </g> + <defs> + <filter id="filter0_d_364_47520" x="-7" y="-4" width="123" height="123" + filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood> + <feColorMatrix in="SourceAlpha" type="matrix" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix> + <feOffset dx="4" dy="7"></feOffset> + <feGaussianBlur stdDeviation="12"></feGaussianBlur> + <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0"></feColorMatrix> + <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_364_47520"></feBlend> + <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_364_47520" result="shape"></feBlend> + </filter> + <clipPath id="clip0_364_47520"> + <rect x="13" y="13" width="75" height="75" rx="37.5" fill="white"></rect> + </clipPath> + </defs> +</svg> \ No newline at end of file diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index ceb5ec2..8c3e9e0 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -1,7 +1,7 @@ import { Fragment } from 'react'; import BlockConnectLine from '@/components/BlockConnectLine'; -import BlockImage from '@/components/images/BlockImage'; +import BlockImage from '@/components/images/block-image/BlockImage'; import styles from './blocks.module.scss'; diff --git a/frontend/src/components/card/Card.tsx b/frontend/src/components/card/Card.tsx index 5388dab..d792a69 100644 --- a/frontend/src/components/card/Card.tsx +++ b/frontend/src/components/card/Card.tsx @@ -1,4 +1,5 @@ import { PropsWithChildren } from 'react'; +import { twMerge } from 'tailwind-merge'; interface CardProps extends PropsWithChildren { className?: string; @@ -6,7 +7,10 @@ interface CardProps extends PropsWithChildren { export default function Card({ children, className }: CardProps) { return ( - <div className={`${className ? className : ''} rounded-3xl border-2 border-grey-200 py-6 px-5 shadow-grey dark:bg-bg-dark-800 dark:border-0 text-sm`}> + <div className={`${twMerge( + 'rounded-3xl border-2 border-grey-200 py-6 px-5 shadow-grey dark:bg-bg-dark-800 dark:border-0 text-sm' + , className + )}`}> {children} </div> ); diff --git a/frontend/src/components/dot/Dot.tsx b/frontend/src/components/dot/Dot.tsx new file mode 100644 index 0000000..d8ee4b7 --- /dev/null +++ b/frontend/src/components/dot/Dot.tsx @@ -0,0 +1,15 @@ +import { twMerge } from 'tailwind-merge'; + +interface DotProps { + className?: string; +} + +export function Dot(props: DotProps) { + + return ( + <div className={`${twMerge( + 'rounded-full w-5 h-5 bg-white dark:bg-bg-dark-700', + props.className, + )}`} /> + ); +} \ No newline at end of file diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index e77c643..958e158 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -1,5 +1,7 @@ import Check from '@/assets/check.svg'; import Cross from '@/assets/cross.svg'; +import InfoDark from '@/assets/info-dark.svg'; +import Info from '@/assets/info.svg'; import Loading from '@/assets/loading.svg'; import Miner from '@/assets/miner.svg'; import NoResultDark from '@/assets/no-results-dark.svg'; @@ -97,9 +99,10 @@ interface SvgProps { svgName: SvgNames; size?: SvgSizes; sizeClass?: string; + className?: string; } -export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass }: SvgProps) { +export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass, className }: SvgProps) { function getSizeClass(size?: SvgSizes) { switch (size) { case 'sm': @@ -164,10 +167,20 @@ export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass }: SvgComponent = NoResultDark; alt = 'No Result'; break; + + case SvgNames.Info: + SvgComponent = Info; + alt = 'Info'; + break; + + case SvgNames.InfoDark: + SvgComponent = InfoDark; + alt = 'Info'; + break; } return ( - <img className={`inline-block ${sizeClass}`} src={SvgComponent} alt={alt} /> + <img className={`inline-block ${sizeClass} ${className}`} src={SvgComponent} alt={alt} /> ); } @@ -192,4 +205,6 @@ export enum SvgNames { Loading = 'Loading', NoResult = 'NoResult', NoResultDark = 'NoResultDark', + Info = 'Info', + InfoDark = 'InfoDark', } diff --git a/frontend/src/components/images/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx similarity index 100% rename from frontend/src/components/images/BlockImage.tsx rename to frontend/src/components/images/block-image/BlockImage.tsx diff --git a/frontend/src/components/images/block-image.module.scss b/frontend/src/components/images/block-image/block-image.module.scss similarity index 100% rename from frontend/src/components/images/block-image.module.scss rename to frontend/src/components/images/block-image/block-image.module.scss diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 5039341..07044e1 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -1,14 +1,25 @@ +import { useContext } from 'react'; + +import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; import Title from '@/components/title/Title'; -import { InfoListHealth } from '@/types/info'; +import { ThemeContext } from '@/contexts/themeContext'; + +import styles from './info-list.module.scss'; + +import type { InfoListHealth } from '@/types/info'; interface InfoListProps { title: string; - info: InfoItemBaseProps[]; + info?: InfoItemBaseProps[]; status?: InfoListHealth; } export default function InfoList({ title, status, info }: InfoListProps) { + if (!info) { + return <InfoListEmpty title={title} />; + } + return ( <> <div className='flex justify-between items-center pb-6'> @@ -18,8 +29,9 @@ export default function InfoList({ title, status, info }: InfoListProps) { <span>Status</span> <span className={`ml-1 px-3 py-2.5 bg-opacity-20 rounded-3xl font-bold leading-none - ${status === 'Normal' ? 'bg-sky-500 text-sky-500' : 'bg-warning text-warning'} - `}> + ${status === 'Normal' ? 'bg-sky-500 text-sky-500' : 'bg-warning text-warning'}` + } + > {status} </span> </div> @@ -52,3 +64,28 @@ function InfoItem({ name, value, isFirst }: InfoItemProps) { </div> ); } + +interface InfoListEmptyProps { + title: string; +} + +function InfoListEmpty({ title }: InfoListEmptyProps) { + const { theme } = useContext(ThemeContext); + const svgName = theme === 'dark' ? SvgNames.InfoDark : SvgNames.Info; + + return ( + <> + <div className='flex items-center pl-6 h-[80px] rounded-t-3xl rounded-b bg-text-dark-100 dark:bg-bg-dark-700'> + <Dot className='bg-text-dark-300 dark:bg-text-dark-600' /> + <Dot className='bg-text-dark-300 dark:bg-text-dark-600 ml-3' /> + <Dot className='bg-text-dark-300 dark:bg-text-dark-600 ml-3' /> + </div> + <div className='h-[319px] bg-white dark:bg-bg-dark-800 rounded-b-3xl flex flex-col items-center justify-center'> + + <Svg svgName={svgName} className={`rounded-full ${styles.svgFilter}`} sizeClass='w-[75px] h-[75px]' /> + <p className='dark:text-text-white text-text-dark text-xl pt-9'>No {title.toLowerCase()}</p> + </div> + </> + ); +} + diff --git a/frontend/src/components/info-list/info-list.module.scss b/frontend/src/components/info-list/info-list.module.scss new file mode 100644 index 0000000..f340903 --- /dev/null +++ b/frontend/src/components/info-list/info-list.module.scss @@ -0,0 +1,3 @@ +.svgFilter { + filter: drop-shadow(4px 7px 24px rgba(0, 0, 0, 0.24)); +} diff --git a/frontend/src/components/search-not-found/SearchNotFound.tsx b/frontend/src/components/search-not-found/SearchNotFound.tsx index 49911c4..a0efb55 100644 --- a/frontend/src/components/search-not-found/SearchNotFound.tsx +++ b/frontend/src/components/search-not-found/SearchNotFound.tsx @@ -1,6 +1,6 @@ import { useContext } from 'react'; -import { twMerge } from 'tailwind-merge'; +import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; import { ThemeContext } from '@/contexts/themeContext'; @@ -23,18 +23,4 @@ export default function SearchNotFound() { </div> </div> ); -} - -interface DotProps { - className?: string; -} - -function Dot(props: DotProps) { - - return ( - <div className={`${twMerge( - props.className, - 'rounded-full w-5 h-5 bg-white dark:bg-bg-dark-700' - )}`} /> - ); } \ No newline at end of file diff --git a/frontend/src/components/theme-switch/theme-switch.module.scss b/frontend/src/components/theme-switch/theme-switch.module.scss deleted file mode 100644 index b69df5b..0000000 --- a/frontend/src/components/theme-switch/theme-switch.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.boxShadow { - box-shadow: 0px 8px 8px -4px rgba(24, 39, 75, 0.08), 0px 2px 6px 0px rgba(24, 39, 75, 0.12); -} \ No newline at end of file diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 9511bec..580d066 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -9,23 +9,23 @@ import { Info } from '@/types/info'; import { SearchResult } from '@/types/searchResult'; export default function CheckerPage() { - const [searchResult, setSearchResult] = useState<SearchResult>(); + const [searchResult, setSearchResult] = useState<SearchResult>({}); const mappedInfo: Info = { transaction: { - data: [{ - name: 'Block time', - value: '2' - }, { - name: 'TX Throughput', - value: '10 texts/s' - }, { - name: 'Checkpointed to', - value: 'XDC' - }, { - name: 'Gas', - value: 'Date Time' - }] + // data: [{ + // name: 'Block time', + // value: '2' + // }, { + // name: 'TX Throughput', + // value: '10 texts/s' + // }, { + // name: 'Checkpointed to', + // value: 'XDC' + // }, { + // name: 'Gas', + // value: 'Date Time' + // }] }, subnetBlock: { data: [{ @@ -66,22 +66,22 @@ export default function CheckerPage() { <> <ConfirmationStatus className='pt-8' /> <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> - <Card> + <Card className='border-none px-0 py-0'> <InfoList title='Transaction Info' - info={mappedInfo.transaction.data} + info={mappedInfo.transaction?.data} /> </Card> <Card> <InfoList title='Subnet Block Info ' - info={mappedInfo.subnetBlock.data} + info={mappedInfo.subnetBlock?.data} /> </Card> <Card> <InfoList title='Checkpointing parent chain block' - info={mappedInfo.parentChain.data} + info={mappedInfo.parentChain?.data} /> </Card> </div></> diff --git a/frontend/src/types/info.d.ts b/frontend/src/types/info.d.ts index 6d27602..ebbb6cd 100644 --- a/frontend/src/types/info.d.ts +++ b/frontend/src/types/info.d.ts @@ -1,6 +1,6 @@ export interface Info { [x: string]: { - data: Info.Data[]; + data?: Info.Data[]; health?: InfoListHealth; }; } From 81268e5a7ddb4c3426e0bee7e5b67e8bd3b3dc91 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 00:39:07 +1000 Subject: [PATCH 16/87] Tweak loader position --- frontend/src/components/loader/Loader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/loader/Loader.tsx b/frontend/src/components/loader/Loader.tsx index 04b9763..2768308 100644 --- a/frontend/src/components/loader/Loader.tsx +++ b/frontend/src/components/loader/Loader.tsx @@ -2,7 +2,7 @@ import Svg, { SvgNames } from '@/components/images/Svg'; export default function Loader(): JSX.Element { return ( - <div className='flex justify-center items-center h-screen'> + <div className='flex justify-center items-center h-full'> <Svg svgName={SvgNames.Loading} sizeClass='w-[100px]' /> </div> ); From 8ffc897625d990d014b88074d348d5a8a3da08c8 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 01:17:13 +1000 Subject: [PATCH 17/87] Change the way of block status indicator shows --- frontend/src/components/Blocks.tsx | 31 +++----------- .../images/block-image/BlockImage.tsx | 42 ++++++++++++++++++- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 8c3e9e0..d72de52 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -26,12 +26,12 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock const confirmedNumber = blockNumber - unConfirmedNumber; // Definition: From left to right, the first visible index is 0 const confirmedBlocksMidIndex = (confirmedNumber - 1) / 2; - const unConfirmedBlocksMidIndex = confirmedNumber + (unConfirmedNumber / 2); + const notConfirmedBlocksMidIndex = confirmedNumber + (unConfirmedNumber / 2); return ( <> {/* Ex: 20 blocks + spacing = (35 + 18) * 20 - 18 = 1042px */} - <div className='pt-[60px] llg:w-[1060px] w-[672px] h-[150px] overflow-hidden relative'> + <div className='pt-[60px] llg:w-[1060px] w-[685px] h-[150px] overflow-hidden relative'> <div className='flex items-center transition duration-1000' style={{ transform: `translateX(${translateAmount}px)` }}> { blocks.map((block, index) => { @@ -50,6 +50,9 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock isLast={isLast} index={index} blockNumber={blocks.length + 1} + confirmedBlocksMidIndex={confirmedBlocksMidIndex} + notConfirmedBlocksMidIndex={notConfirmedBlocksMidIndex} + blockSize={blockSize} /> { !isLast && ( @@ -63,28 +66,6 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock } </div> - {/* 'Confirmed' text */} - <div - style={{ transform: `translateX(${confirmedBlocksMidIndex * blockSize}px)` }} - className={` - text-text-white-500 - absolute -top-[5px] -left-[28px] px-1 flex text-lg dark:bg-bg-dark-800 bg-white z-30 dark:text-text-white-800 ${styles.animate} - `} - > - Confirmed - </div> - - {/* 'Not Confirmed' text */} - <div - style={{ transform: `translateX(${unConfirmedBlocksMidIndex * blockSize}px)` }} - className={` - text-text-white-500 - absolute -top-[5px] -left-[72px] px-1 flex text-lg dark:bg-bg-dark-800 bg-white z-20 dark:text-text-white-800 ${styles.animate} - `} - > - Not Confirmed - </div> - {/* 'Block 1' text */} <div className='absolute top-[120px] left-0 text-primary flex text-lg'> <div>Block</div> @@ -92,7 +73,7 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock </div> {/* First confirmed left brace */} - <div className='absolute top-[6px] w-[50px] left-[16px] z-20 pt-[20px] dark:bg-bg-dark-800 bg-white border-t-2 border-l-2 rounded-tl-[20px] dark:border-text-white-800' /> + <div className='absolute top-[6px] w-[30px] left-[16px] z-20 pt-[20px] dark:bg-bg-dark-800 bg-white border-t-2 border-l-2 rounded-tl-[20px] dark:border-text-white-800' /> {/* Left brace layer mask */} <div className='absolute top-[0px] w-[40px] left-[-3px] h-[40px] dark:bg-bg-dark-800 bg-white ' /> diff --git a/frontend/src/components/images/block-image/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx index 62f6086..5b68236 100644 --- a/frontend/src/components/images/block-image/BlockImage.tsx +++ b/frontend/src/components/images/block-image/BlockImage.tsx @@ -1,4 +1,5 @@ import { useContext } from 'react'; +import { twMerge } from 'tailwind-merge'; import BlueBlock from '@/assets/blocks/blue-block.svg'; import GreyBlock from '@/assets/blocks/grey-block.svg'; @@ -15,6 +16,9 @@ interface BlockImageProps { isLast: boolean; index: number; blockNumber: number; + confirmedBlocksMidIndex: number; + notConfirmedBlocksMidIndex: number; + blockSize: number; } export default function BlockImage(props: BlockImageProps) { @@ -44,12 +48,48 @@ export default function BlockImage(props: BlockImageProps) { )} <StatusBrace {...props} /> <BlockNumber {...props} /> + {props.isFirstConfirmed && ( + <> + <StatusText + text='Confirmed' + translateAmount={props.confirmedBlocksMidIndex * props.blockSize} + className='-left-[28px]' + /> + <StatusText + text='Not Confirmed' + translateAmount={props.notConfirmedBlocksMidIndex * props.blockSize} + className='-left-[72px]' + /> + </> + )} + </div> + ); +} +interface BaseTextProps { + text: string; + translateAmount: number; + className: string; +} + +function StatusText({ text, translateAmount, className }: BaseTextProps) { + return ( + <div + style={{ transform: `translateX(${translateAmount}px)` }} + className={twMerge(` + text-text-white-500 whitespace-nowrap + absolute -top-[65px] -left-[50px] px-1 flex text-lg dark:bg-bg-dark-800 bg-white z-30 dark:text-text-white-800 ${styles.animate} + `, className)} + > + {text} </div> ); } +/** + * The line(border) that surrounded with 'Confirmed'/'Not Confirmed' text + * --- Confirmed --- + */ function StatusBrace({ isLastConfirmed, isFirstUnConfirmed, isLast }: BlockImageProps) { - // Block status indicator if (isFirstUnConfirmed) { return <BraceStart />; } else if (isLastConfirmed || isLast) { From 188eed6f54c98d6e4398acd79045f8a4f7c7b898 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 01:22:19 +1000 Subject: [PATCH 18/87] Fix svgs --- frontend/src/assets/miner.svg | 17 ++++------- frontend/src/assets/penalty.svg | 52 ++++++++++++++++++++------------- frontend/src/assets/standby.svg | 36 +++++++++++++++++++++-- 3 files changed, 71 insertions(+), 34 deletions(-) diff --git a/frontend/src/assets/miner.svg b/frontend/src/assets/miner.svg index d23be94..4b209d2 100644 --- a/frontend/src/assets/miner.svg +++ b/frontend/src/assets/miner.svg @@ -1,12 +1,7 @@ -<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'> - -<!-- Uploa'ed to: 'VG Repo' www.'vgrepo.c'm, Tr'nsformed 'y: SVG Repo'Mixer Too's -'>'''''''' -<svg fi'l="#0ea5e9" width'"800px" height'"'00px" viewBox="0 0 512 512" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke="#0ea5e9"> -'''''' -<g id="'VGRepo_bgCarrier" s'roke-wid'h="0"/>''''''''''''''''''''''''''''''''' - -<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> - -<g id="SVGRepo_iconCarrier"> <g id="Mining_Cart"> <g id="XMLID_888_"> <path d="M358.371,398.16c-18.273,0-33.14,14.867-33.14,33.141c0,18.273,14.867,33.14,33.14,33.14 c18.274,0,33.141-14.867,33.141-33.14C391.512,413.027,376.645,398.16,358.371,398.16z M365.419,438.345 c-1.863,1.852-4.424,2.919-7.054,2.919c-2.62,0-5.181-1.066-7.044-2.919c-1.854-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.424-2.919,7.044-2.919c2.63,0,5.19,1.066,7.054,2.919 c1.852,1.853,2.909,4.424,2.909,7.044C368.328,433.921,367.272,436.492,365.419,438.345z" id="XMLID_891_"/> <polygon id="XMLID_892_" points="412.531,152 325.531,47.571 260.958,115.1 297.843,152 "/> <polygon id="XMLID_893_" points="180.569,62.905 91.474,152 269.664,152 "/> <path d="M153.629,398.16c-18.274,0-33.141,14.867-33.141,33.141c0,18.273,14.867,33.14,33.141,33.14 c18.273,0,33.14-14.867,33.14-33.14C186.77,413.027,171.902,398.16,153.629,398.16z M160.667,438.345 c-1.852,1.852-4.413,2.919-7.033,2.919c-2.631,0-5.2-1.066-7.054-2.919c-1.853-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.423-2.919,7.054-2.919c2.62,0,5.181,1.066,7.033,2.919 c1.863,1.853,2.929,4.424,2.929,7.044C163.597,433.921,162.53,436.492,160.667,438.345z" id="XMLID_896_"/> <polygon id="XMLID_897_" points="45.254,271 466.746,271 475.526,227 36.474,227 "/> <path d="M433.82,172c-0.004,0-0.008,0-0.011,0c-0.003,0-0.006,0-0.009,0H67.428c-0.005,0-0.01,0-0.015,0H2v34h508 v-34H433.82z" id="XMLID_898_"/> <path d="M75.586,422h25.833c4.466-25,26.169-43.632,52.21-43.632c26.04,0,47.743,18.632,52.209,43.632h100.324 c4.466-25,26.169-43.632,52.209-43.632c26.04,0,47.744,18.632,52.21,43.632h25.833l26.341-131H49.245L75.586,422z" id="XMLID_899_"/> </g> </g> <g id="Layer_1"/> </g> - +<svg fill="#0ea5e9" width="800px" height="800px" viewBox="0 0 512 512" version="1.1" + xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + stroke="#0ea5e9"> + <g id="SVGRepo_bgCarrier" stroke-width="0"/> + <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> + <g id="SVGRepo_iconCarrier"> <g id="Mining_Cart"> <g id="XMLID_888_"> <path d="M358.371,398.16c-18.273,0-33.14,14.867-33.14,33.141c0,18.273,14.867,33.14,33.14,33.14 c18.274,0,33.141-14.867,33.141-33.14C391.512,413.027,376.645,398.16,358.371,398.16z M365.419,438.345 c-1.863,1.852-4.424,2.919-7.054,2.919c-2.62,0-5.181-1.066-7.044-2.919c-1.854-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.424-2.919,7.044-2.919c2.63,0,5.19,1.066,7.054,2.919 c1.852,1.853,2.909,4.424,2.909,7.044C368.328,433.921,367.272,436.492,365.419,438.345z" id="XMLID_891_"/> <polygon id="XMLID_892_" points="412.531,152 325.531,47.571 260.958,115.1 297.843,152 "/> <polygon id="XMLID_893_" points="180.569,62.905 91.474,152 269.664,152 "/> <path d="M153.629,398.16c-18.274,0-33.141,14.867-33.141,33.141c0,18.273,14.867,33.14,33.141,33.14 c18.273,0,33.14-14.867,33.14-33.14C186.77,413.027,171.902,398.16,153.629,398.16z M160.667,438.345 c-1.852,1.852-4.413,2.919-7.033,2.919c-2.631,0-5.2-1.066-7.054-2.919c-1.853-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.423-2.919,7.054-2.919c2.62,0,5.181,1.066,7.033,2.919 c1.863,1.853,2.929,4.424,2.929,7.044C163.597,433.921,162.53,436.492,160.667,438.345z" id="XMLID_896_"/> <polygon id="XMLID_897_" points="45.254,271 466.746,271 475.526,227 36.474,227 "/> <path d="M433.82,172c-0.004,0-0.008,0-0.011,0c-0.003,0-0.006,0-0.009,0H67.428c-0.005,0-0.01,0-0.015,0H2v34h508 v-34H433.82z" id="XMLID_898_"/> <path d="M75.586,422h25.833c4.466-25,26.169-43.632,52.21-43.632c26.04,0,47.743,18.632,52.209,43.632h100.324 c4.466-25,26.169-43.632,52.209-43.632c26.04,0,47.744,18.632,52.21,43.632h25.833l26.341-131H49.245L75.586,422z" id="XMLID_899_"/> </g> </g> <g id="Layer_1"/> </g> </svg> \ No newline at end of file diff --git a/frontend/src/assets/penalty.svg b/frontend/src/assets/penalty.svg index c017898..beabb05 100644 --- a/frontend/src/assets/penalty.svg +++ b/frontend/src/assets/penalty.svg @@ -1,38 +1,48 @@ -<?xml version='1.0' encoding='utf-8'?> +<?xml version="1.0" encoding="utf-8"?> -<!-- Upload'd to:'SVG Repo' www.'vgrepo.co', Generator' SVG Re'o Mixer Tools -->''''''''''' -<svg widt'="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/199'/xlink' aria-h'dden="true" role="img" class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet"> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg width="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" + class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet"> -<path d="'69.8 88'1l1.8-80c.1-2.2-1.8-4.1-4-4.1H55.9c-2.3 0-4.1 1.9-4 4.1l1.8 80c.1 2.2 1.8 3.9 4 3.9h8c2.2 0 4-1.7 4.1-3.9z" fill="#f44336"> -'''' -</path> -'''''''' -<g fill="#ffffff"> + <path + d="M69.8 88.1l1.8-80c.1-2.2-1.8-4.1-4-4.1H55.9c-2.3 0-4.1 1.9-4 4.1l1.8 80c.1 2.2 1.8 3.9 4 3.9h8c2.2 0 4-1.7 4.1-3.9z" + fill="#f44336"> -<path d="M64'50c.'-1.9'.5-'6.2'.5'26.2s.'-3.5'2.8-3.8c-2.8-.3-3.8 2.3-3.8 4.1c0 1.8.4 21.5.6 23.3c.1 1.8 1.9 3.7 3.6 4.2s2.7-.2 2.9-1.6z" opacity=".2"> + </path> -</path>'''''''' + <g fill="#ffffff"> -<circle c'="59.6" cy="13.4" r="3.3" opacity=".2">''''' + <path + d="M64 50c.3-1.9-.5-26.2-.5-26.2s.1-3.5-2.8-3.8c-2.8-.3-3.8 2.3-3.8 4.1c0 1.8.4 21.5.6 23.3c.1 1.8 1.9 3.7 3.6 4.2s2.7-.2 2.9-1.6z" + opacity=".2"> -</circle>'''' + </path> -</g> + <circle cx="59.6" cy="13.4" r="3.3" opacity=".2"> -<circle cx="64.1" cy="112" r="12" fill="#c33"> + </circle> -</circle> + </g> -<circle cx="62.4" cy="112" r="10.3" fill="#f44336"> + <circle cx="64.1" cy="112" r="12" fill="#c33"> -</circle> + </circle> -<path d="M56.5 108.4c1.2-1.8 3.8-3.3 6.5-3.7c.7-.1 1.3-.1 1.9.1c.4.2.8.6.5 1c-.2.4-.7.5-1.1.6c-2.5.7-4.8 2.4-6.2 4.4c-.5.8-1.4 2.9-2.4 2.4c-1-.7-.7-2.6.8-4.8z" opacity=".2" fill="#ffffff"> + <circle cx="62.4" cy="112" r="10.3" fill="#f44336"> -</path> + </circle> -<path d="M71.9 4h-4.3c2.3 0 4.1 1.9 4 4.1l-1.8 80c-.1 2.2-1.8 3.9-4 3.9H70c2.2 0 4-1.7 4-3.9l1.8-80C76 5.9 74.1 4 71.9 4z" fill="#c33"> + <path + d="M56.5 108.4c1.2-1.8 3.8-3.3 6.5-3.7c.7-.1 1.3-.1 1.9.1c.4.2.8.6.5 1c-.2.4-.7.5-1.1.6c-2.5.7-4.8 2.4-6.2 4.4c-.5.8-1.4 2.9-2.4 2.4c-1-.7-.7-2.6.8-4.8z" + opacity=".2" fill="#ffffff"> -</path> + </path> + + <path + d="M71.9 4h-4.3c2.3 0 4.1 1.9 4 4.1l-1.8 80c-.1 2.2-1.8 3.9-4 3.9H70c2.2 0 4-1.7 4-3.9l1.8-80C76 5.9 74.1 4 71.9 4z" + fill="#c33"> + + </path> </svg> \ No newline at end of file diff --git a/frontend/src/assets/standby.svg b/frontend/src/assets/standby.svg index 8ad746e..ce836ed 100644 --- a/frontend/src/assets/standby.svg +++ b/frontend/src/assets/standby.svg @@ -1,3 +1,35 @@ <?xml version='1.0' encoding='utf-8'?> -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg width='800px' height='800px' viewBox='0 0 1024 1024' class='icon' version='1.1' xmlns='http://www.w3.org/2000/svg'><path d='M511.3 512.1m-483.5 0a483.5 483.5 0 1 0 967 0 483.5 483.5 0 1 0-967 0Z' fill='#89B7F5' /><path d='M511.3 1010.7c-67.3 0-132.6-13.2-194.1-39.2-59.4-25.1-112.7-61.1-158.5-106.8C113 818.9 77.1 765.6 52 706.2c-26-61.5-39.2-126.8-39.2-194.1S26 379.5 52 318.1c25.1-59.4 61.1-112.7 106.8-158.5s99.1-81.7 158.5-106.8c61.5-26 126.8-39.2 194.1-39.2S644 26.8 705.5 52.8c59.4 25.1 112.7 61.1 158.5 106.8 45.8 45.8 81.7 99.1 106.8 158.5 26 61.5 39.2 126.8 39.2 194.1s-13.2 132.6-39.2 194.1C945.7 765.7 909.7 819 864 864.8c-45.8 45.8-99.1 81.7-158.5 106.8-61.6 25.9-126.9 39.1-194.2 39.1z m0-967.1C448.1 43.6 386.7 56 329 80.4c-55.8 23.6-105.9 57.4-149 100.4s-76.8 93.2-100.4 149c-24.4 57.8-36.8 119.1-36.8 182.4s12.4 124.6 36.8 182.4C103.2 750.3 137 800.4 180 843.4c43 43 93.1 76.8 148.9 100.4 57.8 24.4 119.1 36.8 182.4 36.8 63.3 0 124.6-12.4 182.4-36.8 55.8-23.6 105.9-57.4 148.9-100.4 43-43 76.8-93.1 100.4-148.9 24.4-57.8 36.8-119.1 36.8-182.4S967.4 387.5 943 329.7c-23.6-55.8-57.4-105.9-100.4-148.9-43-43-93.1-76.8-148.9-100.4C635.9 56 574.6 43.6 511.3 43.6z' fill='#0F53A8' /><path d='M511.3 512.1m-396.3 0a396.3 396.3 0 1 0 792.6 0 396.3 396.3 0 1 0-792.6 0Z' fill='#B6CDEF' /><path d='M511.3 923.4c-55.5 0-109.4-10.9-160.1-32.3-49-20.7-93-50.4-130.7-88.1s-67.4-81.8-88.1-130.7C110.9 621.5 100 567.6 100 512.1s10.9-109.4 32.3-160.1c20.7-49 50.4-93 88.1-130.7 37.8-37.8 81.8-67.4 130.7-88.1 50.7-21.5 104.6-32.3 160.1-32.3s109.4 10.9 160.1 32.3c49 20.7 93 50.4 130.7 88.1 37.8 37.8 67.4 81.8 88.1 130.7 21.5 50.7 32.3 104.6 32.3 160.1s-10.9 109.4-32.3 160.1c-20.7 49-50.4 93-88.1 130.7s-81.8 67.4-130.7 88.1c-50.6 21.6-104.4 32.4-160 32.4z m0-792.6c-51.5 0-101.4 10.1-148.4 30-45.4 19.2-86.2 46.7-121.2 81.7s-62.5 75.8-81.7 121.2c-19.9 47-30 96.9-30 148.4s10.1 101.4 30 148.4c19.2 45.4 46.7 86.2 81.7 121.2s75.8 62.5 121.2 81.7c47 19.9 96.9 30 148.4 30s101.4-10.1 148.4-30c45.4-19.2 86.2-46.7 121.2-81.7s62.5-75.8 81.7-121.2c19.9-47 30-96.9 30-148.4s-10.1-101.4-30-148.4c-19.2-45.4-46.7-86.2-81.7-121.2s-75.8-62.5-121.2-81.7c-47-19.9-96.9-30-148.4-30z' fill='#0F53A8' /><path d='M511.3 246.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM511.3 854.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM709.6 328.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 3-6.7 4.4-10.6 4.4zM279.7 758.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 2.9-6.8 4.4-10.6 4.4zM838.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15zM230.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15z' fill='#0F53A8' /><path d='M743 758.8c-3.8 0-7.7-1.5-10.6-4.4L699 721c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 2.9-6.8 4.4-10.6 4.4zM313 328.8c-3.8 0-7.7-1.5-10.6-4.4L269 291c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.7 4.4-10.6 4.4z' fill='#0F53A8' /><path d='M493 512.1l18.3-239.4 18.4 239.4z' fill='#B6CDEF' /><path d='M529.7 527.1H493c-4.2 0-8.2-1.7-11-4.8s-4.3-7.2-4-11.3l18.3-239.5c0.6-7.8 7.1-13.9 15-13.9 7.8 0 14.4 6 15 13.9L544.6 511c0.3 4.2-1.1 8.3-4 11.3s-6.8 4.8-10.9 4.8z m-20.5-30h4.3l-2.1-27.9-2.2 27.9z' fill='#0F53A8' /><path d='M517.5 499.7l138.9 162-162-138.9z' fill='#B6CDEF' /><path d='M656.4 676.7c-3.5 0-6.9-1.2-9.8-3.6l-162-138.9c-3.2-2.7-5.1-6.6-5.2-10.8-0.2-4.2 1.4-8.2 4.4-11.2l23-23c3-3 7-4.5 11.2-4.4 4.2 0.2 8.1 2.1 10.8 5.2l138.9 162c5.1 6 4.8 14.8-0.8 20.4-2.8 2.8-6.6 4.3-10.5 4.3zM516.5 521.9l0.9 0.8-0.8-0.9-0.1 0.1z' fill='#0F53A8' /><path d='M242 616.5l266-104.4' fill='#B6CDEF' /><path d='M242 631.5c-6 0-11.6-3.6-14-9.5-3-7.7 0.8-16.4 8.5-19.4l266-104.3c7.7-3 16.4 0.8 19.4 8.5s-0.8 16.4-8.5 19.4l-266 104.3c-1.7 0.6-3.6 1-5.4 1z' fill='#0F53A8' /><path d='M511.3 512.1m-30.1 0a30.1 30.1 0 1 0 60.2 0 30.1 30.1 0 1 0-60.2 0Z' fill='#B6CDEF' /><path d='M511.3 557.3c-24.9 0-45.1-20.2-45.1-45.1 0-24.9 20.2-45.1 45.1-45.1s45.1 20.2 45.1 45.1c0.1 24.8-20.2 45.1-45.1 45.1z m0-60.3c-8.3 0-15.1 6.8-15.1 15.1s6.8 15.1 15.1 15.1 15.1-6.8 15.1-15.1c0.1-8.3-6.7-15.1-15.1-15.1z' fill='#0F53A8' /></svg> \ No newline at end of file +<svg width='800px' height='800px' viewBox='0 0 1024 1024' class='icon' version='1.1' + xmlns='http://www.w3.org/2000/svg'> + <path d='M511.3 512.1m-483.5 0a483.5 483.5 0 1 0 967 0 483.5 483.5 0 1 0-967 0Z' fill='#89B7F5' /> + <path + d='M511.3 1010.7c-67.3 0-132.6-13.2-194.1-39.2-59.4-25.1-112.7-61.1-158.5-106.8C113 818.9 77.1 765.6 52 706.2c-26-61.5-39.2-126.8-39.2-194.1S26 379.5 52 318.1c25.1-59.4 61.1-112.7 106.8-158.5s99.1-81.7 158.5-106.8c61.5-26 126.8-39.2 194.1-39.2S644 26.8 705.5 52.8c59.4 25.1 112.7 61.1 158.5 106.8 45.8 45.8 81.7 99.1 106.8 158.5 26 61.5 39.2 126.8 39.2 194.1s-13.2 132.6-39.2 194.1C945.7 765.7 909.7 819 864 864.8c-45.8 45.8-99.1 81.7-158.5 106.8-61.6 25.9-126.9 39.1-194.2 39.1z m0-967.1C448.1 43.6 386.7 56 329 80.4c-55.8 23.6-105.9 57.4-149 100.4s-76.8 93.2-100.4 149c-24.4 57.8-36.8 119.1-36.8 182.4s12.4 124.6 36.8 182.4C103.2 750.3 137 800.4 180 843.4c43 43 93.1 76.8 148.9 100.4 57.8 24.4 119.1 36.8 182.4 36.8 63.3 0 124.6-12.4 182.4-36.8 55.8-23.6 105.9-57.4 148.9-100.4 43-43 76.8-93.1 100.4-148.9 24.4-57.8 36.8-119.1 36.8-182.4S967.4 387.5 943 329.7c-23.6-55.8-57.4-105.9-100.4-148.9-43-43-93.1-76.8-148.9-100.4C635.9 56 574.6 43.6 511.3 43.6z' + fill='#0F53A8' /> + <path d='M511.3 512.1m-396.3 0a396.3 396.3 0 1 0 792.6 0 396.3 396.3 0 1 0-792.6 0Z' + fill='#B6CDEF' /> + <path + d='M511.3 923.4c-55.5 0-109.4-10.9-160.1-32.3-49-20.7-93-50.4-130.7-88.1s-67.4-81.8-88.1-130.7C110.9 621.5 100 567.6 100 512.1s10.9-109.4 32.3-160.1c20.7-49 50.4-93 88.1-130.7 37.8-37.8 81.8-67.4 130.7-88.1 50.7-21.5 104.6-32.3 160.1-32.3s109.4 10.9 160.1 32.3c49 20.7 93 50.4 130.7 88.1 37.8 37.8 67.4 81.8 88.1 130.7 21.5 50.7 32.3 104.6 32.3 160.1s-10.9 109.4-32.3 160.1c-20.7 49-50.4 93-88.1 130.7s-81.8 67.4-130.7 88.1c-50.6 21.6-104.4 32.4-160 32.4z m0-792.6c-51.5 0-101.4 10.1-148.4 30-45.4 19.2-86.2 46.7-121.2 81.7s-62.5 75.8-81.7 121.2c-19.9 47-30 96.9-30 148.4s10.1 101.4 30 148.4c19.2 45.4 46.7 86.2 81.7 121.2s75.8 62.5 121.2 81.7c47 19.9 96.9 30 148.4 30s101.4-10.1 148.4-30c45.4-19.2 86.2-46.7 121.2-81.7s62.5-75.8 81.7-121.2c19.9-47 30-96.9 30-148.4s-10.1-101.4-30-148.4c-19.2-45.4-46.7-86.2-81.7-121.2s-75.8-62.5-121.2-81.7c-47-19.9-96.9-30-148.4-30z' + fill='#0F53A8' /> + <path + d='M511.3 246.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM511.3 854.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM709.6 328.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 3-6.7 4.4-10.6 4.4zM279.7 758.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 2.9-6.8 4.4-10.6 4.4zM838.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15zM230.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15z' + fill='#0F53A8' /> + <path + d='M743 758.8c-3.8 0-7.7-1.5-10.6-4.4L699 721c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 2.9-6.8 4.4-10.6 4.4zM313 328.8c-3.8 0-7.7-1.5-10.6-4.4L269 291c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.7 4.4-10.6 4.4z' + fill='#0F53A8' /> + <path d='M493 512.1l18.3-239.4 18.4 239.4z' fill='#B6CDEF' /> + <path + d='M529.7 527.1H493c-4.2 0-8.2-1.7-11-4.8s-4.3-7.2-4-11.3l18.3-239.5c0.6-7.8 7.1-13.9 15-13.9 7.8 0 14.4 6 15 13.9L544.6 511c0.3 4.2-1.1 8.3-4 11.3s-6.8 4.8-10.9 4.8z m-20.5-30h4.3l-2.1-27.9-2.2 27.9z' + fill='#0F53A8' /> + <path d='M517.5 499.7l138.9 162-162-138.9z' fill='#B6CDEF' /> + <path + d='M656.4 676.7c-3.5 0-6.9-1.2-9.8-3.6l-162-138.9c-3.2-2.7-5.1-6.6-5.2-10.8-0.2-4.2 1.4-8.2 4.4-11.2l23-23c3-3 7-4.5 11.2-4.4 4.2 0.2 8.1 2.1 10.8 5.2l138.9 162c5.1 6 4.8 14.8-0.8 20.4-2.8 2.8-6.6 4.3-10.5 4.3zM516.5 521.9l0.9 0.8-0.8-0.9-0.1 0.1z' + fill='#0F53A8' /> + <path d='M242 616.5l266-104.4' fill='#B6CDEF' /> + <path + d='M242 631.5c-6 0-11.6-3.6-14-9.5-3-7.7 0.8-16.4 8.5-19.4l266-104.3c7.7-3 16.4 0.8 19.4 8.5s-0.8 16.4-8.5 19.4l-266 104.3c-1.7 0.6-3.6 1-5.4 1z' + fill='#0F53A8' /> + <path d='M511.3 512.1m-30.1 0a30.1 30.1 0 1 0 60.2 0 30.1 30.1 0 1 0-60.2 0Z' fill='#B6CDEF' /> + <path + d='M511.3 557.3c-24.9 0-45.1-20.2-45.1-45.1 0-24.9 20.2-45.1 45.1-45.1s45.1 20.2 45.1 45.1c0.1 24.8-20.2 45.1-45.1 45.1z m0-60.3c-8.3 0-15.1 6.8-15.1 15.1s6.8 15.1 15.1 15.1 15.1-6.8 15.1-15.1c0.1-8.3-6.7-15.1-15.1-15.1z' + fill='#0F53A8' /> +</svg> \ No newline at end of file From 4c9aa178527d2e5c99f6ec03c39271432f967d54 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 01:28:57 +1000 Subject: [PATCH 19/87] Fix svgs --- frontend/src/assets/miner.svg | 17 ++++------- frontend/src/assets/penalty.svg | 52 ++++++++++++++++++++------------- frontend/src/assets/standby.svg | 36 +++++++++++++++++++++-- 3 files changed, 71 insertions(+), 34 deletions(-) diff --git a/frontend/src/assets/miner.svg b/frontend/src/assets/miner.svg index d23be94..4b209d2 100644 --- a/frontend/src/assets/miner.svg +++ b/frontend/src/assets/miner.svg @@ -1,12 +1,7 @@ -<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'> - -<!-- Uploa'ed to: 'VG Repo' www.'vgrepo.c'm, Tr'nsformed 'y: SVG Repo'Mixer Too's -'>'''''''' -<svg fi'l="#0ea5e9" width'"800px" height'"'00px" viewBox="0 0 512 512" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke="#0ea5e9"> -'''''' -<g id="'VGRepo_bgCarrier" s'roke-wid'h="0"/>''''''''''''''''''''''''''''''''' - -<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> - -<g id="SVGRepo_iconCarrier"> <g id="Mining_Cart"> <g id="XMLID_888_"> <path d="M358.371,398.16c-18.273,0-33.14,14.867-33.14,33.141c0,18.273,14.867,33.14,33.14,33.14 c18.274,0,33.141-14.867,33.141-33.14C391.512,413.027,376.645,398.16,358.371,398.16z M365.419,438.345 c-1.863,1.852-4.424,2.919-7.054,2.919c-2.62,0-5.181-1.066-7.044-2.919c-1.854-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.424-2.919,7.044-2.919c2.63,0,5.19,1.066,7.054,2.919 c1.852,1.853,2.909,4.424,2.909,7.044C368.328,433.921,367.272,436.492,365.419,438.345z" id="XMLID_891_"/> <polygon id="XMLID_892_" points="412.531,152 325.531,47.571 260.958,115.1 297.843,152 "/> <polygon id="XMLID_893_" points="180.569,62.905 91.474,152 269.664,152 "/> <path d="M153.629,398.16c-18.274,0-33.141,14.867-33.141,33.141c0,18.273,14.867,33.14,33.141,33.14 c18.273,0,33.14-14.867,33.14-33.14C186.77,413.027,171.902,398.16,153.629,398.16z M160.667,438.345 c-1.852,1.852-4.413,2.919-7.033,2.919c-2.631,0-5.2-1.066-7.054-2.919c-1.853-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.423-2.919,7.054-2.919c2.62,0,5.181,1.066,7.033,2.919 c1.863,1.853,2.929,4.424,2.929,7.044C163.597,433.921,162.53,436.492,160.667,438.345z" id="XMLID_896_"/> <polygon id="XMLID_897_" points="45.254,271 466.746,271 475.526,227 36.474,227 "/> <path d="M433.82,172c-0.004,0-0.008,0-0.011,0c-0.003,0-0.006,0-0.009,0H67.428c-0.005,0-0.01,0-0.015,0H2v34h508 v-34H433.82z" id="XMLID_898_"/> <path d="M75.586,422h25.833c4.466-25,26.169-43.632,52.21-43.632c26.04,0,47.743,18.632,52.209,43.632h100.324 c4.466-25,26.169-43.632,52.209-43.632c26.04,0,47.744,18.632,52.21,43.632h25.833l26.341-131H49.245L75.586,422z" id="XMLID_899_"/> </g> </g> <g id="Layer_1"/> </g> - +<svg fill="#0ea5e9" width="800px" height="800px" viewBox="0 0 512 512" version="1.1" + xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + stroke="#0ea5e9"> + <g id="SVGRepo_bgCarrier" stroke-width="0"/> + <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> + <g id="SVGRepo_iconCarrier"> <g id="Mining_Cart"> <g id="XMLID_888_"> <path d="M358.371,398.16c-18.273,0-33.14,14.867-33.14,33.141c0,18.273,14.867,33.14,33.14,33.14 c18.274,0,33.141-14.867,33.141-33.14C391.512,413.027,376.645,398.16,358.371,398.16z M365.419,438.345 c-1.863,1.852-4.424,2.919-7.054,2.919c-2.62,0-5.181-1.066-7.044-2.919c-1.854-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.424-2.919,7.044-2.919c2.63,0,5.19,1.066,7.054,2.919 c1.852,1.853,2.909,4.424,2.909,7.044C368.328,433.921,367.272,436.492,365.419,438.345z" id="XMLID_891_"/> <polygon id="XMLID_892_" points="412.531,152 325.531,47.571 260.958,115.1 297.843,152 "/> <polygon id="XMLID_893_" points="180.569,62.905 91.474,152 269.664,152 "/> <path d="M153.629,398.16c-18.274,0-33.141,14.867-33.141,33.141c0,18.273,14.867,33.14,33.141,33.14 c18.273,0,33.14-14.867,33.14-33.14C186.77,413.027,171.902,398.16,153.629,398.16z M160.667,438.345 c-1.852,1.852-4.413,2.919-7.033,2.919c-2.631,0-5.2-1.066-7.054-2.919c-1.853-1.853-2.919-4.424-2.919-7.044 c0-2.62,1.065-5.19,2.919-7.044c1.853-1.852,4.423-2.919,7.054-2.919c2.62,0,5.181,1.066,7.033,2.919 c1.863,1.853,2.929,4.424,2.929,7.044C163.597,433.921,162.53,436.492,160.667,438.345z" id="XMLID_896_"/> <polygon id="XMLID_897_" points="45.254,271 466.746,271 475.526,227 36.474,227 "/> <path d="M433.82,172c-0.004,0-0.008,0-0.011,0c-0.003,0-0.006,0-0.009,0H67.428c-0.005,0-0.01,0-0.015,0H2v34h508 v-34H433.82z" id="XMLID_898_"/> <path d="M75.586,422h25.833c4.466-25,26.169-43.632,52.21-43.632c26.04,0,47.743,18.632,52.209,43.632h100.324 c4.466-25,26.169-43.632,52.209-43.632c26.04,0,47.744,18.632,52.21,43.632h25.833l26.341-131H49.245L75.586,422z" id="XMLID_899_"/> </g> </g> <g id="Layer_1"/> </g> </svg> \ No newline at end of file diff --git a/frontend/src/assets/penalty.svg b/frontend/src/assets/penalty.svg index c017898..beabb05 100644 --- a/frontend/src/assets/penalty.svg +++ b/frontend/src/assets/penalty.svg @@ -1,38 +1,48 @@ -<?xml version='1.0' encoding='utf-8'?> +<?xml version="1.0" encoding="utf-8"?> -<!-- Upload'd to:'SVG Repo' www.'vgrepo.co', Generator' SVG Re'o Mixer Tools -->''''''''''' -<svg widt'="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/199'/xlink' aria-h'dden="true" role="img" class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet"> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg width="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" + class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet"> -<path d="'69.8 88'1l1.8-80c.1-2.2-1.8-4.1-4-4.1H55.9c-2.3 0-4.1 1.9-4 4.1l1.8 80c.1 2.2 1.8 3.9 4 3.9h8c2.2 0 4-1.7 4.1-3.9z" fill="#f44336"> -'''' -</path> -'''''''' -<g fill="#ffffff"> + <path + d="M69.8 88.1l1.8-80c.1-2.2-1.8-4.1-4-4.1H55.9c-2.3 0-4.1 1.9-4 4.1l1.8 80c.1 2.2 1.8 3.9 4 3.9h8c2.2 0 4-1.7 4.1-3.9z" + fill="#f44336"> -<path d="M64'50c.'-1.9'.5-'6.2'.5'26.2s.'-3.5'2.8-3.8c-2.8-.3-3.8 2.3-3.8 4.1c0 1.8.4 21.5.6 23.3c.1 1.8 1.9 3.7 3.6 4.2s2.7-.2 2.9-1.6z" opacity=".2"> + </path> -</path>'''''''' + <g fill="#ffffff"> -<circle c'="59.6" cy="13.4" r="3.3" opacity=".2">''''' + <path + d="M64 50c.3-1.9-.5-26.2-.5-26.2s.1-3.5-2.8-3.8c-2.8-.3-3.8 2.3-3.8 4.1c0 1.8.4 21.5.6 23.3c.1 1.8 1.9 3.7 3.6 4.2s2.7-.2 2.9-1.6z" + opacity=".2"> -</circle>'''' + </path> -</g> + <circle cx="59.6" cy="13.4" r="3.3" opacity=".2"> -<circle cx="64.1" cy="112" r="12" fill="#c33"> + </circle> -</circle> + </g> -<circle cx="62.4" cy="112" r="10.3" fill="#f44336"> + <circle cx="64.1" cy="112" r="12" fill="#c33"> -</circle> + </circle> -<path d="M56.5 108.4c1.2-1.8 3.8-3.3 6.5-3.7c.7-.1 1.3-.1 1.9.1c.4.2.8.6.5 1c-.2.4-.7.5-1.1.6c-2.5.7-4.8 2.4-6.2 4.4c-.5.8-1.4 2.9-2.4 2.4c-1-.7-.7-2.6.8-4.8z" opacity=".2" fill="#ffffff"> + <circle cx="62.4" cy="112" r="10.3" fill="#f44336"> -</path> + </circle> -<path d="M71.9 4h-4.3c2.3 0 4.1 1.9 4 4.1l-1.8 80c-.1 2.2-1.8 3.9-4 3.9H70c2.2 0 4-1.7 4-3.9l1.8-80C76 5.9 74.1 4 71.9 4z" fill="#c33"> + <path + d="M56.5 108.4c1.2-1.8 3.8-3.3 6.5-3.7c.7-.1 1.3-.1 1.9.1c.4.2.8.6.5 1c-.2.4-.7.5-1.1.6c-2.5.7-4.8 2.4-6.2 4.4c-.5.8-1.4 2.9-2.4 2.4c-1-.7-.7-2.6.8-4.8z" + opacity=".2" fill="#ffffff"> -</path> + </path> + + <path + d="M71.9 4h-4.3c2.3 0 4.1 1.9 4 4.1l-1.8 80c-.1 2.2-1.8 3.9-4 3.9H70c2.2 0 4-1.7 4-3.9l1.8-80C76 5.9 74.1 4 71.9 4z" + fill="#c33"> + + </path> </svg> \ No newline at end of file diff --git a/frontend/src/assets/standby.svg b/frontend/src/assets/standby.svg index 8ad746e..ce836ed 100644 --- a/frontend/src/assets/standby.svg +++ b/frontend/src/assets/standby.svg @@ -1,3 +1,35 @@ <?xml version='1.0' encoding='utf-8'?> -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg width='800px' height='800px' viewBox='0 0 1024 1024' class='icon' version='1.1' xmlns='http://www.w3.org/2000/svg'><path d='M511.3 512.1m-483.5 0a483.5 483.5 0 1 0 967 0 483.5 483.5 0 1 0-967 0Z' fill='#89B7F5' /><path d='M511.3 1010.7c-67.3 0-132.6-13.2-194.1-39.2-59.4-25.1-112.7-61.1-158.5-106.8C113 818.9 77.1 765.6 52 706.2c-26-61.5-39.2-126.8-39.2-194.1S26 379.5 52 318.1c25.1-59.4 61.1-112.7 106.8-158.5s99.1-81.7 158.5-106.8c61.5-26 126.8-39.2 194.1-39.2S644 26.8 705.5 52.8c59.4 25.1 112.7 61.1 158.5 106.8 45.8 45.8 81.7 99.1 106.8 158.5 26 61.5 39.2 126.8 39.2 194.1s-13.2 132.6-39.2 194.1C945.7 765.7 909.7 819 864 864.8c-45.8 45.8-99.1 81.7-158.5 106.8-61.6 25.9-126.9 39.1-194.2 39.1z m0-967.1C448.1 43.6 386.7 56 329 80.4c-55.8 23.6-105.9 57.4-149 100.4s-76.8 93.2-100.4 149c-24.4 57.8-36.8 119.1-36.8 182.4s12.4 124.6 36.8 182.4C103.2 750.3 137 800.4 180 843.4c43 43 93.1 76.8 148.9 100.4 57.8 24.4 119.1 36.8 182.4 36.8 63.3 0 124.6-12.4 182.4-36.8 55.8-23.6 105.9-57.4 148.9-100.4 43-43 76.8-93.1 100.4-148.9 24.4-57.8 36.8-119.1 36.8-182.4S967.4 387.5 943 329.7c-23.6-55.8-57.4-105.9-100.4-148.9-43-43-93.1-76.8-148.9-100.4C635.9 56 574.6 43.6 511.3 43.6z' fill='#0F53A8' /><path d='M511.3 512.1m-396.3 0a396.3 396.3 0 1 0 792.6 0 396.3 396.3 0 1 0-792.6 0Z' fill='#B6CDEF' /><path d='M511.3 923.4c-55.5 0-109.4-10.9-160.1-32.3-49-20.7-93-50.4-130.7-88.1s-67.4-81.8-88.1-130.7C110.9 621.5 100 567.6 100 512.1s10.9-109.4 32.3-160.1c20.7-49 50.4-93 88.1-130.7 37.8-37.8 81.8-67.4 130.7-88.1 50.7-21.5 104.6-32.3 160.1-32.3s109.4 10.9 160.1 32.3c49 20.7 93 50.4 130.7 88.1 37.8 37.8 67.4 81.8 88.1 130.7 21.5 50.7 32.3 104.6 32.3 160.1s-10.9 109.4-32.3 160.1c-20.7 49-50.4 93-88.1 130.7s-81.8 67.4-130.7 88.1c-50.6 21.6-104.4 32.4-160 32.4z m0-792.6c-51.5 0-101.4 10.1-148.4 30-45.4 19.2-86.2 46.7-121.2 81.7s-62.5 75.8-81.7 121.2c-19.9 47-30 96.9-30 148.4s10.1 101.4 30 148.4c19.2 45.4 46.7 86.2 81.7 121.2s75.8 62.5 121.2 81.7c47 19.9 96.9 30 148.4 30s101.4-10.1 148.4-30c45.4-19.2 86.2-46.7 121.2-81.7s62.5-75.8 81.7-121.2c19.9-47 30-96.9 30-148.4s-10.1-101.4-30-148.4c-19.2-45.4-46.7-86.2-81.7-121.2s-75.8-62.5-121.2-81.7c-47-19.9-96.9-30-148.4-30z' fill='#0F53A8' /><path d='M511.3 246.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM511.3 854.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM709.6 328.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 3-6.7 4.4-10.6 4.4zM279.7 758.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 2.9-6.8 4.4-10.6 4.4zM838.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15zM230.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15z' fill='#0F53A8' /><path d='M743 758.8c-3.8 0-7.7-1.5-10.6-4.4L699 721c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 2.9-6.8 4.4-10.6 4.4zM313 328.8c-3.8 0-7.7-1.5-10.6-4.4L269 291c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.7 4.4-10.6 4.4z' fill='#0F53A8' /><path d='M493 512.1l18.3-239.4 18.4 239.4z' fill='#B6CDEF' /><path d='M529.7 527.1H493c-4.2 0-8.2-1.7-11-4.8s-4.3-7.2-4-11.3l18.3-239.5c0.6-7.8 7.1-13.9 15-13.9 7.8 0 14.4 6 15 13.9L544.6 511c0.3 4.2-1.1 8.3-4 11.3s-6.8 4.8-10.9 4.8z m-20.5-30h4.3l-2.1-27.9-2.2 27.9z' fill='#0F53A8' /><path d='M517.5 499.7l138.9 162-162-138.9z' fill='#B6CDEF' /><path d='M656.4 676.7c-3.5 0-6.9-1.2-9.8-3.6l-162-138.9c-3.2-2.7-5.1-6.6-5.2-10.8-0.2-4.2 1.4-8.2 4.4-11.2l23-23c3-3 7-4.5 11.2-4.4 4.2 0.2 8.1 2.1 10.8 5.2l138.9 162c5.1 6 4.8 14.8-0.8 20.4-2.8 2.8-6.6 4.3-10.5 4.3zM516.5 521.9l0.9 0.8-0.8-0.9-0.1 0.1z' fill='#0F53A8' /><path d='M242 616.5l266-104.4' fill='#B6CDEF' /><path d='M242 631.5c-6 0-11.6-3.6-14-9.5-3-7.7 0.8-16.4 8.5-19.4l266-104.3c7.7-3 16.4 0.8 19.4 8.5s-0.8 16.4-8.5 19.4l-266 104.3c-1.7 0.6-3.6 1-5.4 1z' fill='#0F53A8' /><path d='M511.3 512.1m-30.1 0a30.1 30.1 0 1 0 60.2 0 30.1 30.1 0 1 0-60.2 0Z' fill='#B6CDEF' /><path d='M511.3 557.3c-24.9 0-45.1-20.2-45.1-45.1 0-24.9 20.2-45.1 45.1-45.1s45.1 20.2 45.1 45.1c0.1 24.8-20.2 45.1-45.1 45.1z m0-60.3c-8.3 0-15.1 6.8-15.1 15.1s6.8 15.1 15.1 15.1 15.1-6.8 15.1-15.1c0.1-8.3-6.7-15.1-15.1-15.1z' fill='#0F53A8' /></svg> \ No newline at end of file +<svg width='800px' height='800px' viewBox='0 0 1024 1024' class='icon' version='1.1' + xmlns='http://www.w3.org/2000/svg'> + <path d='M511.3 512.1m-483.5 0a483.5 483.5 0 1 0 967 0 483.5 483.5 0 1 0-967 0Z' fill='#89B7F5' /> + <path + d='M511.3 1010.7c-67.3 0-132.6-13.2-194.1-39.2-59.4-25.1-112.7-61.1-158.5-106.8C113 818.9 77.1 765.6 52 706.2c-26-61.5-39.2-126.8-39.2-194.1S26 379.5 52 318.1c25.1-59.4 61.1-112.7 106.8-158.5s99.1-81.7 158.5-106.8c61.5-26 126.8-39.2 194.1-39.2S644 26.8 705.5 52.8c59.4 25.1 112.7 61.1 158.5 106.8 45.8 45.8 81.7 99.1 106.8 158.5 26 61.5 39.2 126.8 39.2 194.1s-13.2 132.6-39.2 194.1C945.7 765.7 909.7 819 864 864.8c-45.8 45.8-99.1 81.7-158.5 106.8-61.6 25.9-126.9 39.1-194.2 39.1z m0-967.1C448.1 43.6 386.7 56 329 80.4c-55.8 23.6-105.9 57.4-149 100.4s-76.8 93.2-100.4 149c-24.4 57.8-36.8 119.1-36.8 182.4s12.4 124.6 36.8 182.4C103.2 750.3 137 800.4 180 843.4c43 43 93.1 76.8 148.9 100.4 57.8 24.4 119.1 36.8 182.4 36.8 63.3 0 124.6-12.4 182.4-36.8 55.8-23.6 105.9-57.4 148.9-100.4 43-43 76.8-93.1 100.4-148.9 24.4-57.8 36.8-119.1 36.8-182.4S967.4 387.5 943 329.7c-23.6-55.8-57.4-105.9-100.4-148.9-43-43-93.1-76.8-148.9-100.4C635.9 56 574.6 43.6 511.3 43.6z' + fill='#0F53A8' /> + <path d='M511.3 512.1m-396.3 0a396.3 396.3 0 1 0 792.6 0 396.3 396.3 0 1 0-792.6 0Z' + fill='#B6CDEF' /> + <path + d='M511.3 923.4c-55.5 0-109.4-10.9-160.1-32.3-49-20.7-93-50.4-130.7-88.1s-67.4-81.8-88.1-130.7C110.9 621.5 100 567.6 100 512.1s10.9-109.4 32.3-160.1c20.7-49 50.4-93 88.1-130.7 37.8-37.8 81.8-67.4 130.7-88.1 50.7-21.5 104.6-32.3 160.1-32.3s109.4 10.9 160.1 32.3c49 20.7 93 50.4 130.7 88.1 37.8 37.8 67.4 81.8 88.1 130.7 21.5 50.7 32.3 104.6 32.3 160.1s-10.9 109.4-32.3 160.1c-20.7 49-50.4 93-88.1 130.7s-81.8 67.4-130.7 88.1c-50.6 21.6-104.4 32.4-160 32.4z m0-792.6c-51.5 0-101.4 10.1-148.4 30-45.4 19.2-86.2 46.7-121.2 81.7s-62.5 75.8-81.7 121.2c-19.9 47-30 96.9-30 148.4s10.1 101.4 30 148.4c19.2 45.4 46.7 86.2 81.7 121.2s75.8 62.5 121.2 81.7c47 19.9 96.9 30 148.4 30s101.4-10.1 148.4-30c45.4-19.2 86.2-46.7 121.2-81.7s62.5-75.8 81.7-121.2c19.9-47 30-96.9 30-148.4s-10.1-101.4-30-148.4c-19.2-45.4-46.7-86.2-81.7-121.2s-75.8-62.5-121.2-81.7c-47-19.9-96.9-30-148.4-30z' + fill='#0F53A8' /> + <path + d='M511.3 246.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM511.3 854.7c-8.3 0-15-6.7-15-15v-47.2c0-8.3 6.7-15 15-15s15 6.7 15 15v47.2c0 8.3-6.7 15-15 15zM709.6 328.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 3-6.7 4.4-10.6 4.4zM279.7 758.8c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l33.4-33.4c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-33.4 33.4c-2.9 2.9-6.8 4.4-10.6 4.4zM838.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15zM230.9 527.1h-47.2c-8.3 0-15-6.7-15-15s6.7-15 15-15h47.2c8.3 0 15 6.7 15 15s-6.7 15-15 15z' + fill='#0F53A8' /> + <path + d='M743 758.8c-3.8 0-7.7-1.5-10.6-4.4L699 721c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 2.9-6.8 4.4-10.6 4.4zM313 328.8c-3.8 0-7.7-1.5-10.6-4.4L269 291c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l33.4 33.4c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.7 4.4-10.6 4.4z' + fill='#0F53A8' /> + <path d='M493 512.1l18.3-239.4 18.4 239.4z' fill='#B6CDEF' /> + <path + d='M529.7 527.1H493c-4.2 0-8.2-1.7-11-4.8s-4.3-7.2-4-11.3l18.3-239.5c0.6-7.8 7.1-13.9 15-13.9 7.8 0 14.4 6 15 13.9L544.6 511c0.3 4.2-1.1 8.3-4 11.3s-6.8 4.8-10.9 4.8z m-20.5-30h4.3l-2.1-27.9-2.2 27.9z' + fill='#0F53A8' /> + <path d='M517.5 499.7l138.9 162-162-138.9z' fill='#B6CDEF' /> + <path + d='M656.4 676.7c-3.5 0-6.9-1.2-9.8-3.6l-162-138.9c-3.2-2.7-5.1-6.6-5.2-10.8-0.2-4.2 1.4-8.2 4.4-11.2l23-23c3-3 7-4.5 11.2-4.4 4.2 0.2 8.1 2.1 10.8 5.2l138.9 162c5.1 6 4.8 14.8-0.8 20.4-2.8 2.8-6.6 4.3-10.5 4.3zM516.5 521.9l0.9 0.8-0.8-0.9-0.1 0.1z' + fill='#0F53A8' /> + <path d='M242 616.5l266-104.4' fill='#B6CDEF' /> + <path + d='M242 631.5c-6 0-11.6-3.6-14-9.5-3-7.7 0.8-16.4 8.5-19.4l266-104.3c7.7-3 16.4 0.8 19.4 8.5s-0.8 16.4-8.5 19.4l-266 104.3c-1.7 0.6-3.6 1-5.4 1z' + fill='#0F53A8' /> + <path d='M511.3 512.1m-30.1 0a30.1 30.1 0 1 0 60.2 0 30.1 30.1 0 1 0-60.2 0Z' fill='#B6CDEF' /> + <path + d='M511.3 557.3c-24.9 0-45.1-20.2-45.1-45.1 0-24.9 20.2-45.1 45.1-45.1s45.1 20.2 45.1 45.1c0.1 24.8-20.2 45.1-45.1 45.1z m0-60.3c-8.3 0-15.1 6.8-15.1 15.1s6.8 15.1 15.1 15.1 15.1-6.8 15.1-15.1c0.1-8.3-6.7-15.1-15.1-15.1z' + fill='#0F53A8' /> +</svg> \ No newline at end of file From 354b8cd37015b39442ae893a6d552f6f60746667 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 01:31:12 +1000 Subject: [PATCH 20/87] Fix build error --- frontend/src/components/Blocks.tsx | 2 -- frontend/src/components/blocks.module.scss | 4 ---- frontend/src/pages/CheckerPage.tsx | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 frontend/src/components/blocks.module.scss diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index d72de52..ed3f45d 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -3,8 +3,6 @@ import { Fragment } from 'react'; import BlockConnectLine from '@/components/BlockConnectLine'; import BlockImage from '@/components/images/block-image/BlockImage'; -import styles from './blocks.module.scss'; - export interface Block { number: number; confirmed: boolean; diff --git a/frontend/src/components/blocks.module.scss b/frontend/src/components/blocks.module.scss deleted file mode 100644 index 17bae73..0000000 --- a/frontend/src/components/blocks.module.scss +++ /dev/null @@ -1,4 +0,0 @@ -.animate { - -webkit-transition: transform 1.5s; - transition: transform 1.5s; -} diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 580d066..f82f162 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -9,7 +9,7 @@ import { Info } from '@/types/info'; import { SearchResult } from '@/types/searchResult'; export default function CheckerPage() { - const [searchResult, setSearchResult] = useState<SearchResult>({}); + const [searchResult] = useState<SearchResult>({}); const mappedInfo: Info = { transaction: { From 57dc488bcd6ede6593c398e1558fb49c1a921281 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 16:19:25 +1000 Subject: [PATCH 21/87] Fix block images --- frontend/public/dark-blue-block.png | Bin 0 -> 4467 bytes frontend/public/dark-grey-block.png | Bin 0 -> 10157 bytes .../images/block-image/BlockImage.tsx | 13 +++++++++++-- .../images/block-image/block-image.module.scss | 8 -------- 4 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 frontend/public/dark-blue-block.png create mode 100644 frontend/public/dark-grey-block.png diff --git a/frontend/public/dark-blue-block.png b/frontend/public/dark-blue-block.png new file mode 100644 index 0000000000000000000000000000000000000000..7a1797335b573925ec41df8419631cd6bc741be5 GIT binary patch literal 4467 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 C>yoGd literal 0 HcmV?d00001 diff --git a/frontend/public/dark-grey-block.png b/frontend/public/dark-grey-block.png new file mode 100644 index 0000000000000000000000000000000000000000..403e958ffe2a5799544648abc089c044581060fd GIT binary patch literal 10157 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 bUqJi+(YF{e^i{}@00000NkvXXu0mjfE{&}^ literal 0 HcmV?d00001 diff --git a/frontend/src/components/images/block-image/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx index 5b68236..df24118 100644 --- a/frontend/src/components/images/block-image/BlockImage.tsx +++ b/frontend/src/components/images/block-image/BlockImage.tsx @@ -2,6 +2,8 @@ import { useContext } from 'react'; import { twMerge } from 'tailwind-merge'; import BlueBlock from '@/assets/blocks/blue-block.svg'; +import DarkBlueBlock from '@/assets/blocks/dark-blue-block.png'; +import DarkGreyBlock from '@/assets/blocks/dark-grey-block.png'; import GreyBlock from '@/assets/blocks/grey-block.svg'; import { Block } from '@/components/Blocks'; import { ThemeContext } from '@/contexts/themeContext'; @@ -37,8 +39,15 @@ export default function BlockImage(props: BlockImageProps) { <div className='shrink-0 relative w-[35px] h-[37.82px] text-lg leading-none'> {isDarkMode ? ( <> - <div className={`z-30 absolute left-0 top-0 ${styles.darkBlock} ${styles.darkGreyBlock}`} /> - <div className={`z-40 absolute left-0 top-0 ${styles.darkBlock} ${styles.darkBlueBlock} ${styles.animate} ${block.confirmed ? styles.show : styles.hide}`} /> + {/* The provided dark block image size are different from the light block images, therefore we use background image to adjust width */} + <div + className={`z-30 absolute left-0 top-0 ${styles.darkBlock}`} + style={{ backgroundImage: `url(${DarkGreyBlock})` }} + /> + <div + className={`z-40 absolute left-0 top-0 ${styles.darkBlock} ${styles.animate} ${block.confirmed ? styles.show : styles.hide}`} + style={{ backgroundImage: `url(${DarkBlueBlock})` }} + /> </> ) : ( <> diff --git a/frontend/src/components/images/block-image/block-image.module.scss b/frontend/src/components/images/block-image/block-image.module.scss index c5ac6d1..22717c9 100644 --- a/frontend/src/components/images/block-image/block-image.module.scss +++ b/frontend/src/components/images/block-image/block-image.module.scss @@ -17,11 +17,3 @@ background-size: cover; margin: 0 -8px; } - -.darkBlueBlock { - background-image: url('src/assets/blocks/dark-blue-block.png'); -} - -.darkGreyBlock { - background-image: url('src/assets/blocks/dark-grey-block.png'); -} From be8f5fdae2b1a5c215952eba79dbf96d1664612e Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 16:22:18 +1000 Subject: [PATCH 22/87] Fix animation when switch theme --- .../src/components/images/block-image/block-image.module.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/images/block-image/block-image.module.scss b/frontend/src/components/images/block-image/block-image.module.scss index 22717c9..f209c5e 100644 --- a/frontend/src/components/images/block-image/block-image.module.scss +++ b/frontend/src/components/images/block-image/block-image.module.scss @@ -1,6 +1,6 @@ .animate { - -webkit-transition: all 0.5s ease-out; - transition: all 0.5s ease-out; + -webkit-transition: transform 0.5s ease-out; + transition: transform 0.5s ease-out; } .hide { From 4fa5c5f9f045e91eb9385b51cb19823affb142e0 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 17:05:24 +1000 Subject: [PATCH 23/87] Remove png in public folder --- frontend/public/dark-blue-block.png | Bin 4467 -> 0 bytes frontend/public/dark-grey-block.png | Bin 10157 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 frontend/public/dark-blue-block.png delete mode 100644 frontend/public/dark-grey-block.png diff --git a/frontend/public/dark-blue-block.png b/frontend/public/dark-blue-block.png deleted file mode 100644 index 7a1797335b573925ec41df8419631cd6bc741be5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4467 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 C>yoGd diff --git a/frontend/public/dark-grey-block.png b/frontend/public/dark-grey-block.png deleted file mode 100644 index 403e958ffe2a5799544648abc089c044581060fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10157 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 bUqJi+(YF{e^i{}@00000NkvXXu0mjfE{&}^ From 093f75019d025b70f1fb8f68bc0a85ebe2e157cc Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 22:46:42 +1000 Subject: [PATCH 24/87] Simple tooltip component --- .../src/components/blocks-info/BlocksInfo.tsx | 8 +++- .../src/components/blocks-info/constants.ts | 10 ++--- frontend/src/components/tooltip/Tooltip.tsx | 40 +++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/tooltip/Tooltip.tsx diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index e89bb7a..fb3716d 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -1,10 +1,11 @@ import { - BlockCell, BlocksInfoItem + BlockCell, BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import { cellWith } from '@/components/blocks-info/constants'; import { MasterNodeTitle } from '@/components/blocks-info/master-node-title/MasterNodeTitle'; import InfiniteList from '@/components/infinite-list/InfiniteList'; import Title from '@/components/title/Title'; +import Tooltip from '@/components/tooltip/Tooltip'; interface BlocksInfoProps { title: string; @@ -55,7 +56,10 @@ function BlocksInfoHeading({ type }: BlocksInfoHeadingProps) { <BlockCell className={cellWith.recentBlocks.hash}>Hash</BlockCell> <BlockCell className={cellWith.recentBlocks.proposedBy}>Proposed By</BlockCell> <BlockCell className={cellWith.recentBlocks.status}>Status</BlockCell> - <BlockCell className={cellWith.recentBlocks.time}>Time</BlockCell> + <BlockCell className={cellWith.recentBlocks.time}> + Time + {/* <div className='inline-flex'>Time <Tooltip text='The time passed after the block get confirmed.' buttonClassName='ml-1' /></div> */} + </BlockCell> </div> ); } diff --git a/frontend/src/components/blocks-info/constants.ts b/frontend/src/components/blocks-info/constants.ts index 9173e98..a727a66 100644 --- a/frontend/src/components/blocks-info/constants.ts +++ b/frontend/src/components/blocks-info/constants.ts @@ -1,16 +1,16 @@ export const cellWith = { recentBlocks: { 'height': 'w-[85px]', - 'hash': 'w-[144px]', - 'proposedBy': 'w-[144px]', - 'status': 'w-[75px]', + 'hash': 'w-[140px]', + 'proposedBy': 'w-[140px]', + 'status': 'w-[70px]', 'time': 'w-[50px]', }, masterNodes: { 'number': 'w-[64px]', - 'account': 'w-[144px]', + 'account': 'w-[140px]', 'role': 'w-[290px]', 'activity': 'w-[100px]', - 'lastedParticipatedBlock': 'w-[144px]', + 'lastedParticipatedBlock': 'w-[140px]', } }; \ No newline at end of file diff --git a/frontend/src/components/tooltip/Tooltip.tsx b/frontend/src/components/tooltip/Tooltip.tsx new file mode 100644 index 0000000..364a1ab --- /dev/null +++ b/frontend/src/components/tooltip/Tooltip.tsx @@ -0,0 +1,40 @@ +import { twMerge } from 'tailwind-merge'; + +interface TooltipProps { + text: string; + buttonClassName?: string; + tooltipClassName?: string; +} +export default function Tooltip({ text, buttonClassName, tooltipClassName }: TooltipProps) { + return ( + <> + <button + data-tooltip-target='tooltip-default' + type='button' + className={twMerge(` + group w-1 h-1 flex items-center justify-center relative text-xs + rounded-full text-white bg-slate-300 p-2 leading-tight + dark:bg-blue-600 + `, buttonClassName)} + > + ? + <div + id='tooltip-default' + role='tooltip' + className={` + ${twMerge(` + w-[145px] absolute -left-[120px] top-[22px] z-20 p-2 text-sm font-medium text-white transition-opacity + duration-500 bg-gray-600 rounded-lg shadow-sm opacity-0 + group-hover:opacity-100 + dark:bg-gray-700 + `, tooltipClassName)} + `} + > + {text} + <div className='tooltip-arrow' data-popper-arrow></div> + </div> + </button> + + </> + ); +} \ No newline at end of file From b14c53a3080d84dad9207793277cd84e67181dea Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 22:48:26 +1000 Subject: [PATCH 25/87] Rename context --- frontend/src/App.tsx | 4 ++-- .../blocks-info/blocks-info-item/BlocksInfoItem.tsx | 2 +- frontend/src/components/images/block-image/BlockImage.tsx | 2 +- frontend/src/components/info-list/InfoList.tsx | 2 +- frontend/src/components/search-not-found/SearchNotFound.tsx | 2 +- frontend/src/components/theme-switch/ThemeSwitch.tsx | 2 +- frontend/src/pages/HomePage.tsx | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 721ca5f..b4f3913 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,8 +4,8 @@ import { Outlet, useNavigation } from 'react-router-dom'; import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; -import { ThemeContext } from '@/contexts/themeContext'; -import { TimeContext } from '@/contexts/timeContext'; +import { ThemeContext } from '@/contexts/ThemeContext'; +import { TimeContext } from '@/contexts/TimeContext'; import { getUnixTime, pollingPeriod } from '@/utils/time'; function App() { diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index fd54934..91b8148 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -2,8 +2,8 @@ import { PropsWithChildren, useContext } from 'react'; import { cellWith } from '@/components/blocks-info/constants'; import Svg, { SvgNames } from '@/components/images/Svg'; +import { TimeContext } from '@/contexts/TimeContext'; import { formatHash } from '@/utils/formatter'; -import { TimeContext } from '@/contexts/timeContext'; export type BlocksInfoItem = RecentBlock | MasterNode; type BlocksInfoItemProps = BlocksInfoItem; diff --git a/frontend/src/components/images/block-image/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx index df24118..4651efd 100644 --- a/frontend/src/components/images/block-image/BlockImage.tsx +++ b/frontend/src/components/images/block-image/BlockImage.tsx @@ -6,7 +6,7 @@ import DarkBlueBlock from '@/assets/blocks/dark-blue-block.png'; import DarkGreyBlock from '@/assets/blocks/dark-grey-block.png'; import GreyBlock from '@/assets/blocks/grey-block.svg'; import { Block } from '@/components/Blocks'; -import { ThemeContext } from '@/contexts/themeContext'; +import { ThemeContext } from '@/contexts/ThemeContext'; import styles from './block-image.module.scss'; diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 07044e1..97262f8 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; import Title from '@/components/title/Title'; -import { ThemeContext } from '@/contexts/themeContext'; +import { ThemeContext } from '@/contexts/ThemeContext'; import styles from './info-list.module.scss'; diff --git a/frontend/src/components/search-not-found/SearchNotFound.tsx b/frontend/src/components/search-not-found/SearchNotFound.tsx index a0efb55..9020c2f 100644 --- a/frontend/src/components/search-not-found/SearchNotFound.tsx +++ b/frontend/src/components/search-not-found/SearchNotFound.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; -import { ThemeContext } from '@/contexts/themeContext'; +import { ThemeContext } from '@/contexts/ThemeContext'; export default function SearchNotFound() { const { theme } = useContext(ThemeContext); diff --git a/frontend/src/components/theme-switch/ThemeSwitch.tsx b/frontend/src/components/theme-switch/ThemeSwitch.tsx index 851b6dd..203d544 100644 --- a/frontend/src/components/theme-switch/ThemeSwitch.tsx +++ b/frontend/src/components/theme-switch/ThemeSwitch.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren, useContext, useEffect, useState } from 'react'; import { InlineSvg, InlineSvgColours, InlineSvgNames } from '@/components/images/Svg'; -import { ThemeContext } from '@/contexts/themeContext'; +import { ThemeContext } from '@/contexts/ThemeContext'; export default function ThemeSwitch() { const [selectedTheme, setSelectedTheme] = useState<ThemeModes>('dark'); diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 9ea1c56..7c33fb1 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -6,7 +6,7 @@ import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; import { baseUrl } from '@/constants/urls'; -import { TimeContext } from '@/contexts/timeContext'; +import { TimeContext } from '@/contexts/TimeContext'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; import type { HomeLoaderData } from '@/types/loaderData'; From 67bf027246194c5cc337978db294bf08d6f07bae Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 22:58:57 +1000 Subject: [PATCH 26/87] Add option to get data when tab is active --- frontend/src/App.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b4f3913..55a2acc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -14,6 +14,20 @@ function App() { const navigation = useNavigation(); useEffect(() => { + /** + * The following code runs timer only when tab is active + */ + // let intervalId: number; + + // window.addEventListener('focus', () => { + // intervalId = setInterval(() => { + // setCurrentUnixTime(getUnixTime()); + // }, pollingPeriod); + // }); + + // window.addEventListener('blur', () => { + // clearInterval(intervalId); + // }); const intervalId = setInterval(() => { setCurrentUnixTime(getUnixTime()); }, pollingPeriod); From 238795ab94c3ba3c86eac26acb6f5684b68ed0d6 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sun, 9 Jul 2023 23:05:14 +1000 Subject: [PATCH 27/87] Update ErrorPage styling --- frontend/src/pages/ErrorPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/ErrorPage.tsx b/frontend/src/pages/ErrorPage.tsx index 68c54f9..ff4d984 100644 --- a/frontend/src/pages/ErrorPage.tsx +++ b/frontend/src/pages/ErrorPage.tsx @@ -9,10 +9,10 @@ export default function ErrorPage() { const error = useRouteError() as ErrorType; return ( - <div> + <div className='w-full pt-[300px] flex items-center justify-center flex-col'> <h1>Oops!</h1> - <p>Sorry, an unexpected error has occurred.</p> - <p> + <p>An unexpected error has occurred.</p> + <p className='pt-5 text-lg'> <i>{error.statusText || error.message}</i> </p> </div> From 7da2cca38007a98b8e83aa110b9b5e966cdbdf32 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 14:31:07 +1000 Subject: [PATCH 28/87] Tweak UI by feedback --- frontend/index.html | 2 +- frontend/src/assets/xdc-logo.svg | 9 +++++++++ frontend/src/components/Blocks.tsx | 4 ++-- .../src/components/blocks-info/BlocksInfo.tsx | 9 +++++---- .../blocks-info-item/BlocksInfoItem.tsx | 2 +- frontend/src/components/blocks-info/constants.ts | 10 +++++----- .../confirmation-status/ConfirmationStatus.tsx | 2 +- frontend/src/components/images/Svg.tsx | 7 +++++++ .../components/images/block-image/BlockImage.tsx | 16 ++++++++-------- frontend/src/components/info-cards/InfoCards.tsx | 4 ++-- frontend/src/components/nav/Nav.tsx | 7 ++++--- frontend/src/components/title/Title.tsx | 3 +-- frontend/src/pages/HomePage.tsx | 4 ++-- 13 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 frontend/src/assets/xdc-logo.svg diff --git a/frontend/index.html b/frontend/index.html index b0d398b..c4c2767 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,7 +5,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> - <link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;700&display=swap" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;500;700&display=swap" rel="stylesheet"> <link rel="icon" href="/src/assets/icon/icon.ico"> <title>Subnet blockchain diff --git a/frontend/src/assets/xdc-logo.svg b/frontend/src/assets/xdc-logo.svg new file mode 100644 index 0000000..9a3ecda --- /dev/null +++ b/frontend/src/assets/xdc-logo.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index ed3f45d..2d79b31 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -65,13 +65,13 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock
{/* 'Block 1' text */} -
+
Block
1
{/* First confirmed left brace */} -
+
{/* Left brace layer mask */}
diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index fb3716d..2c5d281 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -5,7 +5,8 @@ import { cellWith } from '@/components/blocks-info/constants'; import { MasterNodeTitle } from '@/components/blocks-info/master-node-title/MasterNodeTitle'; import InfiniteList from '@/components/infinite-list/InfiniteList'; import Title from '@/components/title/Title'; -import Tooltip from '@/components/tooltip/Tooltip'; + +// import Tooltip from '@/components/tooltip/Tooltip'; interface BlocksInfoProps { title: string; @@ -26,7 +27,7 @@ export default function BlocksInfo({ title, data, fetchMoreData }: BlocksInfoPro ) : ( )} - <div className='mt-6 h-[400px] overflow-hidden hover:overflow-auto relative dark:text-text-dark-100'> + <div className='mt-0 h-[400px] overflow-hidden hover:overflow-auto relative dark:text-text-dark-100'> <> <BlocksInfoHeading type={data[0].type} /> {fetchMoreData ? ( @@ -51,11 +52,11 @@ interface BlocksInfoHeadingProps { function BlocksInfoHeading({ type }: BlocksInfoHeadingProps) { if (type === 'recent-block') { return ( - <div className='flex dark:bg-bg-dark-800 bg-white sticky top-0'> + <div className='flex dark:bg-bg-dark-800 bg-white sticky top-0 items-center'> <BlockCell className={cellWith.recentBlocks.height}>Height</BlockCell> <BlockCell className={cellWith.recentBlocks.hash}>Hash</BlockCell> <BlockCell className={cellWith.recentBlocks.proposedBy}>Proposed By</BlockCell> - <BlockCell className={cellWith.recentBlocks.status}>Status</BlockCell> + <BlockCell className={cellWith.recentBlocks.status}>Confirmation Status</BlockCell> <BlockCell className={cellWith.recentBlocks.time}> Time {/* <div className='inline-flex'>Time <Tooltip text='The time passed after the block get confirmed.' buttonClassName='ml-1' /></div> */} diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index 91b8148..08266de 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -53,7 +53,7 @@ export function BlocksInfoItem(data: BlocksInfoItemProps) { <BlockImageCell className={cellWith.recentBlocks.status}> <BlockConfirmStatus committedInSubnet={data.committedInSubnet} committedInParentChain={data.committedInParentChain} /> </BlockImageCell> - <BlockCell className={cellWith.recentBlocks.time}>{getTimeDiff(data.timestamp)}</BlockCell> + <BlockCell className={cellWith.recentBlocks.time}>{getTimeDiff(data.timestamp)} ago</BlockCell> </div> ); } diff --git a/frontend/src/components/blocks-info/constants.ts b/frontend/src/components/blocks-info/constants.ts index a727a66..01e9c5f 100644 --- a/frontend/src/components/blocks-info/constants.ts +++ b/frontend/src/components/blocks-info/constants.ts @@ -1,10 +1,10 @@ export const cellWith = { recentBlocks: { - 'height': 'w-[85px]', - 'hash': 'w-[140px]', - 'proposedBy': 'w-[140px]', - 'status': 'w-[70px]', - 'time': 'w-[50px]', + 'height': 'w-[70px]', + 'hash': 'w-[125px]', + 'proposedBy': 'w-[125px]', + 'status': 'w-[100px]', + 'time': 'w-[80px]', }, masterNodes: { 'number': 'w-[64px]', diff --git a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx index 35d6f89..e769b7a 100644 --- a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx +++ b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx @@ -27,5 +27,5 @@ function ConfirmationStatusItem(props: ConfirmationStatusItemProps) { <span className='inline-block w-[120px]'>{props.name}</span> <span className='text-primary dark:text-sky-300 font-semibold leading-tight bg-sky-300 bg-opacity-20 rounded-lg px-[5px] py-[3px]'>{props.value}</span> </div> - ) + ); } diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index 958e158..2ea94ab 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -10,6 +10,7 @@ import Penalty from '@/assets/penalty.svg'; import Rhombus from '@/assets/rhombus.svg'; import Search from '@/assets/search.svg'; import Standby from '@/assets/standby.svg'; +import Logo from '@/assets/xdc-logo.svg'; interface GeneralSvgProps { colour: string; @@ -177,6 +178,11 @@ export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass, cl SvgComponent = InfoDark; alt = 'Info'; break; + + case SvgNames.Logo: + SvgComponent = Logo; + alt = 'XDO Logo'; + break; } return ( @@ -207,4 +213,5 @@ export enum SvgNames { NoResultDark = 'NoResultDark', Info = 'Info', InfoDark = 'InfoDark', + Logo = 'Logo', } diff --git a/frontend/src/components/images/block-image/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx index 4651efd..2634635 100644 --- a/frontend/src/components/images/block-image/BlockImage.tsx +++ b/frontend/src/components/images/block-image/BlockImage.tsx @@ -62,12 +62,12 @@ export default function BlockImage(props: BlockImageProps) { <StatusText text='Confirmed' translateAmount={props.confirmedBlocksMidIndex * props.blockSize} - className='-left-[28px]' + className='-left-[18px]' /> <StatusText text='Not Confirmed' translateAmount={props.notConfirmedBlocksMidIndex * props.blockSize} - className='-left-[72px]' + className='-left-[58.5px]' /> </> )} @@ -86,7 +86,7 @@ function StatusText({ text, translateAmount, className }: BaseTextProps) { style={{ transform: `translateX(${translateAmount}px)` }} className={twMerge(` text-text-white-500 whitespace-nowrap - absolute -top-[65px] -left-[50px] px-1 flex text-lg dark:bg-bg-dark-800 bg-white z-30 dark:text-text-white-800 ${styles.animate} + absolute -top-[43px] -left-[50px] px-1 flex text-sm dark:bg-bg-dark-800 bg-white z-30 dark:text-text-white-800 ${styles.animate} `, className)} > {text} @@ -106,33 +106,33 @@ function StatusBrace({ isLastConfirmed, isFirstUnConfirmed, isLast }: BlockImage } return ( - <div className='absolute -top-[54px] -right-[10px] -left-[10px] border-t-2 dark:border-text-white-800' /> + <div className='absolute -top-[34px] -right-[10px] -left-[10px] border-t dark:border-text-white-800' /> ); } function BraceStart() { return ( - <div className='absolute -top-[54px] -right-[9px] left-[16px] pt-[20px] dark:text-primary flex border-t-2 border-l-2 rounded-tl-[20px] dark:border-text-white-800' /> + <div className='absolute -top-[34px] -right-[9px] left-[16px] pt-[20px] dark:text-primary flex border-t border-l rounded-tl-[20px] dark:border-text-white-800' /> ); } function BraceEnd() { return ( - <div className='absolute -top-[54px] right-[16px] -left-[9px] pt-[20px] dark:text-primary flex border-t-2 border-r-2 rounded-tr-[20px] dark:border-text-white-800' /> + <div className='absolute -top-[34px] right-[16px] -left-[9px] pt-[20px] dark:text-primary flex border-t border-r rounded-tr-[20px] dark:border-text-white-800' /> ); } function BlockNumber({ block, isLastConfirmed, isLast }: BlockImageProps) { if (isLastConfirmed) { return ( - <div className='absolute top-[64px] right-0 text-primary flex'> + <div className='absolute top-[60px] right-0 text-primary flex text-sm'> <div>Block</div> <div className='pl-1'>{block.number}</div> </div> ); } else if (isLast) { return ( - <div className='absolute top-[64px] right-0 text-primary flex'> + <div className='absolute top-[60px] right-0 text-primary flex text-sm'> <div>Block</div> <div className='pl-1'>{block.number}</div> </div> diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 870dd92..be51dda 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -58,7 +58,7 @@ export default function InfoCards() { masterNodes: { data: [ { name: 'Current committee size', value: loaderData.masterNodes.summary.committee }, - { name: 'Activity', value: `${loaderData.masterNodes.summary.activeNodes}(active) ${loaderData.masterNodes.summary.inActiveNodes}(inactive)` }, + { name: 'Activity(active / inactive)', value: `${loaderData.masterNodes.summary.activeNodes} / ${loaderData.masterNodes.summary.committee - loaderData.masterNodes.summary.activeNodes}` }, { name: 'Number of standby nodes', value: loaderData.masterNodes.summary.inActiveNodes }, ], }, @@ -69,7 +69,7 @@ export default function InfoCards() { type: 'master-node', account: formatHash(v.address), number: i + 1 - })) + })); const fetchMoreRecentBlocks = () => { if (!recentBlocks) { diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index 258d571..21c15c6 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -3,6 +3,7 @@ import { useLoaderData } from 'react-router-dom'; import CheckerImage from '@/components/images/CheckerImage'; import HouseImage from '@/components/images/HouseImage'; import ManagementImage from '@/components/images/ManagementImage'; +import Svg, { SvgNames } from '@/components/images/Svg'; import NavItem from '@/components/nav-item/NavItem'; import ThemeSwitch from '@/components/theme-switch/ThemeSwitch'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; @@ -17,15 +18,15 @@ export default function Nav(): JSX.Element { return ( <nav id='nav' className={`sticky top-0 dark:bg-bg-dark-1000 w-[246px] ${isDesktopL ? 'h-[1024px]' : 'h-[600px]'} max-h-screen shrink-0 shadow-grey flex flex-col justify-between`}> <div> - <div className='flex items-center flex-col border-b-[1px] border-text-white dark:border-border-light'> + <div className='flex items-center flex-col border-text-white dark:border-border-light'> <div className='pt-12 font-bold text-[26px]'> - Logo: TODO + <Svg svgName={SvgNames.Logo} sizeClass='w-[80px]' /> </div> <div className='py-6 dark:text-sky-300'> {loaderData.name} </div> </div> - <NavItem Image={HouseImage} text='Home' className='pt-[72px]' page='home' /> + <NavItem Image={HouseImage} text='Home' className='pt-2' page='home' /> <NavItem Image={CheckerImage} text='Confirmation Checker' page='checker' /> <NavItem Image={ManagementImage} text='Management' page='management' /> </div> diff --git a/frontend/src/components/title/Title.tsx b/frontend/src/components/title/Title.tsx index 3bdd4b9..5808938 100644 --- a/frontend/src/components/title/Title.tsx +++ b/frontend/src/components/title/Title.tsx @@ -5,8 +5,7 @@ interface TitleProps { export default function Title({ title }: TitleProps) { return ( <div className='h-[62px] flex items-center'> - <div className='text-2xl font-bold leading-tight'>{title}</div> + <div className='text-xl font-medium leading-tight'>{title}</div> </div> ); } - \ No newline at end of file diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 7c33fb1..08b0231 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -59,7 +59,7 @@ export default function HomePage() { return ( <div className='grid gap-6 grid-col-1'> <Card> - <h1 className='pb-6 text-3xl font-bold'>Subnet Blockchain</h1> + <h1 className='pb-4 text-xl font-medium'>Subnet Blockchain</h1> <Blocks initialLastBlock={initialLastBlock} lastBlock={lastBlock} @@ -69,7 +69,7 @@ export default function HomePage() { /> </Card> <Card> - <h1 className='pb-6 text-3xl font-bold'>Copy at the parent chain</h1> + <h1 className='pb-4 text-xl font-medium'>Copy at the parent chain</h1> <Blocks initialLastBlock={initialLastBlock} lastBlock={lastBlock} From c4185d00f1925b8f20e162fb341719bd6c65581b Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 15:28:43 +1000 Subject: [PATCH 29/87] Update icon --- frontend/src/assets/icon/icon.ico | Bin 4286 -> 4286 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/src/assets/icon/icon.ico b/frontend/src/assets/icon/icon.ico index ab726be5244622322a3e80b118b0e2856e2a21a0..195ed497a6c9c6d7ac2446262bbe563accee6aaf 100644 GIT binary patch literal 4286 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 Z=^;LrGcLzWat5?OCd+0F#$rsy{u`mq`&j@0 literal 4286 zcmeHKStzbs82(J#Mwvo{5@KgALx$8T$xcc_rX&$%2pOUfv0b=Ow1sf7&6#q+6_HFS zV_Pmr+$huD37L7%|Gc~P)jlq^{eQ<fbvjGy_x<Z%>wVt!4$u33lBA*fv$K=%DGeA) zl7%EmA5>GdB>kb9Sm&Yo&B5cx{IMWO^#6F31)iUu>Fd|8)Z5!j-QC?h$H&L%?Ck7S z=ap`no}MO0M@Q1p(IIVZZJv61dX$oqLKhboO2z;3e13jDsbYpF@U^tG_}R$Fh^D5d zetBNWhKGj-%FfP~vCGQJsHmujZK0u|LEhfpR8UYrBO@bpa&n>s>c!cutu4yS%Ogum zOU4IYXJ;p2U91Vaz#l<DK{PZpM0a<0FJ%4B0`A<?(?ibA&WsBT*xb&}jt~p?_xIG$ z&_H2fVdUlIMeqZ3!#+IM*Vil30oW!cCbG@_{r#!Bx|-VC+o`FkiHeJhDK|Hl3JVMA z^XJbrJUmR9nVDp4Y%J^0-{1c`SC;qQ-riDlbTrGsol;X%X>f3m5)%{I=9-$C{F|`5 zzP>(1Mn-ae!Z$+ifPerxIy#c&zV_ki>51b>$ai&hC0kot8522(dzzV<k%57MY<H21 zSy@@U7kW)hOuQEMr+s^SdlVWPDq}(yU0q%J`0*okbac?l$_nl5><~D}-QAt{IXF1b z;o%{#K}Vc{uhbp8ySo$>6-Bs*wY4?J)cN^2i@UnIqPe*_0xwutSTGj!K>lGJb^(4( zO^v$neD3@A?<^1bhMd^n-{(7pgoMbkCTvqzSI1}C+S*9ONNa1WB7AWe91B~zxVXsN zCvbgzeF>P?*VnYXyi6V*9(;EzD=S)CTN5JG{(pRY%sBx60~0x6V`D?f$;r%lh;jHG z`Z+l{@gD30Z-FP24DcAZRoD}8P*G7q+uPe*`(|fnsidTYV-R%#xZtVS*jUy{2{z8o z&(D*&xjD;)?Pq3Y=<@QC`uh4fKfras6ccu-s;Z*X(^I9;eE!p?Pb~Z0yLa^E%NOQU za38o{V7`C<o;*E033X|6bCYubaS9$&G9DivDI+6;aS%($$AyIjnct9i;GD6sF~$Vm z=;$Z~2M3eAy*=gR<fz4eLTBKor>Fmf4;~hNj*E+<#l=NhU0vnAC?O$%YmcxCVg@m) z#24TX$V2P{dwqRfuDe)+{|yZdxfc+);D5v^zC#E2P~CxeGc`43S$=+geAnjYX08V! zCj<`mg@=bzOG^vE7DAu+_;|JR@aE=*($dmoS#EA_T=x-+m6erTf6L3u2|5i73{Y)t zE%zXT|G~qk7fQy#!2v}?M96gx{zsk#1_n}0Objh8Epc8XB_+u`fx9CH!HY`s0J?+U zN=r*$*c~|N4^Su2d*M5B6LGt-u|eqD|E_`hgLi<&#zw+j1uvmTLl5NR<HPvq3&8_# zIl#-fw}>%tuIT@u7xLt-$K>QB`}nW#B+%=hcqc)h20Nn`;GL|itBZS|Z{NPX_0|6~ L@>TKkuZ}+fAZgbF From 5e0b2df3c955781d67ecd2a8d90d9fb374deb8be Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 15:39:23 +1000 Subject: [PATCH 30/87] Add theme in localstorage --- frontend/src/components/theme-switch/ThemeSwitch.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/theme-switch/ThemeSwitch.tsx b/frontend/src/components/theme-switch/ThemeSwitch.tsx index 203d544..e8fc1c9 100644 --- a/frontend/src/components/theme-switch/ThemeSwitch.tsx +++ b/frontend/src/components/theme-switch/ThemeSwitch.tsx @@ -4,7 +4,14 @@ import { InlineSvg, InlineSvgColours, InlineSvgNames } from '@/components/images import { ThemeContext } from '@/contexts/ThemeContext'; export default function ThemeSwitch() { - const [selectedTheme, setSelectedTheme] = useState<ThemeModes>('dark'); + const [selectedTheme, setSelectedTheme] = useState<ThemeModes>(() => { + const theme = window.localStorage.getItem('theme'); + if (theme !== 'light' && theme !== 'dark') { + return 'dark'; + } + + return theme; + }); const { setTheme } = useContext(ThemeContext); useEffect(() => { @@ -13,11 +20,13 @@ export default function ThemeSwitch() { if (selectedTheme === 'dark') { window.document.body.classList.add('dark'); document.body.style.backgroundColor = 'black'; + window.localStorage.setItem('theme', 'dark'); return; } window.document.body.classList.remove('dark'); document.body.style.backgroundColor = 'white'; + window.localStorage.setItem('theme', 'light'); }, [setTheme, selectedTheme]); return ( From b497c80f0b7d06ed72e673d7030bf0a1fc595807 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 19:23:00 +1000 Subject: [PATCH 31/87] Rename context --- frontend/src/App.tsx | 2 +- frontend/src/components/images/block-image/BlockImage.tsx | 2 +- frontend/src/components/info-list/InfoList.tsx | 2 +- frontend/src/components/search-not-found/SearchNotFound.tsx | 2 +- frontend/src/components/theme-switch/ThemeSwitch.tsx | 2 +- frontend/src/contexts/{themeContext.tsx => ThemeContext2.tsx} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename frontend/src/contexts/{themeContext.tsx => ThemeContext2.tsx} (100%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 55a2acc..2fcb73b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,7 +4,7 @@ import { Outlet, useNavigation } from 'react-router-dom'; import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; -import { ThemeContext } from '@/contexts/ThemeContext'; +import { ThemeContext } from '@/contexts/ThemeContext2'; import { TimeContext } from '@/contexts/TimeContext'; import { getUnixTime, pollingPeriod } from '@/utils/time'; diff --git a/frontend/src/components/images/block-image/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx index 2634635..14e1629 100644 --- a/frontend/src/components/images/block-image/BlockImage.tsx +++ b/frontend/src/components/images/block-image/BlockImage.tsx @@ -6,7 +6,7 @@ import DarkBlueBlock from '@/assets/blocks/dark-blue-block.png'; import DarkGreyBlock from '@/assets/blocks/dark-grey-block.png'; import GreyBlock from '@/assets/blocks/grey-block.svg'; import { Block } from '@/components/Blocks'; -import { ThemeContext } from '@/contexts/ThemeContext'; +import { ThemeContext } from '@/contexts/ThemeContext2'; import styles from './block-image.module.scss'; diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 97262f8..090845b 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; import Title from '@/components/title/Title'; -import { ThemeContext } from '@/contexts/ThemeContext'; +import { ThemeContext } from '@/contexts/ThemeContext2'; import styles from './info-list.module.scss'; diff --git a/frontend/src/components/search-not-found/SearchNotFound.tsx b/frontend/src/components/search-not-found/SearchNotFound.tsx index 9020c2f..639bfcb 100644 --- a/frontend/src/components/search-not-found/SearchNotFound.tsx +++ b/frontend/src/components/search-not-found/SearchNotFound.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; -import { ThemeContext } from '@/contexts/ThemeContext'; +import { ThemeContext } from '@/contexts/ThemeContext2'; export default function SearchNotFound() { const { theme } = useContext(ThemeContext); diff --git a/frontend/src/components/theme-switch/ThemeSwitch.tsx b/frontend/src/components/theme-switch/ThemeSwitch.tsx index e8fc1c9..a99cf74 100644 --- a/frontend/src/components/theme-switch/ThemeSwitch.tsx +++ b/frontend/src/components/theme-switch/ThemeSwitch.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren, useContext, useEffect, useState } from 'react'; import { InlineSvg, InlineSvgColours, InlineSvgNames } from '@/components/images/Svg'; -import { ThemeContext } from '@/contexts/ThemeContext'; +import { ThemeContext } from '@/contexts/ThemeContext2'; export default function ThemeSwitch() { const [selectedTheme, setSelectedTheme] = useState<ThemeModes>(() => { diff --git a/frontend/src/contexts/themeContext.tsx b/frontend/src/contexts/ThemeContext2.tsx similarity index 100% rename from frontend/src/contexts/themeContext.tsx rename to frontend/src/contexts/ThemeContext2.tsx From 51a6b2e573c9f170628b931c2f5bcd6613dbdf2b Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 19:24:30 +1000 Subject: [PATCH 32/87] Rename context --- frontend/src/App.tsx | 2 +- .../components/blocks-info/blocks-info-item/BlocksInfoItem.tsx | 2 +- frontend/src/contexts/{timeContext.tsx => TimeContext2.tsx} | 0 frontend/src/pages/HomePage.tsx | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename frontend/src/contexts/{timeContext.tsx => TimeContext2.tsx} (100%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2fcb73b..c7e9835 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,7 +5,7 @@ import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; import { ThemeContext } from '@/contexts/ThemeContext2'; -import { TimeContext } from '@/contexts/TimeContext'; +import { TimeContext } from '@/contexts/TimeContext2'; import { getUnixTime, pollingPeriod } from '@/utils/time'; function App() { diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index 08266de..7d147a1 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -2,7 +2,7 @@ import { PropsWithChildren, useContext } from 'react'; import { cellWith } from '@/components/blocks-info/constants'; import Svg, { SvgNames } from '@/components/images/Svg'; -import { TimeContext } from '@/contexts/TimeContext'; +import { TimeContext } from '@/contexts/TimeContext2'; import { formatHash } from '@/utils/formatter'; export type BlocksInfoItem = RecentBlock | MasterNode; diff --git a/frontend/src/contexts/timeContext.tsx b/frontend/src/contexts/TimeContext2.tsx similarity index 100% rename from frontend/src/contexts/timeContext.tsx rename to frontend/src/contexts/TimeContext2.tsx diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 08b0231..4973dc5 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -6,7 +6,7 @@ import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; import { baseUrl } from '@/constants/urls'; -import { TimeContext } from '@/contexts/TimeContext'; +import { TimeContext } from '@/contexts/TimeContext2'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; import type { HomeLoaderData } from '@/types/loaderData'; From 7d8458632b88735aa952c5b206fe58bba135b4fb Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 19:32:34 +1000 Subject: [PATCH 33/87] Rename contexts back --- frontend/src/App.tsx | 4 ++-- .../blocks-info/blocks-info-item/BlocksInfoItem.tsx | 2 +- frontend/src/components/images/block-image/BlockImage.tsx | 2 +- frontend/src/components/info-list/InfoList.tsx | 2 +- frontend/src/components/search-not-found/SearchNotFound.tsx | 2 +- frontend/src/components/theme-switch/ThemeSwitch.tsx | 2 +- frontend/src/contexts/{ThemeContext2.tsx => ThemeContext.tsx} | 0 frontend/src/contexts/{TimeContext2.tsx => TimeContext.tsx} | 0 frontend/src/pages/HomePage.tsx | 2 +- 9 files changed, 8 insertions(+), 8 deletions(-) rename frontend/src/contexts/{ThemeContext2.tsx => ThemeContext.tsx} (100%) rename frontend/src/contexts/{TimeContext2.tsx => TimeContext.tsx} (100%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c7e9835..55a2acc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,8 +4,8 @@ import { Outlet, useNavigation } from 'react-router-dom'; import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; -import { ThemeContext } from '@/contexts/ThemeContext2'; -import { TimeContext } from '@/contexts/TimeContext2'; +import { ThemeContext } from '@/contexts/ThemeContext'; +import { TimeContext } from '@/contexts/TimeContext'; import { getUnixTime, pollingPeriod } from '@/utils/time'; function App() { diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index 7d147a1..08266de 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -2,7 +2,7 @@ import { PropsWithChildren, useContext } from 'react'; import { cellWith } from '@/components/blocks-info/constants'; import Svg, { SvgNames } from '@/components/images/Svg'; -import { TimeContext } from '@/contexts/TimeContext2'; +import { TimeContext } from '@/contexts/TimeContext'; import { formatHash } from '@/utils/formatter'; export type BlocksInfoItem = RecentBlock | MasterNode; diff --git a/frontend/src/components/images/block-image/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx index 14e1629..2634635 100644 --- a/frontend/src/components/images/block-image/BlockImage.tsx +++ b/frontend/src/components/images/block-image/BlockImage.tsx @@ -6,7 +6,7 @@ import DarkBlueBlock from '@/assets/blocks/dark-blue-block.png'; import DarkGreyBlock from '@/assets/blocks/dark-grey-block.png'; import GreyBlock from '@/assets/blocks/grey-block.svg'; import { Block } from '@/components/Blocks'; -import { ThemeContext } from '@/contexts/ThemeContext2'; +import { ThemeContext } from '@/contexts/ThemeContext'; import styles from './block-image.module.scss'; diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 090845b..97262f8 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; import Title from '@/components/title/Title'; -import { ThemeContext } from '@/contexts/ThemeContext2'; +import { ThemeContext } from '@/contexts/ThemeContext'; import styles from './info-list.module.scss'; diff --git a/frontend/src/components/search-not-found/SearchNotFound.tsx b/frontend/src/components/search-not-found/SearchNotFound.tsx index 639bfcb..9020c2f 100644 --- a/frontend/src/components/search-not-found/SearchNotFound.tsx +++ b/frontend/src/components/search-not-found/SearchNotFound.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; -import { ThemeContext } from '@/contexts/ThemeContext2'; +import { ThemeContext } from '@/contexts/ThemeContext'; export default function SearchNotFound() { const { theme } = useContext(ThemeContext); diff --git a/frontend/src/components/theme-switch/ThemeSwitch.tsx b/frontend/src/components/theme-switch/ThemeSwitch.tsx index a99cf74..e8fc1c9 100644 --- a/frontend/src/components/theme-switch/ThemeSwitch.tsx +++ b/frontend/src/components/theme-switch/ThemeSwitch.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren, useContext, useEffect, useState } from 'react'; import { InlineSvg, InlineSvgColours, InlineSvgNames } from '@/components/images/Svg'; -import { ThemeContext } from '@/contexts/ThemeContext2'; +import { ThemeContext } from '@/contexts/ThemeContext'; export default function ThemeSwitch() { const [selectedTheme, setSelectedTheme] = useState<ThemeModes>(() => { diff --git a/frontend/src/contexts/ThemeContext2.tsx b/frontend/src/contexts/ThemeContext.tsx similarity index 100% rename from frontend/src/contexts/ThemeContext2.tsx rename to frontend/src/contexts/ThemeContext.tsx diff --git a/frontend/src/contexts/TimeContext2.tsx b/frontend/src/contexts/TimeContext.tsx similarity index 100% rename from frontend/src/contexts/TimeContext2.tsx rename to frontend/src/contexts/TimeContext.tsx diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 4973dc5..08b0231 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -6,7 +6,7 @@ import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; import { baseUrl } from '@/constants/urls'; -import { TimeContext } from '@/contexts/TimeContext2'; +import { TimeContext } from '@/contexts/TimeContext'; import { useIsDesktopL } from '@/hooks/useMediaQuery'; import type { HomeLoaderData } from '@/types/loaderData'; From f6ef580f38fa28bc349837cb2d7ea5e69814b552 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 20:33:29 +1000 Subject: [PATCH 34/87] temp --- frontend/src/App.tsx | 8 +- frontend/src/assets/menu.svg | 5 ++ frontend/src/components/images/Svg.tsx | 7 ++ .../src/components/info-cards/InfoCards.tsx | 8 +- frontend/src/components/nav/Nav.tsx | 55 ++++++++++++- frontend/src/hooks/useMediaQuery.tsx | 52 +++++++++++- frontend/src/pages/HomePage.tsx | 82 +++++++++++++------ 7 files changed, 184 insertions(+), 33 deletions(-) create mode 100644 frontend/src/assets/menu.svg diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 55a2acc..b392508 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,12 +6,14 @@ import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; +import { useIsDesktop } from '@/hooks/useMediaQuery'; import { getUnixTime, pollingPeriod } from '@/utils/time'; function App() { const [theme, setTheme] = useState<ThemeModes>('light'); const [currentUnixTime, setCurrentUnixTime] = useState(getUnixTime()); const navigation = useNavigation(); + const isDesktop = useIsDesktop(); useEffect(() => { /** @@ -38,9 +40,11 @@ function App() { return ( <TimeContext.Provider value={{ currentUnixTime }}> <ThemeContext.Provider value={{ theme, setTheme }}> - <div className='relative max-w-[1440px] m-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900'> + <div className={`${!isDesktop ? 'flex-col' : ''} + relative max-w-[1440px] m-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900` + }> <Nav /> - <main className='mx-6 my-8 grow w-[1146px]'> + <main className='mx-6 my-8 grow llg-w-[1146px]'> {navigation.state === 'loading' ? ( <Loader /> ) : ( diff --git a/frontend/src/assets/menu.svg b/frontend/src/assets/menu.svg new file mode 100644 index 0000000..64b0d45 --- /dev/null +++ b/frontend/src/assets/menu.svg @@ -0,0 +1,5 @@ +<svg style="fill:#9aa0a6;width:24px;height:24px" viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg"> + <path d="M0 0h24v24H0z" fill="none"></path> + <path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path> +</svg> \ No newline at end of file diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index 2ea94ab..054b103 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -3,6 +3,7 @@ import Cross from '@/assets/cross.svg'; import InfoDark from '@/assets/info-dark.svg'; import Info from '@/assets/info.svg'; import Loading from '@/assets/loading.svg'; +import Menu from '@/assets/menu.svg'; import Miner from '@/assets/miner.svg'; import NoResultDark from '@/assets/no-results-dark.svg'; import NoResult from '@/assets/no-results.svg'; @@ -183,6 +184,11 @@ export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass, cl SvgComponent = Logo; alt = 'XDO Logo'; break; + + case SvgNames.Menu: + SvgComponent = Menu; + alt = 'Menu'; + break; } return ( @@ -214,4 +220,5 @@ export enum SvgNames { Info = 'Info', InfoDark = 'InfoDark', Logo = 'Logo', + Menu = 'Menu', } diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index be51dda..5406f26 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -86,22 +86,22 @@ export default function InfoCards() { return ( <> - <div className='grid grid-cols-2 llg:grid-cols-3 gap-6'> - <Card> + <div className='grid lg:grid-cols-2 llg:grid-cols-3 gap-6'> + <Card className='max-w-[400px]'> <InfoList title='Network Info' status={mappedInfo.network.health} info={mappedInfo.network.data} /> </Card> - <Card> + <Card className='max-w-[400px]'> <InfoList title='Relayer Info' status={mappedInfo.relayer.health} info={mappedInfo.relayer.data} /> </Card> - <Card> + <Card className='max-w-[400px]'> <InfoList title='Master Nodes Info' status={mappedInfo.masterNodes.health} diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index 21c15c6..aa33e57 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -1,4 +1,5 @@ -import { useLoaderData } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { useLoaderData, useLocation } from 'react-router-dom'; import CheckerImage from '@/components/images/CheckerImage'; import HouseImage from '@/components/images/HouseImage'; @@ -6,19 +7,67 @@ import ManagementImage from '@/components/images/ManagementImage'; import Svg, { SvgNames } from '@/components/images/Svg'; import NavItem from '@/components/nav-item/NavItem'; import ThemeSwitch from '@/components/theme-switch/ThemeSwitch'; -import { useIsDesktopL } from '@/hooks/useMediaQuery'; +import { useIsDesktop, useIsDesktopL } from '@/hooks/useMediaQuery'; import type { AppLoaderData } from '@/types/loaderData'; export type Pages = 'home' | 'checker' | 'management'; export default function Nav(): JSX.Element { const loaderData = useLoaderData() as AppLoaderData; + const isDesktop = useIsDesktop(); const isDesktopL = useIsDesktopL(); + const location = useLocation(); + const [navOpened, setNavOpened] = useState(false); + + function openNav() { + setNavOpened(true); + } + + function closeNav() { + setNavOpened(false); + } + + useEffect(() => { + if (navOpened) { + closeNav(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [location]); + + if (!isDesktop) { + if (!navOpened) { + return ( + <button onClick={openNav}> + <Svg svgName={SvgNames.Menu} /> + </button> + ); + } + + return ( + <nav className='z-[100] sticky top-0 left-0 dark:bg-bg-dark-1000 grow shadow-grey flex flex-col justify-between rounded-b-xl'> + <div> + <div className='flex items-center flex-col border-text-white dark:border-border-light relative'> + <button onClick={closeNav} className='absolute right-0 top-2 px-3 py-2 text-3xl border rounded-full'>X</button> + <div className='pt-12 font-bold text-[26px]'> + <Svg svgName={SvgNames.Logo} sizeClass='w-[80px]' /> + </div> + <div className='py-6 dark:text-sky-300'> + {loaderData.name} + </div> + </div> + <NavItem Image={HouseImage} text='Home' className='pt-2' page='home' /> + <NavItem Image={CheckerImage} text='Confirmation Checker' page='checker' /> + <NavItem Image={ManagementImage} text='Management' page='management' /> + </div> + <ThemeSwitch /> + </nav> + ); + } return ( <nav id='nav' className={`sticky top-0 dark:bg-bg-dark-1000 w-[246px] ${isDesktopL ? 'h-[1024px]' : 'h-[600px]'} max-h-screen shrink-0 shadow-grey flex flex-col justify-between`}> <div> - <div className='flex items-center flex-col border-text-white dark:border-border-light'> + <div className='flex items-center flex-col border-text-white dark:border-border-light'> <div className='pt-12 font-bold text-[26px]'> <Svg svgName={SvgNames.Logo} sizeClass='w-[80px]' /> </div> diff --git a/frontend/src/hooks/useMediaQuery.tsx b/frontend/src/hooks/useMediaQuery.tsx index 2b663c2..755b03a 100644 --- a/frontend/src/hooks/useMediaQuery.tsx +++ b/frontend/src/hooks/useMediaQuery.tsx @@ -1,3 +1,53 @@ +import { useEffect, useState } from 'react'; + +const breakpoints = { + desktopL: 1440, + desktop: 1024, + tablet: 768, +}; + export function useIsDesktopL() { - return window.screen.width >= 1440; + const { width } = useWindowDimensions(); + + return width >= breakpoints.desktopL; +} + +export function useIsDesktop() { + const { width } = useWindowDimensions(); + + return width >= breakpoints.desktop; +} + +export function useIsWiderThanTablet() { + const { width } = useWindowDimensions(); + + return width > breakpoints.tablet; +} + +export function useIsTablet() { + const { width } = useWindowDimensions(); + + return width >= breakpoints.tablet; +} + +function getWindowDimensions() { + return { + width: window.screen.width, + height: window.screen.height + }; +} + +export default function useWindowDimensions() { + const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); + + useEffect(() => { + function handleResize() { + setWindowDimensions(getWindowDimensions()); + } + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return windowDimensions; } \ No newline at end of file diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 08b0231..38ea7b3 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -5,9 +5,10 @@ import { useLoaderData } from 'react-router-dom'; import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; +import InfoList from '@/components/info-list/InfoList'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; -import { useIsDesktopL } from '@/hooks/useMediaQuery'; +import { useIsDesktop, useIsDesktopL, useIsTablet } from '@/hooks/useMediaQuery'; import type { HomeLoaderData } from '@/types/loaderData'; function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { @@ -27,8 +28,10 @@ function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: n } export default function HomePage() { const isDesktopL = useIsDesktopL(); - // use 13 blocks(desktop), otherwise use 20 blocks(XL desktop) - const blockNumber = isDesktopL ? 20 : 13; + const isDesktop = useIsDesktop(); + const isTablet = useIsTablet(); + // use 13 blocks for tablet and desktop, otherwise use 20 blocks(XL desktop) + const blockNumber = isDesktopL ? 20 : isDesktop ? 13 : 13; const loaderData = useLoaderData() as HomeLoaderData; const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); @@ -56,28 +59,61 @@ export default function HomePage() { getData(); }, [blockNumber, currentUnixTime, initialLastBlock]); + const mappedInfo = { + subnet: { + data: [{ + name: 'Last committed block number', value: lastConfirmedBlock + }, { + name: 'Last mined block number', value: lastBlock + }] + }, + parentChain: { + data: [{ + name: 'Last committed block number', value: lastParentConfirmedBlock + }, { + name: 'Last mined block number', value: lastBlock + }] + } + }; + return ( <div className='grid gap-6 grid-col-1'> - <Card> - <h1 className='pb-4 text-xl font-medium'>Subnet Blockchain</h1> - <Blocks - initialLastBlock={initialLastBlock} - lastBlock={lastBlock} - lastConfirmedBlock={lastConfirmedBlock} - blockNumber={blockNumber} - blocks={blocks} - /> - </Card> - <Card> - <h1 className='pb-4 text-xl font-medium'>Copy at the parent chain</h1> - <Blocks - initialLastBlock={initialLastBlock} - lastBlock={lastBlock} - lastConfirmedBlock={lastParentConfirmedBlock} - blockNumber={blockNumber} - blocks={parentChainBlocks} - /> - </Card> + {isTablet ? ( + <Card> + <h1 className='pb-4 text-xl font-medium'>Subnet blockchain</h1> + <Blocks + initialLastBlock={initialLastBlock} + lastBlock={lastBlock} + lastConfirmedBlock={lastConfirmedBlock} + blockNumber={blockNumber} + blocks={blocks} + /> + </Card>) : ( + <Card className='max-w-[400px]'> + <InfoList + title='Subnet blockchain' + info={mappedInfo.parentChain.data} + /> + </Card> + )} + {isTablet ? ( + <Card> + <h1 className='pb-4 text-xl font-medium'>Copy at the parent chain</h1> + <Blocks + initialLastBlock={initialLastBlock} + lastBlock={lastBlock} + lastConfirmedBlock={lastParentConfirmedBlock} + blockNumber={blockNumber} + blocks={parentChainBlocks} + /> + </Card>) : ( + <Card className='max-w-[400px]'> + <InfoList + title='Copy at the parent chain' + info={mappedInfo.parentChain.data} + /> + </Card> + )} <InfoCards /> </div> From 2066307101b10835cf5cd12d4339a0af36e1efcc Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 20:46:51 +1000 Subject: [PATCH 35/87] Mock masternodes data --- frontend/src/main.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d2b026a..3fe5606 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -29,7 +29,7 @@ export async function appLoader() { export async function homeLoader() { async function getData() { const urls = [ - `${baseUrl}/masternodes`, + // `${baseUrl}/masternodes`, `${baseUrl}/relayer`, `${baseUrl}/network`, `${baseUrl}/blocks`, @@ -41,11 +41,24 @@ export async function homeLoader() { const data = await getData(); + // return { + // masterNodes: data[0], + // relayer: data[1], + // network: data[2], + // blocks: data[3], + // }; return { - masterNodes: data[0], - relayer: data[1], - network: data[2], - blocks: data[3], + masterNodes: { + summary: { + committee: 3, + activeNodes: 3, + inActiveNodes: 0, + }, + nodes: [] + }, + relayer: data[0], + network: data[1], + blocks: data[2], }; } From dc969ab316b2355494a7bed1b5fa82b4cd23d665 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 21:15:20 +1000 Subject: [PATCH 36/87] Mock data --- frontend/src/components/Blocks.tsx | 5 +- .../src/components/blocks-info/BlocksInfo.tsx | 9 +-- .../src/components/info-cards/InfoCards.tsx | 16 ++--- frontend/src/main.tsx | 58 +++++++++++++------ frontend/src/pages/HomePage.tsx | 6 +- 5 files changed, 59 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 2d79b31..91d73d0 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -14,9 +14,10 @@ interface BlocksProps { lastConfirmedBlock: number; blockNumber: number; blocks: Block[]; + name?: string; } -export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks }: BlocksProps) { +export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks, name }: BlocksProps) { {/* n * block-width + (n - 1) * spacing */ } const blockSize = 35 + 17.99; const translateAmount = initialLastBlock ? -((lastBlock - initialLastBlock) * blockSize) : 0; @@ -39,7 +40,7 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock const isLast = index === blocks.length - 1; return ( - <Fragment key={block.number}> + <Fragment key={`${name} ${block.number}`}> <BlockImage block={block} isFirstConfirmed={isFirstVisibleConfirmed} diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index 2c5d281..ff7efb1 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -32,10 +32,10 @@ export default function BlocksInfo({ title, data, fetchMoreData }: BlocksInfoPro <BlocksInfoHeading type={data[0].type} /> {fetchMoreData ? ( <InfiniteList data={data} fetchData={fetchMoreData}> - <BlocksInfoItems data={data} /> + <BlocksInfoItems data={data} title={title} /> </InfiniteList> ) : ( - <BlocksInfoItems data={data} /> + <BlocksInfoItems data={data} title={title} /> )} </> </div> @@ -78,9 +78,10 @@ function BlocksInfoHeading({ type }: BlocksInfoHeadingProps) { interface BlocksInfoItemsProps { data: BlocksInfoItem[]; + title: string; } -function BlocksInfoItems({ data }: BlocksInfoItemsProps) { +function BlocksInfoItems({ data, title }: BlocksInfoItemsProps) { return ( <> { @@ -89,7 +90,7 @@ function BlocksInfoItems({ data }: BlocksInfoItemsProps) { className={`flex border-b-2 border-text-white-400 dark:border-opacity-40 dark:border-text-dark-400 ${index === 0 ? 'border-t-2' : ''}` } - key={d.number} + key={`${title}-${d.number}`} > <BlocksInfoItem {...d} /> </div> diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index be51dda..c203536 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -57,14 +57,14 @@ export default function InfoCards() { }, masterNodes: { data: [ - { name: 'Current committee size', value: loaderData.masterNodes.summary.committee }, - { name: 'Activity(active / inactive)', value: `${loaderData.masterNodes.summary.activeNodes} / ${loaderData.masterNodes.summary.committee - loaderData.masterNodes.summary.activeNodes}` }, - { name: 'Number of standby nodes', value: loaderData.masterNodes.summary.inActiveNodes }, + { name: 'Current committee size', value: loaderData.masterNodes?.summary?.committee }, + { name: 'Activity(active / inactive)', value: `${loaderData.masterNodes?.summary?.activeNodes} / ${loaderData.masterNodes.summary.committee - loaderData.masterNodes?.summary?.activeNodes}` }, + { name: 'Number of standby nodes', value: loaderData.masterNodes?.summary?.inActiveNodes }, ], }, }; - const masterNodes = loaderData.masterNodes.nodes.map((v: any, i: number) => ({ + const masterNodes = loaderData.masterNodes?.nodes?.map((v: any, i: number) => ({ ...v, type: 'master-node', account: formatHash(v.address), @@ -111,12 +111,12 @@ export default function InfoCards() { </div> <div className='grid grid-cols-1 llg:grid-cols-2 gap-6'> - <Card className='max-w-[565px]'> + {/* <Card className='max-w-[565px]'> <BlocksInfo title='Recent Blocks' data={recentBlocks} fetchMoreData={fetchMoreRecentBlocks} enableInfinite /> - </Card> - <Card className='max-w-[565px]'> + </Card> */} + {/* {masterNodes && <Card className='max-w-[565px]'> <BlocksInfo title='Master Nodes' data={masterNodes} /> - </Card> + </Card>} */} </div> </> ); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 3fe5606..96ecf98 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -39,27 +39,47 @@ export async function homeLoader() { return apiResponses.map(response => response.data); } - const data = await getData(); + try { + const data = await getData(); - // return { - // masterNodes: data[0], - // relayer: data[1], - // network: data[2], - // blocks: data[3], - // }; - return { - masterNodes: { - summary: { - committee: 3, - activeNodes: 3, - inActiveNodes: 0, + // return { + // masterNodes: data[0], + // relayer: data[1], + // network: data[2], + // blocks: data[3], + // }; + return { + masterNodes: { + summary: { + committee: 3, + activeNodes: 3, + inActiveNodes: 0, + }, + nodes: [] }, - nodes: [] - }, - relayer: data[0], - network: data[1], - blocks: data[2], - }; + relayer: data[0], + network: data[1], + blocks: { + ...data[2], latestParentChainCommittedBlock: { + "hash": "0x0ab7ea86c39aca0ac7b8a33c8d852eb44e6ec9861e3735fb2159752a925c49ef", + "number": data[2].latestSubnetCommittedBlock.number + } + }, + }; + } catch (error) { + // console.log(data) + console.log(error); + return { + masterNodes: { + summary: { + committee: 3, + activeNodes: 3, + inActiveNodes: 0, + }, + nodes: [] + } + }; + } } const router = createBrowserRouter([ diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 08b0231..7139ee6 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -66,9 +66,10 @@ export default function HomePage() { lastConfirmedBlock={lastConfirmedBlock} blockNumber={blockNumber} blocks={blocks} + name='subnet' /> </Card> - <Card> + {/* <Card> <h1 className='pb-4 text-xl font-medium'>Copy at the parent chain</h1> <Blocks initialLastBlock={initialLastBlock} @@ -76,8 +77,9 @@ export default function HomePage() { lastConfirmedBlock={lastParentConfirmedBlock} blockNumber={blockNumber} blocks={parentChainBlocks} + name='parent' /> - </Card> + </Card> */} <InfoCards /> </div> From 7ad5b02e5c944d0ed28ccb89096a06c65a29f220 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 21:17:11 +1000 Subject: [PATCH 37/87] For demo --- .../src/components/info-cards/InfoCards.tsx | 41 ++++++++++--------- frontend/src/pages/HomePage.tsx | 13 +++--- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index c203536..b3d5866 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -1,8 +1,8 @@ -import { useState } from 'react'; +// import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; -import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; -import BlocksInfo from '@/components/blocks-info/BlocksInfo'; +// import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; +// import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; import { Info, InfoListHealth } from '@/types/info'; @@ -12,7 +12,7 @@ import { formatHash } from '@/utils/formatter'; export default function InfoCards() { const loaderData = useLoaderData() as HomeLoaderData; - const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); + // const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); function getNetworkStatus(): InfoListHealth { if (loaderData.network.health.status === 'UP') { @@ -30,12 +30,12 @@ export default function InfoCards() { return 'Abnormal'; } - function getInitRecentBlocks(): BlocksInfoItem[] { - return loaderData.blocks.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ - type: 'recent-block', - ...block - })); - } + // function getInitRecentBlocks(): BlocksInfoItem[] { + // return loaderData.blocks.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ + // type: 'recent-block', + // ...block + // })); + // } const mappedInfo: Info = { network: { @@ -70,19 +70,20 @@ export default function InfoCards() { account: formatHash(v.address), number: i + 1 })); + masterNodes; - const fetchMoreRecentBlocks = () => { - if (!recentBlocks) { - return; - } + // const fetchMoreRecentBlocks = () => { + // if (!recentBlocks) { + // return; + // } - // TODO: From api - const data: any = []; + // // TODO: From api + // const data: any = []; - setRecentBlocks(recentBlocks => { - return [...recentBlocks, ...data]; - }); - }; + // setRecentBlocks(recentBlocks => { + // return [...recentBlocks, ...data]; + // }); + // }; return ( <> diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 7139ee6..866ef12 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -33,24 +33,25 @@ export default function HomePage() { const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); - const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); + // const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); - const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); + // const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); const [initialLastBlock] = useState<number>(loaderData.blocks.latestMinedBlock.number); const { currentUnixTime } = useContext(TimeContext); useEffect(() => { async function getData() { - const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); + // const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); + const { data: { latestMinedBlock, latestSubnetCommittedBlock, } } = await axios.get(`${baseUrl}/blocks`); setLastBlock(latestMinedBlock.number); setLastConfirmedBlock(latestSubnetCommittedBlock.number); - setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); + // setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); const newBlockNumber = latestMinedBlock.number - initialLastBlock + blockNumber; const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); - const parentChainBlocks = getBlocks(latestMinedBlock.number, latestParentChainCommittedBlock.number, newBlockNumber); + // const parentChainBlocks = getBlocks(latestMinedBlock.number, latestParentChainCommittedBlock.number, newBlockNumber); setBlocks(blocks); - setParentChainBlocks(parentChainBlocks); + // setParentChainBlocks(parentChainBlocks); } getData(); From 9cc79fc4322102b5897f85d7ccb263f28e93c504 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 21:21:01 +1000 Subject: [PATCH 38/87] For demo --- .../src/components/info-cards/InfoCards.tsx | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index b3d5866..0a6dd7e 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -1,8 +1,8 @@ -// import { useState } from 'react'; +import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; -// import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; -// import BlocksInfo from '@/components/blocks-info/BlocksInfo'; +import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; +import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; import { Info, InfoListHealth } from '@/types/info'; @@ -12,7 +12,7 @@ import { formatHash } from '@/utils/formatter'; export default function InfoCards() { const loaderData = useLoaderData() as HomeLoaderData; - // const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); + const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); function getNetworkStatus(): InfoListHealth { if (loaderData.network.health.status === 'UP') { @@ -30,12 +30,12 @@ export default function InfoCards() { return 'Abnormal'; } - // function getInitRecentBlocks(): BlocksInfoItem[] { - // return loaderData.blocks.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ - // type: 'recent-block', - // ...block - // })); - // } + function getInitRecentBlocks(): BlocksInfoItem[] { + return loaderData.blocks.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ + type: 'recent-block', + ...block + })); + } const mappedInfo: Info = { network: { @@ -72,18 +72,18 @@ export default function InfoCards() { })); masterNodes; - // const fetchMoreRecentBlocks = () => { - // if (!recentBlocks) { - // return; - // } + const fetchMoreRecentBlocks = () => { + if (!recentBlocks) { + return; + } - // // TODO: From api - // const data: any = []; + // TODO: From api + const data: any = []; - // setRecentBlocks(recentBlocks => { - // return [...recentBlocks, ...data]; - // }); - // }; + setRecentBlocks(recentBlocks => { + return [...recentBlocks, ...data]; + }); + }; return ( <> @@ -112,12 +112,12 @@ export default function InfoCards() { </div> <div className='grid grid-cols-1 llg:grid-cols-2 gap-6'> - {/* <Card className='max-w-[565px]'> + <Card className='max-w-[565px]'> <BlocksInfo title='Recent Blocks' data={recentBlocks} fetchMoreData={fetchMoreRecentBlocks} enableInfinite /> - </Card> */} - {/* {masterNodes && <Card className='max-w-[565px]'> + </Card> + {masterNodes && <Card className='max-w-[565px]'> <BlocksInfo title='Master Nodes' data={masterNodes} /> - </Card>} */} + </Card>} </div> </> ); From 9800f6e3cee00de8241e0d12f4e93d17ad2cfd92 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 21:23:22 +1000 Subject: [PATCH 39/87] Mock --- frontend/src/components/info-cards/InfoCards.tsx | 1 - frontend/src/main.tsx | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 0a6dd7e..a97d910 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -70,7 +70,6 @@ export default function InfoCards() { account: formatHash(v.address), number: i + 1 })); - masterNodes; const fetchMoreRecentBlocks = () => { if (!recentBlocks) { diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 96ecf98..c29b1a7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -55,7 +55,13 @@ export async function homeLoader() { activeNodes: 3, inActiveNodes: 0, }, - nodes: [] + nodes: [{ + address: 'sadjfklasdfj', + type: 'miner' + }, { + address: 'sadjfklasdfj', + type: 'miner' + }] }, relayer: data[0], network: data[1], @@ -76,7 +82,13 @@ export async function homeLoader() { activeNodes: 3, inActiveNodes: 0, }, - nodes: [] + nodes: [{ + address: 'sadjfklasdfj', + type: 'miner' + }, { + address: 'sadjfklasdfj', + type: 'miner' + }] } }; } From aa95b76f157773321f15c9bffbdb0451b4cd2a3a Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 22:18:27 +1000 Subject: [PATCH 40/87] Parent chain for demo --- frontend/src/main.tsx | 34 ++++++++++----------------------- frontend/src/pages/HomePage.tsx | 17 +++++++++-------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c29b1a7..7df4c0a 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -48,6 +48,14 @@ export async function homeLoader() { // network: data[2], // blocks: data[3], // }; + let blocks = data[2]; + blocks = ({ + ...blocks, latestParentChainCommittedBlock: { + hash: blocks.latestParentChainCommittedBlock.hash, + number: blocks.latestSubnetCommittedBlock.number - 1 + } + }); + return { masterNodes: { summary: { @@ -65,32 +73,10 @@ export async function homeLoader() { }, relayer: data[0], network: data[1], - blocks: { - ...data[2], latestParentChainCommittedBlock: { - "hash": "0x0ab7ea86c39aca0ac7b8a33c8d852eb44e6ec9861e3735fb2159752a925c49ef", - "number": data[2].latestSubnetCommittedBlock.number - } - }, + blocks: blocks }; } catch (error) { - // console.log(data) - console.log(error); - return { - masterNodes: { - summary: { - committee: 3, - activeNodes: 3, - inActiveNodes: 0, - }, - nodes: [{ - address: 'sadjfklasdfj', - type: 'miner' - }, { - address: 'sadjfklasdfj', - type: 'miner' - }] - } - }; + console.error(error); } } diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 866ef12..46e463c 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -33,25 +33,26 @@ export default function HomePage() { const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); - // const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); + const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); - // const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); + const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); const [initialLastBlock] = useState<number>(loaderData.blocks.latestMinedBlock.number); const { currentUnixTime } = useContext(TimeContext); useEffect(() => { async function getData() { // const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); - const { data: { latestMinedBlock, latestSubnetCommittedBlock, } } = await axios.get(`${baseUrl}/blocks`); + const { data: { latestMinedBlock, latestSubnetCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); setLastBlock(latestMinedBlock.number); setLastConfirmedBlock(latestSubnetCommittedBlock.number); - // setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); + setLastParentConfirmedBlock(latestSubnetCommittedBlock.number - 1); const newBlockNumber = latestMinedBlock.number - initialLastBlock + blockNumber; const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); - // const parentChainBlocks = getBlocks(latestMinedBlock.number, latestParentChainCommittedBlock.number, newBlockNumber); + // Mock + const parentChainBlocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number - 1, newBlockNumber); setBlocks(blocks); - // setParentChainBlocks(parentChainBlocks); + setParentChainBlocks(parentChainBlocks); } getData(); @@ -70,7 +71,7 @@ export default function HomePage() { name='subnet' /> </Card> - {/* <Card> + <Card> <h1 className='pb-4 text-xl font-medium'>Copy at the parent chain</h1> <Blocks initialLastBlock={initialLastBlock} @@ -80,7 +81,7 @@ export default function HomePage() { blocks={parentChainBlocks} name='parent' /> - </Card> */} + </Card> <InfoCards /> </div> From 2f474ae3b16712d7b340b7d78dbf01fae35cbcfe Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 22:48:36 +1000 Subject: [PATCH 41/87] Remove mocked master nodes --- frontend/src/main.tsx | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7df4c0a..daaf726 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -29,7 +29,7 @@ export async function appLoader() { export async function homeLoader() { async function getData() { const urls = [ - // `${baseUrl}/masternodes`, + `${baseUrl}/masternodes`, `${baseUrl}/relayer`, `${baseUrl}/network`, `${baseUrl}/blocks`, @@ -42,13 +42,7 @@ export async function homeLoader() { try { const data = await getData(); - // return { - // masterNodes: data[0], - // relayer: data[1], - // network: data[2], - // blocks: data[3], - // }; - let blocks = data[2]; + let blocks = data[3]; blocks = ({ ...blocks, latestParentChainCommittedBlock: { hash: blocks.latestParentChainCommittedBlock.hash, @@ -57,23 +51,10 @@ export async function homeLoader() { }); return { - masterNodes: { - summary: { - committee: 3, - activeNodes: 3, - inActiveNodes: 0, - }, - nodes: [{ - address: 'sadjfklasdfj', - type: 'miner' - }, { - address: 'sadjfklasdfj', - type: 'miner' - }] - }, - relayer: data[0], - network: data[1], - blocks: blocks + masterNodes: data[0], + relayer: data[1], + network: data[2], + blocks }; } catch (error) { console.error(error); From b4a1da7fdabaae40a5d037ae99f63817d5780b82 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 10 Jul 2023 22:58:25 +1000 Subject: [PATCH 42/87] Format large number --- .../src/components/info-cards/InfoCards.tsx | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index a97d910..eed1287 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -37,6 +37,37 @@ export default function InfoCards() { })); } + function formatBalance(balance: number) { + const abbreTable = [{ + name: 'Septillion', pow: 24, + }, { + name: 'Sextillion', pow: 21, + }, { + name: 'Quintillion', pow: 18, + }, { + name: 'Quadrillion', pow: 15, + }, { + name: 'Trillion', pow: 12, + }, { + name: 'B', pow: 9, + }, { + name: 'M', pow: 6, + }, { + name: 'K', pow: 3, + }]; + + for (let i = 0; i < abbreTable.length; i++) { + const { name, pow } = abbreTable[i]; + const base = Math.pow(10, pow); + + if (balance / base > 0) { + return `${Math.floor(((balance / base) * 100)) / 100} ${name}`; + } + } + + return balance; + } + const mappedInfo: Info = { network: { health: getNetworkStatus(), @@ -51,8 +82,8 @@ export default function InfoCards() { data: [ { name: 'Smart Contract', value: formatHash(loaderData.relayer.account.walletAddress) }, { name: 'Backlog', value: `${loaderData.relayer.backlog} Subnet Headers` }, - { name: 'Ave. tx fee', value: loaderData.relayer.averageTXfee }, - { name: 'Remaining Balance', value: loaderData.relayer.account.balance }, + // TODO: explore cash format + { name: 'Remaining Balance', value: formatBalance(parseInt(loaderData.relayer.account.balance)) }, ] }, masterNodes: { From 3a84b561dc69e52e2c75aed57cfc4ca43afc9b8d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 12:29:36 +1000 Subject: [PATCH 43/87] Update block cards title --- frontend/src/pages/HomePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 46e463c..d01a5e4 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -61,7 +61,7 @@ export default function HomePage() { return ( <div className='grid gap-6 grid-col-1'> <Card> - <h1 className='pb-4 text-xl font-medium'>Subnet Blockchain</h1> + <h1 className='pb-4 text-xl font-medium'>subnet Blockchain</h1> <Blocks initialLastBlock={initialLastBlock} lastBlock={lastBlock} @@ -72,7 +72,7 @@ export default function HomePage() { /> </Card> <Card> - <h1 className='pb-4 text-xl font-medium'>Copy at the parent chain</h1> + <h1 className='pb-4 text-xl font-medium'>checkpoints at the parent chain</h1> <Blocks initialLastBlock={initialLastBlock} lastBlock={lastBlock} From dd963d25b27aefcd21cd8f63b030a5624e244b20 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 13:02:54 +1000 Subject: [PATCH 44/87] Add copy button for block hash --- .../blocks-info-item/BlocksInfoItem.tsx | 21 +++++++++++++-- frontend/src/components/images/Svg.tsx | 26 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index 08266de..7ccc037 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -1,7 +1,10 @@ import { PropsWithChildren, useContext } from 'react'; import { cellWith } from '@/components/blocks-info/constants'; -import Svg, { SvgNames } from '@/components/images/Svg'; +import Svg, { + InlineSvg, InlineSvgColours, InlineSvgNames, SvgNames +} from '@/components/images/Svg'; +import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; import { formatHash } from '@/utils/formatter'; @@ -31,6 +34,7 @@ type MasterNodeRoles = 'master-node' | 'candidate' | 'penalty'; export function BlocksInfoItem(data: BlocksInfoItemProps) { const { currentUnixTime } = useContext(TimeContext); + const { theme } = useContext(ThemeContext); function getTimeDiff(timestamp: number): string { const timeDiff = Math.floor(currentUnixTime - timestamp); @@ -43,12 +47,25 @@ export function BlocksInfoItem(data: BlocksInfoItemProps) { return '>1hr'; } + function copyToClipboard(hash: string) { + window.navigator.clipboard.writeText(hash); + } + if (data.type === 'recent-block') { return ( <div className='flex'> <BlockCell className={cellWith.recentBlocks.height}>{data.number}</BlockCell> - <BlockCell className={cellWith.recentBlocks.hash}>{formatHash(data.hash)}</BlockCell> + <BlockCell className={cellWith.recentBlocks.hash}> + <div className=''> + <button onClick={() => copyToClipboard(data.hash)} className='shrink-0 flex justify-between group'> + {formatHash(data.hash)} + <div className='hidden group-hover:block'> + <InlineSvg svgName={InlineSvgNames.Copy} colour={theme === 'light' ? InlineSvgColours.Primary : InlineSvgColours.White} /> + </div> + </button> + </div> + </BlockCell> <BlockCell className={cellWith.recentBlocks.proposedBy}>{formatHash(data.miner)}</BlockCell> <BlockImageCell className={cellWith.recentBlocks.status}> <BlockConfirmStatus committedInSubnet={data.committedInSubnet} committedInParentChain={data.committedInParentChain} /> diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index 2ea94ab..1d6d8dd 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -46,6 +46,18 @@ function Sun({ colour }: GeneralSvgProps) { ); } +function Copy({ colour }: GeneralSvgProps) { + return ( + <svg width="16px" height="16px" className={colour} viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g id="SVGRepo_bgCarrier" strokeWidth="0"></g> + <g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g> + <g id="SVGRepo_iconCarrier"> + <path d="M8.25005 8.5C8.25005 8.91421 8.58584 9.25 9.00005 9.25C9.41426 9.25 9.75005 8.91421 9.75005 8.5H8.25005ZM9.00005 8.267H9.75006L9.75004 8.26283L9.00005 8.267ZM9.93892 5.96432L10.4722 6.49171L9.93892 5.96432ZM12.2311 5V4.24999L12.2269 4.25001L12.2311 5ZM16.269 5L16.2732 4.25H16.269V5ZM18.5612 5.96432L18.0279 6.49171V6.49171L18.5612 5.96432ZM19.5 8.267L18.75 8.26283V8.267H19.5ZM19.5 12.233H18.75L18.7501 12.2372L19.5 12.233ZM18.5612 14.5357L18.0279 14.0083L18.5612 14.5357ZM16.269 15.5V16.25L16.2732 16.25L16.269 15.5ZM16 14.75C15.5858 14.75 15.25 15.0858 15.25 15.5C15.25 15.9142 15.5858 16.25 16 16.25V14.75ZM9.00005 9.25C9.41426 9.25 9.75005 8.91421 9.75005 8.5C9.75005 8.08579 9.41426 7.75 9.00005 7.75V9.25ZM8.73105 8.5V7.74999L8.72691 7.75001L8.73105 8.5ZM6.43892 9.46432L6.97218 9.99171L6.43892 9.46432ZM5.50005 11.767H6.25006L6.25004 11.7628L5.50005 11.767ZM5.50005 15.734L6.25005 15.7379V15.734H5.50005ZM8.73105 19L8.72691 19.75H8.73105V19ZM12.769 19V19.75L12.7732 19.75L12.769 19ZM15.0612 18.0357L14.5279 17.5083L15.0612 18.0357ZM16 15.733H15.25L15.2501 15.7372L16 15.733ZM16.75 15.5C16.75 15.0858 16.4143 14.75 16 14.75C15.5858 14.75 15.25 15.0858 15.25 15.5H16.75ZM9.00005 7.75C8.58584 7.75 8.25005 8.08579 8.25005 8.5C8.25005 8.91421 8.58584 9.25 9.00005 9.25V7.75ZM12.7691 8.5L12.7732 7.75H12.7691V8.5ZM15.0612 9.46432L15.5944 8.93694V8.93694L15.0612 9.46432ZM16.0001 11.767L15.2501 11.7628V11.767H16.0001ZM15.2501 15.5C15.2501 15.9142 15.5858 16.25 16.0001 16.25C16.4143 16.25 16.7501 15.9142 16.7501 15.5H15.2501ZM9.75005 8.5V8.267H8.25005V8.5H9.75005ZM9.75004 8.26283C9.74636 7.60005 10.0061 6.96296 10.4722 6.49171L9.40566 5.43694C8.65985 6.19106 8.24417 7.21056 8.25006 8.27117L9.75004 8.26283ZM10.4722 6.49171C10.9382 6.02046 11.5724 5.75365 12.2352 5.74999L12.2269 4.25001C11.1663 4.25587 10.1515 4.68282 9.40566 5.43694L10.4722 6.49171ZM12.2311 5.75H16.269V4.25H12.2311V5.75ZM16.2649 5.74999C16.9277 5.75365 17.5619 6.02046 18.0279 6.49171L19.0944 5.43694C18.3486 4.68282 17.3338 4.25587 16.2732 4.25001L16.2649 5.74999ZM18.0279 6.49171C18.494 6.96296 18.7537 7.60005 18.7501 8.26283L20.25 8.27117C20.2559 7.21056 19.8402 6.19106 19.0944 5.43694L18.0279 6.49171ZM18.75 8.267V12.233H20.25V8.267H18.75ZM18.7501 12.2372C18.7537 12.8999 18.494 13.537 18.0279 14.0083L19.0944 15.0631C19.8402 14.3089 20.2559 13.2894 20.25 12.2288L18.7501 12.2372ZM18.0279 14.0083C17.5619 14.4795 16.9277 14.7463 16.2649 14.75L16.2732 16.25C17.3338 16.2441 18.3486 15.8172 19.0944 15.0631L18.0279 14.0083ZM16.269 14.75H16V16.25H16.269V14.75ZM9.00005 7.75H8.73105V9.25H9.00005V7.75ZM8.72691 7.75001C7.6663 7.75587 6.65146 8.18282 5.90566 8.93694L6.97218 9.99171C7.43824 9.52046 8.07241 9.25365 8.73519 9.24999L8.72691 7.75001ZM5.90566 8.93694C5.15985 9.69106 4.74417 10.7106 4.75006 11.7712L6.25004 11.7628C6.24636 11.1001 6.50612 10.463 6.97218 9.99171L5.90566 8.93694ZM4.75005 11.767V15.734H6.25005V11.767H4.75005ZM4.75006 15.7301C4.73847 17.9382 6.51879 19.7378 8.72691 19.75L8.7352 18.25C7.35533 18.2424 6.2428 17.1178 6.25004 15.7379L4.75006 15.7301ZM8.73105 19.75H12.769V18.25H8.73105V19.75ZM12.7732 19.75C13.8338 19.7441 14.8486 19.3172 15.5944 18.5631L14.5279 17.5083C14.0619 17.9795 13.4277 18.2463 12.7649 18.25L12.7732 19.75ZM15.5944 18.5631C16.3402 17.8089 16.7559 16.7894 16.75 15.7288L15.2501 15.7372C15.2537 16.3999 14.994 17.037 14.5279 17.5083L15.5944 18.5631ZM16.75 15.733V15.5H15.25V15.733H16.75ZM9.00005 9.25H12.7691V7.75H9.00005V9.25ZM12.7649 9.24999C13.4277 9.25365 14.0619 9.52046 14.5279 9.99171L15.5944 8.93694C14.8486 8.18282 13.8338 7.75587 12.7732 7.75001L12.7649 9.24999ZM14.5279 9.99171C14.994 10.463 15.2537 11.1001 15.2501 11.7628L16.75 11.7712C16.7559 10.7106 16.3402 9.69106 15.5944 8.93694L14.5279 9.99171ZM15.2501 11.767V15.5H16.7501V11.767H15.2501Z"></path> + </g> + </svg> + ); +} + interface InlineSvgProps { svgName: InlineSvgNames; colour?: InlineSvgColours; @@ -64,6 +76,10 @@ export function InlineSvg({ svgName, colour, className }: InlineSvgProps) { SvgComponent = Sun; break; + case InlineSvgNames.Copy: + SvgComponent = Copy; + break; + default: SvgComponent = Fallback; break; @@ -81,6 +97,10 @@ export function InlineSvg({ svgName, colour, className }: InlineSvgProps) { fillColour = 'fill-text-dark-600'; break; } + case (InlineSvgColours.White): { + fillColour = 'fill-text-white'; + break; + } default: { console.debug('warning: define fill colour'); break; @@ -191,13 +211,15 @@ export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass, cl } export enum InlineSvgColours { - Primary = 'primary', - Grey = 'grey' + Primary = 'Primary', + Grey = 'Grey', + White = 'White', } export enum InlineSvgNames { Moon = 'Moon', Sun = 'Sun', + Copy = 'Copy', } export enum SvgNames { From d603bac7be0bd6c3f4e997fc00749724a3a8cfb3 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 13:04:23 +1000 Subject: [PATCH 45/87] Make title lowercase --- frontend/src/pages/HomePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index d01a5e4..0aa9569 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -61,7 +61,7 @@ export default function HomePage() { return ( <div className='grid gap-6 grid-col-1'> <Card> - <h1 className='pb-4 text-xl font-medium'>subnet Blockchain</h1> + <h1 className='pb-4 text-xl font-medium'>subnet blockchain</h1> <Blocks initialLastBlock={initialLastBlock} lastBlock={lastBlock} From 03f4580cbc3da77abd2f909e5c29910454067862 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 15:51:49 +1000 Subject: [PATCH 46/87] Add alert --- frontend/src/App.tsx | 31 ++++++----- frontend/src/components/alert/Alert.tsx | 15 ++++++ .../blocks-info-item/BlocksInfoItem.tsx | 3 ++ frontend/src/contexts/AlertContext.tsx | 52 +++++++++++++++++++ 4 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 frontend/src/components/alert/Alert.tsx create mode 100644 frontend/src/contexts/AlertContext.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 55a2acc..c248388 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,11 @@ -import { useEffect, useState } from 'react'; -import { Outlet, useNavigation } from 'react-router-dom'; +import { useContext, useEffect, useState } from 'react'; +import { Outlet, useLocation, useNavigation } from 'react-router-dom'; +import Alert from '@/components/alert/Alert'; import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; +import AlertProvider, { AlertContext } from '@/contexts/AlertContext'; import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; import { getUnixTime, pollingPeriod } from '@/utils/time'; @@ -38,16 +40,21 @@ function App() { return ( <TimeContext.Provider value={{ currentUnixTime }}> <ThemeContext.Provider value={{ theme, setTheme }}> - <div className='relative max-w-[1440px] m-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900'> - <Nav /> - <main className='mx-6 my-8 grow w-[1146px]'> - {navigation.state === 'loading' ? ( - <Loader /> - ) : ( - <Outlet /> - )} - </main> - </div> + <AlertProvider> + <div className='relative max-w-[1440px] m-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900'> + <Nav /> + <main className='mx-6 my-8 grow w-[1146px] relative'> + {navigation.state === 'loading' ? ( + <Loader /> + ) : ( + <> + <Outlet /> + <Alert /> + </> + )} + </main> + </div> + </AlertProvider> </ThemeContext.Provider> </TimeContext.Provider> ); diff --git a/frontend/src/components/alert/Alert.tsx b/frontend/src/components/alert/Alert.tsx new file mode 100644 index 0000000..41e47cc --- /dev/null +++ b/frontend/src/components/alert/Alert.tsx @@ -0,0 +1,15 @@ +import { useContext } from 'react'; + +import { AlertContext } from '@/contexts/AlertContext'; + +export default function Alert() { + const { show, alertText } = useContext(AlertContext); + + return show ? + ( + <div className='px-5 p-3 fixed right-0 left-0 w-[100px] m-auto top-5 dark:bg-primary bg-slate-200 rounded-xl'> + {alertText} + </div> + ) + : null; +} \ No newline at end of file diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index 7ccc037..3d983a6 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -4,6 +4,7 @@ import { cellWith } from '@/components/blocks-info/constants'; import Svg, { InlineSvg, InlineSvgColours, InlineSvgNames, SvgNames } from '@/components/images/Svg'; +import { AlertContext } from '@/contexts/AlertContext'; import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; import { formatHash } from '@/utils/formatter'; @@ -35,6 +36,7 @@ type MasterNodeRoles = 'master-node' | 'candidate' | 'penalty'; export function BlocksInfoItem(data: BlocksInfoItemProps) { const { currentUnixTime } = useContext(TimeContext); const { theme } = useContext(ThemeContext); + const { showAlert } = useContext(AlertContext); function getTimeDiff(timestamp: number): string { const timeDiff = Math.floor(currentUnixTime - timestamp); @@ -49,6 +51,7 @@ export function BlocksInfoItem(data: BlocksInfoItemProps) { function copyToClipboard(hash: string) { window.navigator.clipboard.writeText(hash); + // showAlert('Copied!'); } if (data.type === 'recent-block') { diff --git a/frontend/src/contexts/AlertContext.tsx b/frontend/src/contexts/AlertContext.tsx new file mode 100644 index 0000000..dd2c2f8 --- /dev/null +++ b/frontend/src/contexts/AlertContext.tsx @@ -0,0 +1,52 @@ +import { createContext, PropsWithChildren, useEffect, useState } from 'react'; + +interface AlertContextType { + show: boolean; + alertText: string; + showAlert: (text: string) => void; + hideAlert: () => void; +} + +export const AlertContext = createContext<AlertContextType>({ + show: false, + alertText: '', + showAlert: () => { + // empty function + }, + hideAlert: () => { + // empty function + } +}); + +type AlertProviderProps = PropsWithChildren; + +export default function AlertProvider({ children }: AlertProviderProps) { + const [show, setShow] = useState(false); + const [alertText, setAlertText] = useState(''); + + function showAlert(text: string) { + setAlertText(text); + setShow(true); + } + + function test() { + setAlertText(''); + setShow(false); + } + + useEffect(() => { + if (show) { + const timeout = setTimeout(() => { + setShow(false); + }, 2 * 1000); + + return () => clearTimeout(timeout); + } + }, [show]); + + return ( + <AlertContext.Provider value={{ show, alertText, showAlert, hideAlert: test }}> + {children} + </AlertContext.Provider> + ); +} From 9d5ae7d480ae1bd48c9d69aa47a77760e4021120 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 15:56:00 +1000 Subject: [PATCH 47/87] Tweak font size --- frontend/src/components/info-list/InfoList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 97262f8..c26f7bf 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -60,7 +60,7 @@ function InfoItem({ name, value, isFirst }: InfoItemProps) { <Svg svgName={SvgNames.Rhombus} size='sm' /> <div className='pl-1.5 dark:text-text-dark-100'>{name}</div> </div> - <div className='font-bold text-lg leading-[120%]'>{value}</div> + <div className='font-bold text-sm leading-[120%]'>{value}</div> </div> ); } From 3bee70e87c670b2e87b1202ea7ce919182281546 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 16:05:56 +1000 Subject: [PATCH 48/87] Fix build error --- frontend/src/App.tsx | 6 +++--- .../blocks-info/blocks-info-item/BlocksInfoItem.tsx | 2 -- frontend/src/components/info-list/InfoList.tsx | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c248388..e051575 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,11 +1,11 @@ -import { useContext, useEffect, useState } from 'react'; -import { Outlet, useLocation, useNavigation } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { Outlet, useNavigation } from 'react-router-dom'; import Alert from '@/components/alert/Alert'; import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; -import AlertProvider, { AlertContext } from '@/contexts/AlertContext'; +import AlertProvider from '@/contexts/AlertContext'; import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; import { getUnixTime, pollingPeriod } from '@/utils/time'; diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index 3d983a6..d3556d8 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -4,7 +4,6 @@ import { cellWith } from '@/components/blocks-info/constants'; import Svg, { InlineSvg, InlineSvgColours, InlineSvgNames, SvgNames } from '@/components/images/Svg'; -import { AlertContext } from '@/contexts/AlertContext'; import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; import { formatHash } from '@/utils/formatter'; @@ -36,7 +35,6 @@ type MasterNodeRoles = 'master-node' | 'candidate' | 'penalty'; export function BlocksInfoItem(data: BlocksInfoItemProps) { const { currentUnixTime } = useContext(TimeContext); const { theme } = useContext(ThemeContext); - const { showAlert } = useContext(AlertContext); function getTimeDiff(timestamp: number): string { const timeDiff = Math.floor(currentUnixTime - timestamp); diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index c26f7bf..5a356f6 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -26,7 +26,7 @@ export default function InfoList({ title, status, info }: InfoListProps) { <Title title={title} /> {status && ( <div className='inline-flex items-center'> - <span>Status</span> + {/* <span>Status:</span> */} <span className={`ml-1 px-3 py-2.5 bg-opacity-20 rounded-3xl font-bold leading-none ${status === 'Normal' ? 'bg-sky-500 text-sky-500' : 'bg-warning text-warning'}` From 286ab03c1135faf8377b203c6af86149ca4b1245 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 17:03:10 +1000 Subject: [PATCH 49/87] Add error handling --- .../src/components/info-cards/InfoCards.tsx | 49 +++-- frontend/src/main.tsx | 46 ++-- frontend/src/types/info.d.ts | 2 +- frontend/src/types/loaderData.d.ts | 206 +++++++++--------- 4 files changed, 158 insertions(+), 145 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index eed1287..833e140 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -69,15 +69,15 @@ export default function InfoCards() { } const mappedInfo: Info = { - network: { + network: loaderData.network ? { health: getNetworkStatus(), data: [ { name: 'Block Time', value: `${loaderData.network.subnet.block.averageBlockTime}s` }, { name: 'TX Throughput', value: `${Math.round(loaderData.network.subnet.block.txThroughput * 100) / 100} txs/s` }, { name: 'Checkpointed to', value: loaderData.network.parentChain.name }, ] - }, - relayer: { + } : null, + relayer: loaderData.relayer ? { health: getRelayerStatus(), data: [ { name: 'Smart Contract', value: formatHash(loaderData.relayer.account.walletAddress) }, @@ -85,14 +85,14 @@ export default function InfoCards() { // TODO: explore cash format { name: 'Remaining Balance', value: formatBalance(parseInt(loaderData.relayer.account.balance)) }, ] - }, - masterNodes: { + } : null, + masterNodes: loaderData.masterNodes ? { data: [ { name: 'Current committee size', value: loaderData.masterNodes?.summary?.committee }, { name: 'Activity(active / inactive)', value: `${loaderData.masterNodes?.summary?.activeNodes} / ${loaderData.masterNodes.summary.committee - loaderData.masterNodes?.summary?.activeNodes}` }, { name: 'Number of standby nodes', value: loaderData.masterNodes?.summary?.inActiveNodes }, ], - }, + } : null, }; const masterNodes = loaderData.masterNodes?.nodes?.map((v: any, i: number) => ({ @@ -119,25 +119,32 @@ export default function InfoCards() { <> <div className='grid grid-cols-2 llg:grid-cols-3 gap-6'> <Card> - <InfoList - title='Network Info' - status={mappedInfo.network.health} - info={mappedInfo.network.data} - /> + {mappedInfo.network ? ( + <InfoList + title='Network Info' + status={mappedInfo.network.health} + info={mappedInfo.network.data} + />) : ( + <div>Error state</div> + )} </Card> <Card> - <InfoList - title='Relayer Info' - status={mappedInfo.relayer.health} - info={mappedInfo.relayer.data} - /> + {mappedInfo.relayer ? ( + <InfoList + title='Relayer Info' + status={mappedInfo.relayer.health} + info={mappedInfo.relayer.data} + /> + ) : (<>Error state</>)} </Card> <Card> - <InfoList - title='Master Nodes Info' - status={mappedInfo.masterNodes.health} - info={mappedInfo.masterNodes.data} - /> + {mappedInfo.masterNodes ? ( + <InfoList + title='Master Nodes Info' + status={mappedInfo.masterNodes.health} + info={mappedInfo.masterNodes.data} + /> + ) : (<>Error state</>)} </Card> </div> diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index daaf726..a4203cd 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -27,7 +27,7 @@ export async function appLoader() { } export async function homeLoader() { - async function getData() { + async function getDataPromises() { const urls = [ `${baseUrl}/masternodes`, `${baseUrl}/relayer`, @@ -35,30 +35,34 @@ export async function homeLoader() { `${baseUrl}/blocks`, ]; - const apiResponses = await axios.all(urls.map(url => axios.get(url))); - return apiResponses.map(response => response.data); + return urls.map(url => axios.get(url)); } - try { - const data = await getData(); + const dataPromises = await getDataPromises(); + const apiResponses = await Promise.allSettled(dataPromises); + const data = apiResponses.map(response => { + if (response.status === 'rejected') { + console.error(response.reason); + return null; + } - let blocks = data[3]; - blocks = ({ - ...blocks, latestParentChainCommittedBlock: { - hash: blocks.latestParentChainCommittedBlock.hash, - number: blocks.latestSubnetCommittedBlock.number - 1 - } - }); + return response.value.data; + }); - return { - masterNodes: data[0], - relayer: data[1], - network: data[2], - blocks - }; - } catch (error) { - console.error(error); - } + let blocks = data[3]; + blocks = ({ + ...blocks, latestParentChainCommittedBlock: { + hash: blocks.latestParentChainCommittedBlock.hash, + number: blocks.latestSubnetCommittedBlock.number - 1 + } + }); + + return { + masterNodes: data[0], + relayer: data[1], + network: data[2], + blocks + }; } const router = createBrowserRouter([ diff --git a/frontend/src/types/info.d.ts b/frontend/src/types/info.d.ts index ebbb6cd..75154df 100644 --- a/frontend/src/types/info.d.ts +++ b/frontend/src/types/info.d.ts @@ -2,7 +2,7 @@ export interface Info { [x: string]: { data?: Info.Data[]; health?: InfoListHealth; - }; + } | null; } namespace Info { diff --git a/frontend/src/types/loaderData.d.ts b/frontend/src/types/loaderData.d.ts index 776af04..b6c7957 100644 --- a/frontend/src/types/loaderData.d.ts +++ b/frontend/src/types/loaderData.d.ts @@ -3,112 +3,114 @@ export interface AppLoaderData { } export interface HomeLoaderData { - masterNodes: MasterNodes; - blocks: Blocks; - network: Network; - relayer: Relayer; + masterNodes: HomeLoaderData.MasterNodes; + blocks: HomeLoaderData.Blocks; + network: HomeLoaderData.Network; + relayer: HomeLoaderData.Relayer; } -interface MasterNodes { - summary: { - committee: number; - activeNodes: number; - /** Number of penalised nodes */ - inActiveNodes: number; - }; - /** A list of existing master nodes. Sorted by nodeId */ - nodes: MasterNodes.Node[]; -} - -namespace MasterNodes { - interface Node { - address: string; - type: 'CANDIDATE', 'MASTERNODE', 'PENALTY'; +export namespace HomeLoaderData { + interface MasterNodes { + summary: { + committee: number; + activeNodes: number; + /** Number of penalised nodes */ + inActiveNodes: number; + }; + /** A list of existing master nodes. Sorted by nodeId */ + nodes: MasterNodes.Node[]; } -} - -interface Blocks { - /** A list of recently mined blocks. Sorted by block number */ - blocks: Blocks.Block[]; - /** The block that was most recently mined in the subnet. regardless its confirmation status */ - latestMinedBlock: { - hash: string; - number: number; - }; - /** The block that was most recently confirm to be committed in the subnet. (Ignoring its confirmation status from parent chain) */ - latestSubnetCommittedBlock: { - hash: string; - number: number; - }; - /** The block that was most recently confirm to be committed in the parent chain. */ - latestParentChainCommittedBlock: { - hash: string; - number: number; - }; - /** A simple enum to indicate whether the subnet chain is operational. i.e if blocks are mined */ - chainHealth: 'UP' | 'DOWN'; -} - -namespace Blocks { - interface Block { - /** The subnet block hash */ - hash: string; - /** The subnet block number */ - number: number; - /** The subnet block's parentHash */ - parentHash: string; - /** The masternode address who mined this block */ - miner: string; - /** This boolean value is to indicate whether this block has been confirmed in the subnet itself */ - committedInSubnet: boolean; - /** This boolean value is to indicate whether this block has been confirmed in the parent chain smart contract */ - committedInParentChain: boolean; - - timestamp: number; + + namespace MasterNodes { + interface Node { + address: string; + type: 'CANDIDATE', 'MASTERNODE', 'PENALTY'; + } } -} -interface Relayer { - /** The admin/super account information */ - account: { - /** The super/admin account remaining balance in XDC */ - balance: string; - /** The wallet address of the account */ - walletAddress: string; - }; - /** The current gap between audited block in smartcontract (parent chain) and latest minded block in subnet */ - backlog: number; - contractAddress: string; - health: { - /** An enum value to indicate the current relayer status. */ - status: 'UP' | 'DOWN'; - /** A short description about the current running status when there is an issue. E.g System is running but very low */ - details: string; - }; - averageTXfee: number; -} + interface Blocks { + /** A list of recently mined blocks. Sorted by block number */ + blocks: Blocks.Block[]; + /** The block that was most recently mined in the subnet. regardless its confirmation status */ + latestMinedBlock: { + hash: string; + number: number; + }; + /** The block that was most recently confirm to be committed in the subnet. (Ignoring its confirmation status from parent chain) */ + latestSubnetCommittedBlock: { + hash: string; + number: number; + }; + /** The block that was most recently confirm to be committed in the parent chain. */ + latestParentChainCommittedBlock: { + hash: string; + number: number; + }; + /** A simple enum to indicate whether the subnet chain is operational. i.e if blocks are mined */ + chainHealth: 'UP' | 'DOWN'; + } + + namespace Blocks { + interface Block { + /** The subnet block hash */ + hash: string; + /** The subnet block number */ + number: number; + /** The subnet block's parentHash */ + parentHash: string; + /** The masternode address who mined this block */ + miner: string; + /** This boolean value is to indicate whether this block has been confirmed in the subnet itself */ + committedInSubnet: boolean; + /** This boolean value is to indicate whether this block has been confirmed in the parent chain smart contract */ + committedInParentChain: boolean; + + timestamp: number; + } + } -interface Network { - subnet: { - name: string; - /** block metadata, such as mining frequency */ - block: { - /** The block mining time per X second. */ - averageBlockTime: number; - /** The subnet transaction throughput, we only keep track of the last 10 txs and take average per second. */ - txThroughput: number; + interface Relayer { + /** The admin/super account information */ + account: { + /** The super/admin account remaining balance in XDC */ + balance: string; + /** The wallet address of the account */ + walletAddress: string; }; - }; - parentChain: { - /** A string value which is used to identify the target parent chain. It can be a URL, IP address or a name. */ - url: string; - /** Parent Chain name */ - name: string; - }; - health: { - /** An enum value to indicate the current relayer status. */ - status: 'UP' | 'DOWN'; - /** A short description about the current running status when there is an issue. E.g System is running but very low */ - details: string; - }; -} + /** The current gap between audited block in smartcontract (parent chain) and latest minded block in subnet */ + backlog: number; + contractAddress: string; + health: { + /** An enum value to indicate the current relayer status. */ + status: 'UP' | 'DOWN'; + /** A short description about the current running status when there is an issue. E.g System is running but very low */ + details: string; + }; + averageTXfee: number; + } + + interface Network { + subnet: { + name: string; + /** block metadata, such as mining frequency */ + block: { + /** The block mining time per X second. */ + averageBlockTime: number; + /** The subnet transaction throughput, we only keep track of the last 10 txs and take average per second. */ + txThroughput: number; + }; + }; + parentChain: { + /** A string value which is used to identify the target parent chain. It can be a URL, IP address or a name. */ + url: string; + /** Parent Chain name */ + name: string; + }; + health: { + /** An enum value to indicate the current relayer status. */ + status: 'UP' | 'DOWN'; + /** A short description about the current running status when there is an issue. E.g System is running but very low */ + details: string; + }; + } +} \ No newline at end of file From 9adfb959265c4f4c09bc0f2e3536547b4b35c1b7 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 17:06:04 +1000 Subject: [PATCH 50/87] Add status prefix --- frontend/src/components/info-list/InfoList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 5a356f6..770d2e1 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -26,7 +26,7 @@ export default function InfoList({ title, status, info }: InfoListProps) { <Title title={title} /> {status && ( <div className='inline-flex items-center'> - {/* <span>Status:</span> */} + <span>Status:</span> <span className={`ml-1 px-3 py-2.5 bg-opacity-20 rounded-3xl font-bold leading-none ${status === 'Normal' ? 'bg-sky-500 text-sky-500' : 'bg-warning text-warning'}` From aca4b87cd90f0790d92adb542f6f35313e55056e Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 20:42:03 +1000 Subject: [PATCH 51/87] Use context provider pattern --- frontend/src/App.tsx | 39 ++++--------------------- frontend/src/contexts/ThemeContext.tsx | 15 ++++++++-- frontend/src/contexts/TimeContext.tsx | 40 ++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e051575..8902675 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,45 +1,18 @@ -import { useEffect, useState } from 'react'; import { Outlet, useNavigation } from 'react-router-dom'; import Alert from '@/components/alert/Alert'; import Loader from '@/components/loader/Loader'; import Nav from '@/components/nav/Nav'; -import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; import AlertProvider from '@/contexts/AlertContext'; -import { ThemeContext } from '@/contexts/ThemeContext'; -import { TimeContext } from '@/contexts/TimeContext'; -import { getUnixTime, pollingPeriod } from '@/utils/time'; +import ThemeContextProvider from '@/contexts/ThemeContext'; +import TimeContextProvider from '@/contexts/TimeContext'; function App() { - const [theme, setTheme] = useState<ThemeModes>('light'); - const [currentUnixTime, setCurrentUnixTime] = useState(getUnixTime()); const navigation = useNavigation(); - useEffect(() => { - /** - * The following code runs timer only when tab is active - */ - // let intervalId: number; - - // window.addEventListener('focus', () => { - // intervalId = setInterval(() => { - // setCurrentUnixTime(getUnixTime()); - // }, pollingPeriod); - // }); - - // window.addEventListener('blur', () => { - // clearInterval(intervalId); - // }); - const intervalId = setInterval(() => { - setCurrentUnixTime(getUnixTime()); - }, pollingPeriod); - - return () => clearInterval(intervalId); - }, []); - return ( - <TimeContext.Provider value={{ currentUnixTime }}> - <ThemeContext.Provider value={{ theme, setTheme }}> + <TimeContextProvider> + <ThemeContextProvider> <AlertProvider> <div className='relative max-w-[1440px] m-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900'> <Nav /> @@ -55,8 +28,8 @@ function App() { </main> </div> </AlertProvider> - </ThemeContext.Provider> - </TimeContext.Provider> + </ThemeContextProvider> + </TimeContextProvider> ); } diff --git a/frontend/src/contexts/ThemeContext.tsx b/frontend/src/contexts/ThemeContext.tsx index 86868fc..70ed44a 100644 --- a/frontend/src/contexts/ThemeContext.tsx +++ b/frontend/src/contexts/ThemeContext.tsx @@ -1,4 +1,4 @@ -import { createContext } from 'react'; +import { createContext, PropsWithChildren, useState } from 'react'; import { ThemeModes } from '@/components/theme-switch/ThemeSwitch'; @@ -14,4 +14,15 @@ const initialThemeContext: ThemeContextType = { }, }; -export const ThemeContext = createContext<ThemeContextType>(initialThemeContext); \ No newline at end of file +export const ThemeContext = createContext<ThemeContextType>(initialThemeContext); + +type ThemeContextProviderProps = PropsWithChildren; +export default function ThemeContextProvider({children}: ThemeContextProviderProps) { + const [theme, setTheme] = useState<ThemeModes>('light'); + + return ( + <ThemeContext.Provider value={{ theme, setTheme }}> + {children} + </ThemeContext.Provider> + ) +} \ No newline at end of file diff --git a/frontend/src/contexts/TimeContext.tsx b/frontend/src/contexts/TimeContext.tsx index 8088f58..d1200a9 100644 --- a/frontend/src/contexts/TimeContext.tsx +++ b/frontend/src/contexts/TimeContext.tsx @@ -1,4 +1,6 @@ -import { createContext } from 'react'; +import { createContext, PropsWithChildren, useEffect, useState } from 'react'; + +import { getUnixTime, pollingPeriod } from '@/utils/time'; interface TimeContextType { currentUnixTime: number; @@ -8,4 +10,38 @@ const initialContext: TimeContextType = { currentUnixTime: 0 }; -export const TimeContext = createContext<TimeContextType>(initialContext); \ No newline at end of file +export const TimeContext = createContext<TimeContextType>(initialContext); + +type TimeContextProviderProps = PropsWithChildren; + +export default function TimeContextProvider({ children }: TimeContextProviderProps) { + const [currentUnixTime, setCurrentUnixTime] = useState(getUnixTime()); + + useEffect(() => { + /** + * The following code runs timer only when tab is active + */ + // let intervalId: number; + + // window.addEventListener('focus', () => { + // intervalId = setInterval(() => { + // setCurrentUnixTime(getUnixTime()); + // }, pollingPeriod); + // }); + + // window.addEventListener('blur', () => { + // clearInterval(intervalId); + // }); + const intervalId = setInterval(() => { + setCurrentUnixTime(getUnixTime()); + }, pollingPeriod); + + return () => clearInterval(intervalId); + }, []); + + return ( + <TimeContext.Provider value={{ currentUnixTime }}> + {children} + </TimeContext.Provider> + ); +} \ No newline at end of file From 3e274e8435884070a69b0805d85deb2ae39daf8a Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Tue, 11 Jul 2023 21:50:27 +1000 Subject: [PATCH 52/87] Use innerWidth --- frontend/src/hooks/useMediaQuery.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/useMediaQuery.tsx b/frontend/src/hooks/useMediaQuery.tsx index 755b03a..7e79884 100644 --- a/frontend/src/hooks/useMediaQuery.tsx +++ b/frontend/src/hooks/useMediaQuery.tsx @@ -32,8 +32,8 @@ export function useIsTablet() { function getWindowDimensions() { return { - width: window.screen.width, - height: window.screen.height + width: window.innerWidth, + height: window.innerHeight }; } From ff30513b1b2a3e26e768b389aefa0d85230edef0 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 00:34:03 +1000 Subject: [PATCH 53/87] Hookup confirmation api --- frontend/package.json | 2 + .../ConfirmationStatus.tsx | 14 +- frontend/src/components/loader/Loader.tsx | 2 +- .../src/components/search-bar/SearchBar.tsx | 47 +++++- frontend/src/constants/urls.ts | 2 +- frontend/src/main.tsx | 10 +- frontend/src/pages/CheckerPage.tsx | 146 ++++++++++-------- frontend/src/types/checkerPageData.d.ts | 10 ++ frontend/src/types/searchResult.d.ts | 15 +- frontend/yarn.lock | 17 ++ 10 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 frontend/src/types/checkerPageData.d.ts diff --git a/frontend/package.json b/frontend/package.json index cafcd0c..cdd8b68 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "dependencies": { "autoprefixer": "^10.4.14", "axios": "^1.4.0", + "lodash.debounce": "^4.0.8", "postcss": "^8.4.24", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -22,6 +23,7 @@ "vite-tsconfig-paths": "^4.2.0" }, "devDependencies": { + "@types/lodash.debounce": "^4.0.7", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", "@typescript-eslint/eslint-plugin": "^5.59.0", diff --git a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx index e769b7a..d8a3377 100644 --- a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx +++ b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx @@ -1,16 +1,22 @@ import { twMerge } from 'tailwind-merge'; interface ConfirmationStatusProps { + subnetStatus: boolean; + parentChainStatus: boolean; className?: string; } -export default function ConfirmationStatus(props: ConfirmationStatusProps) { +export default function ConfirmationStatus({ className, subnetStatus, parentChainStatus }: ConfirmationStatusProps) { + function getStatusText(status: boolean) { + return status ? 'Yes' : 'No'; + } + return ( - <div className={twMerge(props.className, '')}> + <div className={twMerge(className, '')}> <ConfirmationStatusItem name='Result Type' value='Subnet Box' /> <h2 className='text-2xl py-4 font-extrabold leading-tight dark:text-white'>Confirmation Status</h2> - <ConfirmationStatusItem name='@SubNet' value='Yes' /> - <ConfirmationStatusItem className='pt-2.5' name='@Parent Chain' value='Yes' /> + <ConfirmationStatusItem name='@SubNet' value={getStatusText(subnetStatus)} /> + <ConfirmationStatusItem className='pt-2.5' name='@Parent Chain' value={getStatusText(parentChainStatus)} /> </div> ); } diff --git a/frontend/src/components/loader/Loader.tsx b/frontend/src/components/loader/Loader.tsx index 2768308..31a04b4 100644 --- a/frontend/src/components/loader/Loader.tsx +++ b/frontend/src/components/loader/Loader.tsx @@ -2,7 +2,7 @@ import Svg, { SvgNames } from '@/components/images/Svg'; export default function Loader(): JSX.Element { return ( - <div className='flex justify-center items-center h-full'> + <div className='flex justify-center items-center h-full min-h-[500px]'> <Svg svgName={SvgNames.Loading} sizeClass='w-[100px]' /> </div> ); diff --git a/frontend/src/components/search-bar/SearchBar.tsx b/frontend/src/components/search-bar/SearchBar.tsx index 5ee390f..864c230 100644 --- a/frontend/src/components/search-bar/SearchBar.tsx +++ b/frontend/src/components/search-bar/SearchBar.tsx @@ -1,13 +1,46 @@ -import { useState } from 'react'; +import axios from 'axios'; +import debounce from 'lodash.debounce'; +import { useEffect, useMemo } from 'react'; import Svg, { SvgNames } from '@/components/images/Svg'; +import { baseUrl } from '@/constants/urls'; +import { SearchResult } from '@/types/searchResult'; -export default function SearchBar() { - const [searchText, setSearchText] = useState(''); +interface SearchBarProps { + searchText: string; + setSearchText: (text: string) => void; + setSearchResult: (result?: SearchResult) => void; +} + +const url = `${baseUrl}/confirmation`; + +export default function SearchBar({ searchText, setSearchText, setSearchResult }: SearchBarProps) { + const debounceLoadData = useMemo( + () => debounce(async (searchText) => { + setSearchResult(undefined); + try { + const response = await axios.get(`${url}?input=${searchText}`, { + validateStatus: function (_status) { + // TODO: explore why status aren't exist + return true; + }, + }); + setSearchResult(response); + } catch (error) { + // TODO: explore the response for 404 + setSearchResult({ status: 400 }); + } + }, 1000), + [setSearchResult] + ); + + useEffect(() => { + if (!searchText) { + return; + } - function search() { - console.log(`Searching with text: ${searchText}`); - } + debounceLoadData(searchText); + }, [searchText, setSearchResult, debounceLoadData]); return ( <div className='flex justify-between border-2 border-text-white-400 dark:border-none rounded-full pl-4 pr-2.5 py-2.5 dark:bg-bg-dark-800'> @@ -21,7 +54,7 @@ export default function SearchBar() { onChange={e => setSearchText(e.target.value)} placeholder='Block Height, Block Hash, TX Hash' /> - <button className='-m-1.5' onClick={search}> + <button className='-m-1.5'> <Svg svgName={SvgNames.Search} sizeClass='w-[48px] h-[48px]' /> </button> </div> diff --git a/frontend/src/constants/urls.ts b/frontend/src/constants/urls.ts index 8bb3fda..a4bb44b 100644 --- a/frontend/src/constants/urls.ts +++ b/frontend/src/constants/urls.ts @@ -1 +1 @@ -export const baseUrl = 'https://devnetstats.apothem.network/stats/information'; \ No newline at end of file +export const baseUrl = 'https://devnetstats.apothem.network/stats'; \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index a4203cd..8abe882 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -15,7 +15,7 @@ import '@/index.css'; export async function appLoader() { async function getData() { - const response = await axios.get(`${baseUrl}/network`); + const response = await axios.get(`${baseUrl}/information/network`); return response.data; } @@ -29,10 +29,10 @@ export async function appLoader() { export async function homeLoader() { async function getDataPromises() { const urls = [ - `${baseUrl}/masternodes`, - `${baseUrl}/relayer`, - `${baseUrl}/network`, - `${baseUrl}/blocks`, + `${baseUrl}/information/masternodes`, + `${baseUrl}/information/relayer`, + `${baseUrl}/information/network`, + `${baseUrl}/information/blocks`, ]; return urls.map(url => axios.get(url)); diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index f82f162..390ea15 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -3,91 +3,103 @@ import { useState } from 'react'; import Card from '@/components/card/Card'; import ConfirmationStatus from '@/components/confirmation-status/ConfirmationStatus'; import InfoList from '@/components/info-list/InfoList'; +import Loader from '@/components/loader/Loader'; import SearchBar from '@/components/search-bar/SearchBar'; import SearchNotFound from '@/components/search-not-found/SearchNotFound'; -import { Info } from '@/types/info'; -import { SearchResult } from '@/types/searchResult'; +import { formatHash } from '@/utils/formatter'; +import type { Info } from '@/types/info'; +import type { SearchResult } from '@/types/searchResult'; export default function CheckerPage() { - const [searchResult] = useState<SearchResult>({}); + const [searchText, setSearchText] = useState(''); + const [searchResult, setSearchResult] = useState<SearchResult>(); + + return ( + <div className='min-h-[500px]'> + <SearchBar + searchText={searchText} + setSearchText={setSearchText} + setSearchResult={setSearchResult} + /> + <SearchResult + searchText={searchText} + searchResult={searchResult} + /> + </div> + ); +} + +interface SearchResultProps { + searchText: string; + searchResult?: SearchResult; +} + +function SearchResult({ searchText, searchResult }: SearchResultProps) { + if (!searchText) { + return <></>; + } + + if (!searchResult) { + return <Loader />; + } + + if (!searchResult.status) { + return <SearchNotFound />; + } + + if (searchResult.status !== 200) { + console.error(searchResult.statusText); + return <SearchNotFound />; + } + + if (!searchResult.data) { + return <></>; + } + + const { parentChain, subnet } = searchResult.data; const mappedInfo: Info = { - transaction: { - // data: [{ - // name: 'Block time', - // value: '2' - // }, { - // name: 'TX Throughput', - // value: '10 texts/s' - // }, { - // name: 'Checkpointed to', - // value: 'XDC' - // }, { - // name: 'Gas', - // value: 'Date Time' - // }] - }, subnetBlock: { data: [{ - name: 'Block time', - value: '2' - }, { - name: 'TX Throughput', - value: '10 texts/s' - }, { - name: 'Checkpointed to', - value: 'XDC' + name: 'Block height', + value: subnet.blockHeight }, { - name: 'TX idx', - value: 'Integer' + name: 'Block hash', + value: formatHash(subnet.blockHash) }] }, parentChain: { data: [{ - name: 'Block time', - value: '2' + name: 'Block height', + value: parentChain.blockHeight }, { - name: 'TX Throughput', - value: '10 texts/s' - }, { - name: 'Checkpointed to', - value: 'XDC' - }, { - name: 'TX idx', - value: 'Integer' + name: 'Block hash', + value: formatHash(parentChain.blockHash) }] - }, + } }; return ( <> - <SearchBar /> - {searchResult ? ( - <> - <ConfirmationStatus className='pt-8' /> - <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> - <Card className='border-none px-0 py-0'> - <InfoList - title='Transaction Info' - info={mappedInfo.transaction?.data} - /> - </Card> - <Card> - <InfoList - title='Subnet Block Info ' - info={mappedInfo.subnetBlock?.data} - /> - </Card> - <Card> - <InfoList - title='Checkpointing parent chain block' - info={mappedInfo.parentChain?.data} - /> - </Card> - </div></> - ) : ( - <SearchNotFound /> - )} + <ConfirmationStatus + className='pt-8' + subnetStatus={subnet.isConfirmed} + parentChainStatus={parentChain.isConfirmed} + /> + <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> + <Card className={mappedInfo.subnetBlock?.data ? '' : 'border-none px-0 py-0'}> + <InfoList + title='Subnet Block Info' + info={mappedInfo.subnetBlock?.data} + /> + </Card> + <Card className={mappedInfo.parentChain?.data ? '' : 'border-none px-0 py-0'}> + <InfoList + title='Checkpointing parent chain block' + info={mappedInfo.parentChain?.data} + /> + </Card> + </div> </> ); -} +} \ No newline at end of file diff --git a/frontend/src/types/checkerPageData.d.ts b/frontend/src/types/checkerPageData.d.ts new file mode 100644 index 0000000..7a83781 --- /dev/null +++ b/frontend/src/types/checkerPageData.d.ts @@ -0,0 +1,10 @@ +export interface CheckerPageData { + status?: number; + data?: CheckerPageData.Data[]; +} + +namespace CheckerPageData { + interface Data { + value: string; + } +} \ No newline at end of file diff --git a/frontend/src/types/searchResult.d.ts b/frontend/src/types/searchResult.d.ts index 7bd7c6b..257c0dc 100644 --- a/frontend/src/types/searchResult.d.ts +++ b/frontend/src/types/searchResult.d.ts @@ -1,10 +1,17 @@ export interface SearchResult { - status?: number; - data?: SearchResult.Data[]; + status: number; + statusText?: string; + data?: { + inputType: string; + parentChain: SearchResult.Chain; + subnet: SearchResult.Chain; + } } namespace SearchResult { - interface Data { - value: string; + interface Chain { + blockHash: string; + blockHeight: string; + isConfirmed: boolean; } } \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d3b3818..c5510fe 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -444,6 +444,18 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== +"@types/lodash.debounce@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f" + integrity sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.195" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" + integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== + "@types/prop-types@*": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" @@ -1352,6 +1364,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" From da4f095b594d8aa8520a3bb62577db99d925d135 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 12:54:52 +1000 Subject: [PATCH 54/87] Cleanup blocks component key --- frontend/src/components/Blocks.tsx | 5 ++--- frontend/src/pages/HomePage.tsx | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 91d73d0..2d79b31 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -14,10 +14,9 @@ interface BlocksProps { lastConfirmedBlock: number; blockNumber: number; blocks: Block[]; - name?: string; } -export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks, name }: BlocksProps) { +export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks }: BlocksProps) { {/* n * block-width + (n - 1) * spacing */ } const blockSize = 35 + 17.99; const translateAmount = initialLastBlock ? -((lastBlock - initialLastBlock) * blockSize) : 0; @@ -40,7 +39,7 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock const isLast = index === blocks.length - 1; return ( - <Fragment key={`${name} ${block.number}`}> + <Fragment key={block.number}> <BlockImage block={block} isFirstConfirmed={isFirstVisibleConfirmed} diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 0292118..0393196 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -89,7 +89,6 @@ export default function HomePage() { lastConfirmedBlock={lastConfirmedBlock} blockNumber={blockNumber} blocks={blocks} - name='subnet' /> </Card>) : ( <Card className='max-w-[400px]'> @@ -108,7 +107,6 @@ export default function HomePage() { lastConfirmedBlock={lastParentConfirmedBlock} blockNumber={blockNumber} blocks={parentChainBlocks} - name='parent' /> </Card>) : ( <Card className='max-w-[400px]'> From 426030f977afe9ffbbc4b30e25f19c0d2dbef590 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 15:24:41 +1000 Subject: [PATCH 55/87] Rewrite block logic to handle too many not confirmed blocks issue --- frontend/src/components/Blocks.tsx | 39 +++++++++------ .../images/block-image/BlockImage.tsx | 7 --- .../block-image/block-image.module.scss | 4 +- frontend/src/constants/config.ts | 13 +++++ frontend/src/main.tsx | 10 +--- frontend/src/pages/HomePage.tsx | 47 ++++++++++++------- frontend/tailwind.config.js | 3 ++ 7 files changed, 74 insertions(+), 49 deletions(-) create mode 100644 frontend/src/constants/config.ts diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 2d79b31..c3daa03 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -1,7 +1,6 @@ -import { Fragment } from 'react'; - import BlockConnectLine from '@/components/BlockConnectLine'; import BlockImage from '@/components/images/block-image/BlockImage'; +import { FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber } from '@/constants/config'; export interface Block { number: number; @@ -9,37 +8,49 @@ export interface Block { } interface BlocksProps { - initialLastBlock: number; lastBlock: number; lastConfirmedBlock: number; blockNumber: number; blocks: Block[]; } -export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock, blockNumber, blocks }: BlocksProps) { +export default function Blocks({ lastBlock, lastConfirmedBlock, blockNumber, blocks }: BlocksProps) { {/* n * block-width + (n - 1) * spacing */ } - const blockSize = 35 + 17.99; - const translateAmount = initialLastBlock ? -((lastBlock - initialLastBlock) * blockSize) : 0; - const unConfirmedNumber = lastBlock - lastConfirmedBlock; + const blockSize = 35 + 18; + const allBlocksNotConfirmed = lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber; + const unConfirmedNumber = allBlocksNotConfirmed ? blockNumber : lastBlock - lastConfirmedBlock; const confirmedNumber = blockNumber - unConfirmedNumber; // Definition: From left to right, the first visible index is 0 - const confirmedBlocksMidIndex = (confirmedNumber - 1) / 2; + const confirmedBlocksMidIndex = getConfirmedBlocksMidIndex(); const notConfirmedBlocksMidIndex = confirmedNumber + (unConfirmedNumber / 2); + function getConfirmedBlocksMidIndex() { + if (allBlocksNotConfirmed) { + // a number that will certainly hide 'confirmed' text + return -1; + } + + return (confirmedNumber - 1) / 2; + } + return ( <> {/* Ex: 20 blocks + spacing = (35 + 18) * 20 - 18 = 1042px */} - <div className='pt-[60px] llg:w-[1060px] w-[685px] h-[150px] overflow-hidden relative'> - <div className='flex items-center transition duration-1000' style={{ transform: `translateX(${translateAmount}px)` }}> + <div className='pt-[60px] llg:w-[1050px] w-[678px] h-[150px] overflow-hidden relative'> + <div className='flex relative'> { blocks.map((block, index) => { const isFirstVisibleConfirmed = block.number === (lastBlock - blockNumber + 1); - const isLastConfirmed = block.number === lastConfirmedBlock; + const isLastConfirmed = allBlocksNotConfirmed ? false : block.number === lastConfirmedBlock; const isFirstUnConfirmed = block.number === (lastConfirmedBlock + 1); - const isLast = index === blocks.length - 1; + const isLast = index === blocks.length - FakedNotConfirmedBlockNumber - 1; return ( - <Fragment key={block.number}> + <div + key={block.number} + className='flex items-center transition-left duration-[2s] absolute top-0' + style={{ left: `${blockSize * (index - FakedConfirmedBlockNumber)}px` }} + > <BlockImage block={block} isFirstConfirmed={isFirstVisibleConfirmed} @@ -58,7 +69,7 @@ export default function Blocks({ initialLastBlock, lastBlock, lastConfirmedBlock ) } - </Fragment> + </div> ); }) } diff --git a/frontend/src/components/images/block-image/BlockImage.tsx b/frontend/src/components/images/block-image/BlockImage.tsx index 2634635..b602471 100644 --- a/frontend/src/components/images/block-image/BlockImage.tsx +++ b/frontend/src/components/images/block-image/BlockImage.tsx @@ -28,13 +28,6 @@ export default function BlockImage(props: BlockImageProps) { const { theme } = useContext(ThemeContext); const isDarkMode = theme === 'dark'; - // Save some render - if (props.blockNumber > 40 && props.index <= 20) { - return ( - <div className='shrink-0 w-[35px]' /> - ); - } - return ( <div className='shrink-0 relative w-[35px] h-[37.82px] text-lg leading-none'> {isDarkMode ? ( diff --git a/frontend/src/components/images/block-image/block-image.module.scss b/frontend/src/components/images/block-image/block-image.module.scss index f209c5e..b8ec8ef 100644 --- a/frontend/src/components/images/block-image/block-image.module.scss +++ b/frontend/src/components/images/block-image/block-image.module.scss @@ -1,6 +1,6 @@ .animate { - -webkit-transition: transform 0.5s ease-out; - transition: transform 0.5s ease-out; + -webkit-transition: clip-path 0.5s ease-out; + transition: clip-path 0.5s ease-out; } .hide { diff --git a/frontend/src/constants/config.ts b/frontend/src/constants/config.ts new file mode 100644 index 0000000..78cfc8b --- /dev/null +++ b/frontend/src/constants/config.ts @@ -0,0 +1,13 @@ +/** + * Number of faked confirmed block on the left of visible blocks area + */ +export const FakedConfirmedBlockNumber = 10; + +/** + * Number of faked notConfirmed block on the right of visible blocks area + */ +export const FakedNotConfirmedBlockNumber = 10; + + +export const WideScreenBlockNumber = 20; +export const StandardScreenBlockNumber = 13; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index a4203cd..d1f8dc0 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -49,19 +49,11 @@ export async function homeLoader() { return response.value.data; }); - let blocks = data[3]; - blocks = ({ - ...blocks, latestParentChainCommittedBlock: { - hash: blocks.latestParentChainCommittedBlock.hash, - number: blocks.latestSubnetCommittedBlock.number - 1 - } - }); - return { masterNodes: data[0], relayer: data[1], network: data[2], - blocks + blocks: data[3] }; } diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 0393196..d9760cf 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -6,54 +6,69 @@ import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; import InfoList from '@/components/info-list/InfoList'; +import { + FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, StandardScreenBlockNumber, + WideScreenBlockNumber +} from '@/constants/config'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; -import { useIsDesktop, useIsDesktopL, useIsTablet } from '@/hooks/useMediaQuery'; +import { useIsDesktopL, useIsTablet } from '@/hooks/useMediaQuery'; import type { HomeLoaderData } from '@/types/loaderData'; function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { const blocks = []; + // To allow animation when the furtherest left block move out + const allBlockNumber = blockNumber + FakedConfirmedBlockNumber; + const unconfirmedCount = lastBlock - lastConfirmedBlock; + const confirmedCount = allBlockNumber - unconfirmedCount > 0 ? allBlockNumber - unconfirmedCount : 0; + const firstBlockHeight = lastBlock - allBlockNumber + 1; + + if (lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber) { + // unconfirmed blocks + for (let i = 0; i < blockNumber + FakedConfirmedBlockNumber + FakedNotConfirmedBlockNumber; i++) { + blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); + } + + return blocks; + } // confirmed blocks - for (let blockHeight = lastBlock - blockNumber + 1; blockHeight <= lastConfirmedBlock; blockHeight++) { - blocks.push({ number: blockHeight, confirmed: true }); + for (let i = 0; i < confirmedCount; i++) { + blocks.push({ number: firstBlockHeight + i, confirmed: true }); } // unconfirmed blocks - for (let blockHeight = lastConfirmedBlock + 1; blockHeight <= lastBlock; blockHeight++) { - blocks.push({ number: blockHeight, confirmed: false }); + for (let i = 0; i < unconfirmedCount + FakedNotConfirmedBlockNumber; i++) { + blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); } return blocks; } + export default function HomePage() { const isDesktopL = useIsDesktopL(); - const isDesktop = useIsDesktop(); const isTablet = useIsTablet(); // use 13 blocks for tablet and desktop, otherwise use 20 blocks(XL desktop) - const blockNumber = isDesktopL ? 20 : isDesktop ? 13 : 13; + const blockNumber = isDesktopL ? WideScreenBlockNumber : StandardScreenBlockNumber; const loaderData = useLoaderData() as HomeLoaderData; const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); - const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); + const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestParentChainCommittedBlock.number, blockNumber)); const [initialLastBlock] = useState<number>(loaderData.blocks.latestMinedBlock.number); const { currentUnixTime } = useContext(TimeContext); useEffect(() => { async function getData() { - // const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); - const { data: { latestMinedBlock, latestSubnetCommittedBlock } } = await axios.get(`${baseUrl}/blocks`); + const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/blocks`); setLastBlock(latestMinedBlock.number); setLastConfirmedBlock(latestSubnetCommittedBlock.number); - setLastParentConfirmedBlock(latestSubnetCommittedBlock.number - 1); + setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); - const newBlockNumber = latestMinedBlock.number - initialLastBlock + blockNumber; - const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, newBlockNumber); - // Mock - const parentChainBlocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number - 1, newBlockNumber); + const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, blockNumber); + const parentChainBlocks = getBlocks(latestMinedBlock.number, latestParentChainCommittedBlock.number, blockNumber); setBlocks(blocks); setParentChainBlocks(parentChainBlocks); } @@ -84,7 +99,6 @@ export default function HomePage() { <Card> <h1 className='pb-4 text-xl font-medium'>subnet blockchain</h1> <Blocks - initialLastBlock={initialLastBlock} lastBlock={lastBlock} lastConfirmedBlock={lastConfirmedBlock} blockNumber={blockNumber} @@ -102,7 +116,6 @@ export default function HomePage() { <Card> <h1 className='pb-4 text-xl font-medium'>checkpoints at the parent chain</h1> <Blocks - initialLastBlock={initialLastBlock} lastBlock={lastBlock} lastConfirmedBlock={lastParentConfirmedBlock} blockNumber={blockNumber} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 079bfde..0f841bf 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -76,6 +76,9 @@ export default { }, fontSize: { '2xl': ['26px'] + }, + transition: { + 'left': 'left' } }, }, From cb08f4193eddba2398a4e950d7434aed114c484d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 15:28:59 +1000 Subject: [PATCH 56/87] Move confirmed text away when it's all unconfirmed --- frontend/src/components/Blocks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index c3daa03..6dfdf4d 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -27,7 +27,7 @@ export default function Blocks({ lastBlock, lastConfirmedBlock, blockNumber, blo function getConfirmedBlocksMidIndex() { if (allBlocksNotConfirmed) { // a number that will certainly hide 'confirmed' text - return -1; + return -100; } return (confirmedNumber - 1) / 2; From 1edea3d4035f29a13cecf3d22aa9f2caec3ff726 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 15:30:01 +1000 Subject: [PATCH 57/87] Put app in middle in big screen --- frontend/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fc1a09e..e66c74f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,7 +17,7 @@ function App() { <ThemeContextProvider> <AlertProvider> <div className={`${!isDesktop ? 'flex-col' : ''} - relative max-w-[1440px] flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900` + relative max-w-[1440px] mx-auto flex font-nunito-sans text-text-dark dark:text-text-white dark:bg-bg-dark-900` }> <Nav /> <main className='mx-6 my-8 grow llg-w-[1146px] relative'> From 73182eb4b17df892395f6e9828ff50d35a1eec70 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 17:20:17 +1000 Subject: [PATCH 58/87] Use the correct api url --- frontend/src/pages/HomePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index d9760cf..157f109 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -62,7 +62,7 @@ export default function HomePage() { useEffect(() => { async function getData() { - const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/blocks`); + const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); setLastBlock(latestMinedBlock.number); setLastConfirmedBlock(latestSubnetCommittedBlock.number); setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); From bb196d405b7cc212f8dc9c74dafce05aece686c8 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 22:59:00 +1000 Subject: [PATCH 59/87] Update subject to blocks api format changes --- frontend/src/pages/HomePage.tsx | 43 +++++++++++++++--------------- frontend/src/types/loaderData.d.ts | 35 ++++++++++++------------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 157f109..13f4ef1 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -52,43 +52,44 @@ export default function HomePage() { const blockNumber = isDesktopL ? WideScreenBlockNumber : StandardScreenBlockNumber; const loaderData = useLoaderData() as HomeLoaderData; - const [lastBlock, setLastBlock] = useState(loaderData.blocks.latestMinedBlock.number); - const [lastConfirmedBlock, setLastConfirmedBlock] = useState(loaderData.blocks.latestSubnetCommittedBlock.number); - const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.latestParentChainCommittedBlock.number); - const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestSubnetCommittedBlock.number, blockNumber)); - const [parentChainBlocks, setParentChainBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.latestMinedBlock.number, loaderData.blocks.latestParentChainCommittedBlock.number, blockNumber)); - const [initialLastBlock] = useState<number>(loaderData.blocks.latestMinedBlock.number); + const [lastSubnetBlock, setLastSubnetBlock] = useState(loaderData.blocks.subnet.latestMinedBlock.number); + const [lastParentBlock, setLastParentBlock] = useState(loaderData.blocks.checkpoint.latestSubmittedSubnetBlock.number); + const [lastSubnetConfirmedBlock, setLastSubnetConfirmedBlock] = useState(loaderData.blocks.subnet.latestCommittedBlock.number); + const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.checkpoint.latestCommittedSubnetBlock.number); + const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.subnet.latestMinedBlock.number, loaderData.blocks.subnet.latestCommittedBlock.number, blockNumber)); + const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); const { currentUnixTime } = useContext(TimeContext); useEffect(() => { async function getData() { - const { data: { latestMinedBlock, latestSubnetCommittedBlock, latestParentChainCommittedBlock } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); - setLastBlock(latestMinedBlock.number); - setLastConfirmedBlock(latestSubnetCommittedBlock.number); - setLastParentConfirmedBlock(latestParentChainCommittedBlock.number); + const { data: { subnet, checkpoint } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); + setLastSubnetBlock(subnet.latestMinedBlock.number); + setLastSubnetConfirmedBlock(subnet.latestCommittedBlock.number); + setLastParentBlock(checkpoint.latestSubmittedSubnetBlock.number); + setLastParentConfirmedBlock(checkpoint.latestCommittedSubnetBlock.number); - const blocks = getBlocks(latestMinedBlock.number, latestSubnetCommittedBlock.number, blockNumber); - const parentChainBlocks = getBlocks(latestMinedBlock.number, latestParentChainCommittedBlock.number, blockNumber); + const blocks = getBlocks(subnet.latestMinedBlock.number, subnet.latestCommittedBlock.number, blockNumber); + const parentBlocks = getBlocks(checkpoint.latestSubmittedSubnetBlock.number, checkpoint.latestCommittedSubnetBlock.number, blockNumber); setBlocks(blocks); - setParentChainBlocks(parentChainBlocks); + setParentBlocks(parentBlocks); } getData(); - }, [blockNumber, currentUnixTime, initialLastBlock]); + }, [blockNumber, currentUnixTime]); const mappedInfo = { subnet: { data: [{ - name: 'Last committed block number', value: lastConfirmedBlock + name: 'Last committed block number', value: lastSubnetConfirmedBlock }, { - name: 'Last mined block number', value: lastBlock + name: 'Last mined block number', value: lastSubnetBlock }] }, parentChain: { data: [{ name: 'Last committed block number', value: lastParentConfirmedBlock }, { - name: 'Last mined block number', value: lastBlock + name: 'Last mined block number', value: lastSubnetBlock }] } }; @@ -99,8 +100,8 @@ export default function HomePage() { <Card> <h1 className='pb-4 text-xl font-medium'>subnet blockchain</h1> <Blocks - lastBlock={lastBlock} - lastConfirmedBlock={lastConfirmedBlock} + lastBlock={lastSubnetBlock} + lastConfirmedBlock={lastSubnetConfirmedBlock} blockNumber={blockNumber} blocks={blocks} /> @@ -116,10 +117,10 @@ export default function HomePage() { <Card> <h1 className='pb-4 text-xl font-medium'>checkpoints at the parent chain</h1> <Blocks - lastBlock={lastBlock} + lastBlock={lastParentBlock} lastConfirmedBlock={lastParentConfirmedBlock} blockNumber={blockNumber} - blocks={parentChainBlocks} + blocks={parentBlocks} /> </Card>) : ( <Card className='max-w-[400px]'> diff --git a/frontend/src/types/loaderData.d.ts b/frontend/src/types/loaderData.d.ts index b6c7957..74f3aba 100644 --- a/frontend/src/types/loaderData.d.ts +++ b/frontend/src/types/loaderData.d.ts @@ -20,7 +20,7 @@ export namespace HomeLoaderData { /** A list of existing master nodes. Sorted by nodeId */ nodes: MasterNodes.Node[]; } - + namespace MasterNodes { interface Node { address: string; @@ -31,31 +31,30 @@ export namespace HomeLoaderData { interface Blocks { /** A list of recently mined blocks. Sorted by block number */ blocks: Blocks.Block[]; - /** The block that was most recently mined in the subnet. regardless its confirmation status */ - latestMinedBlock: { - hash: string; - number: number; + + subnet: { + /** The block that was most recently mined in the subnet. regardless its confirmation status */ + latestMinedBlock: Blocks.BaseBlock; + /** The block that was most recently confirm to be committed in the subnet. (Ignoring its confirmation status from parent chain) */ + latestCommittedBlock: Blocks.BaseBlock; }; - /** The block that was most recently confirm to be committed in the subnet. (Ignoring its confirmation status from parent chain) */ - latestSubnetCommittedBlock: { - hash: string; - number: number; - }; - /** The block that was most recently confirm to be committed in the parent chain. */ - latestParentChainCommittedBlock: { - hash: string; - number: number; + checkpoint: { + latestCommittedSubnetBlock: Blocks.BaseBlock; + latestSubmittedSubnetBlock: Blocks.BaseBlock; }; /** A simple enum to indicate whether the subnet chain is operational. i.e if blocks are mined */ chainHealth: 'UP' | 'DOWN'; } - + namespace Blocks { - interface Block { + interface BaseBlock { /** The subnet block hash */ hash: string; /** The subnet block number */ number: number; + } + + interface Block extends BaseBlock { /** The subnet block's parentHash */ parentHash: string; /** The masternode address who mined this block */ @@ -64,7 +63,7 @@ export namespace HomeLoaderData { committedInSubnet: boolean; /** This boolean value is to indicate whether this block has been confirmed in the parent chain smart contract */ committedInParentChain: boolean; - + timestamp: number; } } @@ -88,7 +87,7 @@ export namespace HomeLoaderData { }; averageTXfee: number; } - + interface Network { subnet: { name: string; From 9ffea62ad35543aefd5dd4fe1738010bb09b3e93 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Wed, 12 Jul 2023 23:27:19 +1000 Subject: [PATCH 60/87] Add axios type and handle error status properly --- frontend/package.json | 1 + .../src/components/search-bar/SearchBar.tsx | 18 ++++++++---------- .../search-not-found/SearchNotFound.tsx | 17 +++++++++++++++-- frontend/src/pages/CheckerPage.tsx | 17 +++++------------ frontend/src/types/searchResult.d.ts | 10 +++------- frontend/yarn.lock | 9 ++++++++- 6 files changed, 40 insertions(+), 32 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index cdd8b68..5e3b7ed 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "vite-tsconfig-paths": "^4.2.0" }, "devDependencies": { + "@types/axios": "^0.14.0", "@types/lodash.debounce": "^4.0.7", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", diff --git a/frontend/src/components/search-bar/SearchBar.tsx b/frontend/src/components/search-bar/SearchBar.tsx index 864c230..640875b 100644 --- a/frontend/src/components/search-bar/SearchBar.tsx +++ b/frontend/src/components/search-bar/SearchBar.tsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import debounce from 'lodash.debounce'; import { useEffect, useMemo } from 'react'; @@ -9,7 +9,7 @@ import { SearchResult } from '@/types/searchResult'; interface SearchBarProps { searchText: string; setSearchText: (text: string) => void; - setSearchResult: (result?: SearchResult) => void; + setSearchResult: (result?: AxiosResponse<SearchResult>) => void; } const url = `${baseUrl}/confirmation`; @@ -19,16 +19,14 @@ export default function SearchBar({ searchText, setSearchText, setSearchResult } () => debounce(async (searchText) => { setSearchResult(undefined); try { - const response = await axios.get(`${url}?input=${searchText}`, { - validateStatus: function (_status) { - // TODO: explore why status aren't exist - return true; - }, - }); + const response = await axios.get<SearchResult>(`${url}?input=${searchText}`); setSearchResult(response); } catch (error) { - // TODO: explore the response for 404 - setSearchResult({ status: 400 }); + if (!axios.isAxiosError<SearchResult>(error)) { + return; + } + + setSearchResult(error.response); } }, 1000), [setSearchResult] diff --git a/frontend/src/components/search-not-found/SearchNotFound.tsx b/frontend/src/components/search-not-found/SearchNotFound.tsx index 9020c2f..251bf08 100644 --- a/frontend/src/components/search-not-found/SearchNotFound.tsx +++ b/frontend/src/components/search-not-found/SearchNotFound.tsx @@ -4,9 +4,22 @@ import { Dot } from '@/components/dot/Dot'; import Svg, { SvgNames } from '@/components/images/Svg'; import { ThemeContext } from '@/contexts/ThemeContext'; -export default function SearchNotFound() { +interface SearchNotFoundProps { + status: number; +} + +function getReadableErrorMessage(status: number) { + if (status === 400) { + return 'Invalid search term, please use a valid Block Height, Block Hash or TX Hash'; + } + + return 'No result for the query, please try again' +} + +export default function SearchNotFound({ status }: SearchNotFoundProps) { const { theme } = useContext(ThemeContext); const svgName = theme === 'dark' ? SvgNames.NoResultDark : SvgNames.NoResult; + const readableErrorMessage = getReadableErrorMessage(status); return ( <div className='rounded-3xl mt-6 bg-text-dark-100 dark:bg-bg-dark-700'> @@ -18,7 +31,7 @@ export default function SearchNotFound() { </div> <div className='mt-6 h-[250px] bg-white dark:bg-bg-dark-900 flex flex-col items-center justify-center'> <Svg svgName={svgName} sizeClass='w-[72px] h-[100px]' /> - <p className='dark:text-text-white text-text-dark text-xl pt-6'>No result for the query, please try again</p> + <p className='dark:text-text-white text-text-dark text-xl pt-6'>{readableErrorMessage}</p> </div> </div> </div> diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 390ea15..f04f8ed 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -1,3 +1,4 @@ +import { AxiosResponse } from 'axios'; import { useState } from 'react'; import Card from '@/components/card/Card'; @@ -10,9 +11,10 @@ import { formatHash } from '@/utils/formatter'; import type { Info } from '@/types/info'; import type { SearchResult } from '@/types/searchResult'; + export default function CheckerPage() { const [searchText, setSearchText] = useState(''); - const [searchResult, setSearchResult] = useState<SearchResult>(); + const [searchResult, setSearchResult] = useState<AxiosResponse<SearchResult>>(); return ( <div className='min-h-[500px]'> @@ -31,7 +33,7 @@ export default function CheckerPage() { interface SearchResultProps { searchText: string; - searchResult?: SearchResult; + searchResult?: AxiosResponse<SearchResult>; } function SearchResult({ searchText, searchResult }: SearchResultProps) { @@ -43,17 +45,8 @@ function SearchResult({ searchText, searchResult }: SearchResultProps) { return <Loader />; } - if (!searchResult.status) { - return <SearchNotFound />; - } - if (searchResult.status !== 200) { - console.error(searchResult.statusText); - return <SearchNotFound />; - } - - if (!searchResult.data) { - return <></>; + return <SearchNotFound status={searchResult.status} />; } const { parentChain, subnet } = searchResult.data; diff --git a/frontend/src/types/searchResult.d.ts b/frontend/src/types/searchResult.d.ts index 257c0dc..e204f65 100644 --- a/frontend/src/types/searchResult.d.ts +++ b/frontend/src/types/searchResult.d.ts @@ -1,11 +1,7 @@ export interface SearchResult { - status: number; - statusText?: string; - data?: { - inputType: string; - parentChain: SearchResult.Chain; - subnet: SearchResult.Chain; - } + inputType: string; + parentChain: SearchResult.Chain; + subnet: SearchResult.Chain; } namespace SearchResult { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c5510fe..ed919a9 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -439,6 +439,13 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.0.tgz#550a8d5760b78efc5d60f62b5829b0f74c1fde81" integrity sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw== +"@types/axios@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" + integrity sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ== + dependencies: + axios "*" + "@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" @@ -665,7 +672,7 @@ autoprefixer@^10.4.14: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -axios@^1.4.0: +axios@*, axios@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== From 9ddddd9431b34831089d8609823b19210ec5b8c6 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 00:02:08 +1000 Subject: [PATCH 61/87] Dynamic block number --- frontend/src/components/Blocks.tsx | 6 ++++-- frontend/src/constants/config.ts | 7 +++++-- frontend/src/hooks/useMediaQuery.tsx | 6 ++++++ frontend/src/pages/HomePage.tsx | 27 +++++++++++++++++++++------ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 6dfdf4d..5c5604a 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -1,6 +1,8 @@ import BlockConnectLine from '@/components/BlockConnectLine'; import BlockImage from '@/components/images/block-image/BlockImage'; -import { FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber } from '@/constants/config'; +import { + BlockGapSize, BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber +} from '@/constants/config'; export interface Block { number: number; @@ -36,7 +38,7 @@ export default function Blocks({ lastBlock, lastConfirmedBlock, blockNumber, blo return ( <> {/* Ex: 20 blocks + spacing = (35 + 18) * 20 - 18 = 1042px */} - <div className='pt-[60px] llg:w-[1050px] w-[678px] h-[150px] overflow-hidden relative'> + <div className='pt-[60px] h-[150px] overflow-hidden relative' style={{ width: `${BlockSizeWithGap * blockNumber - BlockGapSize}px` }}> <div className='flex relative'> { blocks.map((block, index) => { diff --git a/frontend/src/constants/config.ts b/frontend/src/constants/config.ts index 78cfc8b..d5d451e 100644 --- a/frontend/src/constants/config.ts +++ b/frontend/src/constants/config.ts @@ -8,6 +8,9 @@ export const FakedConfirmedBlockNumber = 10; */ export const FakedNotConfirmedBlockNumber = 10; - export const WideScreenBlockNumber = 20; -export const StandardScreenBlockNumber = 13; +export const StandardScreenBlockNumber = 17; + +export const BlockSize = 35; +export const BlockGapSize = 16; +export const BlockSizeWithGap = 53; diff --git a/frontend/src/hooks/useMediaQuery.tsx b/frontend/src/hooks/useMediaQuery.tsx index 7e79884..32308cb 100644 --- a/frontend/src/hooks/useMediaQuery.tsx +++ b/frontend/src/hooks/useMediaQuery.tsx @@ -6,6 +6,12 @@ const breakpoints = { tablet: 768, }; +export function useWindowWidth() { + const { width } = useWindowDimensions(); + + return width; +} + export function useIsDesktopL() { const { width } = useWindowDimensions(); diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 13f4ef1..595e698 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -7,14 +7,15 @@ import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; import InfoList from '@/components/info-list/InfoList'; import { - FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, StandardScreenBlockNumber, - WideScreenBlockNumber + BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, + StandardScreenBlockNumber, WideScreenBlockNumber } from '@/constants/config'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; -import { useIsDesktopL, useIsTablet } from '@/hooks/useMediaQuery'; +import { useIsTablet, useWindowWidth } from '@/hooks/useMediaQuery'; import type { HomeLoaderData } from '@/types/loaderData'; + function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { const blocks = []; // To allow animation when the furtherest left block move out @@ -45,11 +46,25 @@ function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: n return blocks; } +function getBlockNumber(windowWidth: number) { + if (windowWidth >= 1440) { + return WideScreenBlockNumber; + } + + if (windowWidth < 1024) { + const diff = 1024 - windowWidth; + return StandardScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); + } + + const diff = 1440 - windowWidth; + return WideScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); +} + export default function HomePage() { - const isDesktopL = useIsDesktopL(); const isTablet = useIsTablet(); - // use 13 blocks for tablet and desktop, otherwise use 20 blocks(XL desktop) - const blockNumber = isDesktopL ? WideScreenBlockNumber : StandardScreenBlockNumber; + const windowWidth = useWindowWidth(); + + const blockNumber = getBlockNumber(windowWidth); const loaderData = useLoaderData() as HomeLoaderData; const [lastSubnetBlock, setLastSubnetBlock] = useState(loaderData.blocks.subnet.latestMinedBlock.number); From bc37c13210bab8ae652ee7b5a73b53f9571cd7ac Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 00:11:20 +1000 Subject: [PATCH 62/87] Fix theme when open in small screen --- frontend/src/components/nav/Nav.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index 03ed129..240e379 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -37,16 +37,17 @@ export default function Nav(): JSX.Element { if (!isDesktop) { if (!navOpened) { return ( - <div className='z-[100] dark:bg-bg-dark-900 sticky top-0 left-0'> + <div className='z-[100] dark:bg-bg-dark-900 bg-white sticky top-0 left-0 flex justify-between'> <button className='p-6' onClick={openNav}> <Svg svgName={SvgNames.Menu} /> </button> + <ThemeSwitch /> </div> ); } return ( - <nav className='z-[100] sticky top-0 left-0 dark:bg-bg-dark-1000 grow shadow-grey flex flex-col justify-between rounded-b-xl'> + <nav className='z-[100] sticky top-0 left-0 dark:bg-bg-dark-1000 bg-white grow shadow-grey flex flex-col justify-between rounded-b-xl'> <div> <div className='flex items-center flex-col border-text-white dark:border-border-light relative'> <button className='absolute left-0 top-0 p-6 text-3xl' onClick={closeNav} > @@ -63,7 +64,6 @@ export default function Nav(): JSX.Element { <NavItem Image={CheckerImage} text='Confirmation Checker' page='checker' /> <NavItem Image={ManagementImage} text='Management' page='management' /> </div> - <ThemeSwitch /> </nav> ); } From 06d899aa405e17665e578e127b38b61b22f5cc49 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 12:57:15 +1000 Subject: [PATCH 63/87] Tweak theme switch in mobile --- frontend/src/components/nav/Nav.tsx | 2 +- .../components/theme-switch/ThemeSwitch.tsx | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index 240e379..eeb3597 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -41,7 +41,7 @@ export default function Nav(): JSX.Element { <button className='p-6' onClick={openNav}> <Svg svgName={SvgNames.Menu} /> </button> - <ThemeSwitch /> + <ThemeSwitch isMobile /> </div> ); } diff --git a/frontend/src/components/theme-switch/ThemeSwitch.tsx b/frontend/src/components/theme-switch/ThemeSwitch.tsx index e8fc1c9..46db4e1 100644 --- a/frontend/src/components/theme-switch/ThemeSwitch.tsx +++ b/frontend/src/components/theme-switch/ThemeSwitch.tsx @@ -3,7 +3,11 @@ import { PropsWithChildren, useContext, useEffect, useState } from 'react'; import { InlineSvg, InlineSvgColours, InlineSvgNames } from '@/components/images/Svg'; import { ThemeContext } from '@/contexts/ThemeContext'; -export default function ThemeSwitch() { +interface ThemeSwitch { + isMobile?: boolean; +} + +export default function ThemeSwitch({ isMobile }: ThemeSwitch) { const [selectedTheme, setSelectedTheme] = useState<ThemeModes>(() => { const theme = window.localStorage.getItem('theme'); if (theme !== 'light' && theme !== 'dark') { @@ -30,12 +34,12 @@ export default function ThemeSwitch() { }, [setTheme, selectedTheme]); return ( - <div className='shadow-grey m-6 w-[188px] dark:bg-bg-dark-900 dark:border-0 border-2 rounded-full'> + <div className={`${isMobile ? ' w-[128px]' : 'w-[188px]'}shadow-grey m-6 dark:bg-bg-dark-900 dark:border-0 border-2 rounded-full`}> <div className='flex justify-between px-[6px]'> - <ThemeItem selected={selectedTheme === 'light'} changeTheme={() => setSelectedTheme('light')}> + <ThemeItem selected={selectedTheme === 'light'} changeTheme={() => setSelectedTheme('light')} isMobile={isMobile}> <InlineSvg svgName={InlineSvgNames.Sun} colour={selectedTheme === 'light' ? InlineSvgColours.Primary : InlineSvgColours.Grey} /> </ThemeItem> - <ThemeItem selected={selectedTheme === 'dark'} changeTheme={() => setSelectedTheme('dark')}> + <ThemeItem selected={selectedTheme === 'dark'} changeTheme={() => setSelectedTheme('dark')} isMobile={isMobile}> <InlineSvg svgName={InlineSvgNames.Moon} colour={selectedTheme === 'dark' ? InlineSvgColours.Primary : InlineSvgColours.Grey} /> </ThemeItem> </div> @@ -48,12 +52,17 @@ export type ThemeModes = 'dark' | 'light'; interface ThemeItemProps extends PropsWithChildren { changeTheme: () => void; selected?: boolean; + isMobile?: boolean; } -function ThemeItem({ children, selected, changeTheme }: ThemeItemProps) { +function ThemeItem({ children, selected, changeTheme, isMobile }: ThemeItemProps) { return ( <div - className={`${selected ? 'bg-primary-light' : ''} px-6 py-2.5 grow rounded-full my-2.5 flex justify-center hover:cursor-pointer`} + className={` + ${selected ? 'bg-primary-light' : ''} + ${isMobile ? 'px-3 py-1' : 'px-6 py-2.5'} + grow rounded-full my-1 flex justify-center hover:cursor-pointer` + } onClick={changeTheme} > {children} From 60c819100e044dc849942efeac980ac337d6a5e3 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 14:19:45 +1000 Subject: [PATCH 64/87] Fix any type --- .../blocks-info/blocks-info-item/BlocksInfoItem.tsx | 12 ++++++------ frontend/src/components/info-cards/InfoCards.tsx | 4 ++-- frontend/src/types/loaderData.d.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index d3556d8..e5d6f32 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -21,16 +21,16 @@ interface RecentBlock { timestamp: number; } -interface MasterNode { +export interface MasterNode { type: 'master-node'; number: number; account: string; role: MasterNodeRoles; - activity: boolean; - latestParticipateBlock: number; + // activity: boolean; + // latestParticipateBlock: number; } -type MasterNodeRoles = 'master-node' | 'candidate' | 'penalty'; +type MasterNodeRoles = 'MASTERNODE' | 'CANDIDATE' | 'PENALTY'; export function BlocksInfoItem(data: BlocksInfoItemProps) { const { currentUnixTime } = useContext(TimeContext); @@ -123,10 +123,10 @@ interface MasterNodeRoleProps { } function MasterNodeRole({ role }: MasterNodeRoleProps) { - if (role === 'candidate') { + if (role === 'CANDIDATE') { return <Svg svgName={SvgNames.Standby} />; } - else if (role === 'penalty') { + else if (role === 'PENALTY') { return <Svg svgName={SvgNames.Penalty} />; } diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index f16d3f3..f0a536a 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -95,7 +95,7 @@ export default function InfoCards() { } : null, }; - const masterNodes = loaderData.masterNodes?.nodes?.map((v: any, i: number) => ({ + const masterNodes = loaderData.masterNodes?.nodes?.map<MasterNode>((v, i: number) => ({ ...v, type: 'master-node', account: formatHash(v.address), @@ -108,7 +108,7 @@ export default function InfoCards() { } // TODO: From api - const data: any = []; + const data: MasterNode[] = []; setRecentBlocks(recentBlocks => { return [...recentBlocks, ...data]; diff --git a/frontend/src/types/loaderData.d.ts b/frontend/src/types/loaderData.d.ts index 74f3aba..0a724e9 100644 --- a/frontend/src/types/loaderData.d.ts +++ b/frontend/src/types/loaderData.d.ts @@ -24,7 +24,7 @@ export namespace HomeLoaderData { namespace MasterNodes { interface Node { address: string; - type: 'CANDIDATE', 'MASTERNODE', 'PENALTY'; + role: 'CANDIDATE', 'MASTERNODE', 'PENALTY'; } } From 6319e944b6ee0c84011f6ecfd6cdb5ada44139e8 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 14:20:01 +1000 Subject: [PATCH 65/87] Checker page transaction info card --- .../ConfirmationStatus.tsx | 71 ++++++++-- .../src/components/info-cards/InfoCards.tsx | 40 +----- .../src/components/info-list/InfoList.tsx | 2 +- frontend/src/pages/CheckerPage.tsx | 129 +++++++++++++----- frontend/src/types/searchResult.d.ts | 14 +- frontend/src/utils/formatter.ts | 39 ++++++ 6 files changed, 209 insertions(+), 86 deletions(-) diff --git a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx index d8a3377..17a3f88 100644 --- a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx +++ b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx @@ -1,37 +1,84 @@ import { twMerge } from 'tailwind-merge'; +import { SearchResult } from '@/types/searchResult'; + interface ConfirmationStatusProps { subnetStatus: boolean; parentChainStatus: boolean; + inputType: SearchResult.InputType; className?: string; } -export default function ConfirmationStatus({ className, subnetStatus, parentChainStatus }: ConfirmationStatusProps) { - function getStatusText(status: boolean) { - return status ? 'Yes' : 'No'; +export default function ConfirmationStatus({ className, inputType, subnetStatus, parentChainStatus }: ConfirmationStatusProps) { + function getInputTypeText(inputType: SearchResult.InputType) { + switch (inputType) { + case 'BLOCK_HASH': + case 'BLOCK_HEIGHT': + return 'Subnet box'; + case 'TRANSACTION_HASH': + return 'Subnet tx'; + default: + return 'Unknown'; + } } return ( <div className={twMerge(className, '')}> - <ConfirmationStatusItem name='Result Type' value='Subnet Box' /> + <ConfirmationItem name='Result Type' text={getInputTypeText(inputType)} /> <h2 className='text-2xl py-4 font-extrabold leading-tight dark:text-white'>Confirmation Status</h2> - <ConfirmationStatusItem name='@SubNet' value={getStatusText(subnetStatus)} /> - <ConfirmationStatusItem className='pt-2.5' name='@Parent Chain' value={getStatusText(parentChainStatus)} /> + <ConfirmationStatusItem name='@SubNet' status={subnetStatus} /> + <ConfirmationStatusItem className='pt-2.5' name='@Parent Chain' status={parentChainStatus} /> </div> ); } -interface ConfirmationStatusItemProps { +interface ConfirmationStatusItemProps extends ConfirmationBaseItem { + status: boolean; +} + +function ConfirmationStatusItem(props: ConfirmationStatusItemProps) { + function getStatusText(status: boolean) { + return status ? 'Yes' : 'No'; + } + + const { status, ...remainingProps } = props; + + + const text = getStatusText(status); + + return ( + <ConfirmationItem text={text} {...remainingProps} isWarningStatus={!status} /> + ); +} + +interface ConfirmationBaseItem { name: string; - value: string; className?: string; } -function ConfirmationStatusItem(props: ConfirmationStatusItemProps) { +interface ConfirmationItemProps extends ConfirmationBaseItem { + text: string; + isWarningStatus?: boolean; +} + +function ConfirmationItem({ className, name, text, isWarningStatus }: ConfirmationItemProps) { + function getStatusClassName(isWarningStatus?: boolean) { + if (isWarningStatus) { + return 'bg-warning text-warning'; + } + + return 'bg-sky-500 text-sky-500'; + } + return ( - <div className={twMerge(props.className, '')}> - <span className='inline-block w-[120px]'>{props.name}</span> - <span className='text-primary dark:text-sky-300 font-semibold leading-tight bg-sky-300 bg-opacity-20 rounded-lg px-[5px] py-[3px]'>{props.value}</span> + <div className={twMerge(className, '')}> + <span className='inline-block w-[120px]'>{name}</span> + <span className={` + ${getStatusClassName(isWarningStatus)} + font-semibold leading-tight bg-opacity-20 rounded-lg px-[5px] py-[3px]` + }> + {text} + </span> </div> ); } diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index f0a536a..52ef2cc 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -1,13 +1,15 @@ import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; -import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; +import { + BlocksInfoItem, MasterNode +} from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; import { Info, InfoListHealth } from '@/types/info'; import { HomeLoaderData } from '@/types/loaderData'; -import { formatHash } from '@/utils/formatter'; +import { formatHash, formatMoney } from '@/utils/formatter'; export default function InfoCards() { const loaderData = useLoaderData() as HomeLoaderData; @@ -37,37 +39,6 @@ export default function InfoCards() { })); } - function formatBalance(balance: number) { - const abbreTable = [{ - name: 'Septillion', pow: 24, - }, { - name: 'Sextillion', pow: 21, - }, { - name: 'Quintillion', pow: 18, - }, { - name: 'Quadrillion', pow: 15, - }, { - name: 'Trillion', pow: 12, - }, { - name: 'B', pow: 9, - }, { - name: 'M', pow: 6, - }, { - name: 'K', pow: 3, - }]; - - for (let i = 0; i < abbreTable.length; i++) { - const { name, pow } = abbreTable[i]; - const base = Math.pow(10, pow); - - if (balance / base > 0) { - return `${Math.floor(((balance / base) * 100)) / 100} ${name}`; - } - } - - return balance; - } - const mappedInfo: Info = { network: loaderData.network ? { health: getNetworkStatus(), @@ -82,8 +53,7 @@ export default function InfoCards() { data: [ { name: 'Smart Contract', value: formatHash(loaderData.relayer.account.walletAddress) }, { name: 'Backlog', value: `${loaderData.relayer.backlog} Subnet Headers` }, - // TODO: explore cash format - { name: 'Remaining Balance', value: formatBalance(parseInt(loaderData.relayer.account.balance)) }, + { name: 'Remaining Balance', value: formatMoney(parseInt(loaderData.relayer.account.balance)) }, ] } : null, masterNodes: loaderData.masterNodes ? { diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index 770d2e1..f562257 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -44,7 +44,7 @@ export default function InfoList({ title, status, info }: InfoListProps) { ); } -interface InfoItemBaseProps { +export interface InfoItemBaseProps { name: string; value: number | string; } diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index f04f8ed..4d80840 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -3,11 +3,11 @@ import { useState } from 'react'; import Card from '@/components/card/Card'; import ConfirmationStatus from '@/components/confirmation-status/ConfirmationStatus'; -import InfoList from '@/components/info-list/InfoList'; +import InfoList, { InfoItemBaseProps } from '@/components/info-list/InfoList'; import Loader from '@/components/loader/Loader'; import SearchBar from '@/components/search-bar/SearchBar'; import SearchNotFound from '@/components/search-not-found/SearchNotFound'; -import { formatHash } from '@/utils/formatter'; +import { formatHash, formatMoney, formatTime } from '@/utils/formatter'; import type { Info } from '@/types/info'; import type { SearchResult } from '@/types/searchResult'; @@ -36,6 +36,62 @@ interface SearchResultProps { searchResult?: AxiosResponse<SearchResult>; } +function getMappedInfo(searchResult: SearchResult) { + const { parentChain, subnet, transaction } = searchResult; + + const mappedInfo: Info = {}; + + if (transaction) { + mappedInfo.transaction = { + data: [{ + name: 'From', + value: formatHash(transaction.from) + }, { + name: 'To', + value: formatHash(transaction.to) + }, { + name: 'Timestamp', + value: formatTime(transaction.timestamp) + }, { + name: 'Gas', + value: formatMoney(transaction.gas) + }] + }; + } + + if (parentChain) { + mappedInfo.parentChain = { + data: [{ + name: 'Height', + value: parentChain.blockHeight + }, { + name: 'Hash', + value: formatHash(parentChain.blockHash) + }, { + name: 'Proposer', + value: '' + }] + }; + } + + if (subnet) { + mappedInfo.subnetBlock = { + data: [{ + name: 'Height', + value: subnet.blockHeight + }, { + name: 'Hash', + value: formatHash(subnet.blockHash) + }, { + name: 'Proposer', + value: '' + }] + }; + } + + return mappedInfo; +} + function SearchResult({ searchText, searchResult }: SearchResultProps) { if (!searchText) { return <></>; @@ -49,28 +105,8 @@ function SearchResult({ searchText, searchResult }: SearchResultProps) { return <SearchNotFound status={searchResult.status} />; } - const { parentChain, subnet } = searchResult.data; - - const mappedInfo: Info = { - subnetBlock: { - data: [{ - name: 'Block height', - value: subnet.blockHeight - }, { - name: 'Block hash', - value: formatHash(subnet.blockHash) - }] - }, - parentChain: { - data: [{ - name: 'Block height', - value: parentChain.blockHeight - }, { - name: 'Block hash', - value: formatHash(parentChain.blockHash) - }] - } - }; + const { parentChain, subnet, inputType } = searchResult.data; + const mappedInfo: Info = getMappedInfo(searchResult.data); return ( <> @@ -78,21 +114,42 @@ function SearchResult({ searchText, searchResult }: SearchResultProps) { className='pt-8' subnetStatus={subnet.isConfirmed} parentChainStatus={parentChain.isConfirmed} + inputType={inputType} /> <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> - <Card className={mappedInfo.subnetBlock?.data ? '' : 'border-none px-0 py-0'}> - <InfoList - title='Subnet Block Info' - info={mappedInfo.subnetBlock?.data} - /> - </Card> - <Card className={mappedInfo.parentChain?.data ? '' : 'border-none px-0 py-0'}> - <InfoList - title='Checkpointing parent chain block' - info={mappedInfo.parentChain?.data} - /> - </Card> + <InfoListCard + title='Transaction Info' + info={mappedInfo.transaction?.data} + /> + <InfoListCard + title='Subnet Block Info' + info={mappedInfo.subnetBlock?.data} + /> + <InfoListCard + title='Checkpointing parent chain block' + info={mappedInfo.parentChain?.data} + /> </div> </> ); +} + +interface InfoListCardProps { + title: string; + info?: InfoItemBaseProps[]; +} + +function InfoListCard({ info }: InfoListCardProps) { + if (!info) { + return <></>; + } + + return ( + <Card className={info ? '' : 'border-none px-0 py-0'}> + <InfoList + title='Subnet Block Info' + info={info} + /> + </Card> + ); } \ No newline at end of file diff --git a/frontend/src/types/searchResult.d.ts b/frontend/src/types/searchResult.d.ts index e204f65..af51daf 100644 --- a/frontend/src/types/searchResult.d.ts +++ b/frontend/src/types/searchResult.d.ts @@ -1,13 +1,23 @@ export interface SearchResult { - inputType: string; + inputType: SearchResult.InputType; parentChain: SearchResult.Chain; subnet: SearchResult.Chain; + transaction: SearchResult.Transaction; } namespace SearchResult { - interface Chain { + export interface Chain { blockHash: string; blockHeight: string; isConfirmed: boolean; } + + export interface Transaction { + from: string; + to: string; + gas: number; + timestamp: number; + } + + export type InputType = 'BLOCK_HASH' | 'BLOCK_HEIGHT' | 'TRANSACTION_HASH'; } \ No newline at end of file diff --git a/frontend/src/utils/formatter.ts b/frontend/src/utils/formatter.ts index af51800..4523717 100644 --- a/frontend/src/utils/formatter.ts +++ b/frontend/src/utils/formatter.ts @@ -3,4 +3,43 @@ export function formatHash(hash: string): string { return `${hash.slice(0, 6)}...${hash.slice(-6)}`; } return ''; +} + +export function formatMoney(money: number) { + const abbreTable = [{ + name: 'Septillion', pow: 24, + }, { + name: 'Sextillion', pow: 21, + }, { + name: 'Quintillion', pow: 18, + }, { + name: 'Quadrillion', pow: 15, + }, { + name: 'Trillion', pow: 12, + }, { + name: 'B', pow: 9, + }, { + name: 'M', pow: 6, + }, { + name: 'K', pow: 3, + }]; + + for (let i = 0; i < abbreTable.length; i++) { + const { name, pow } = abbreTable[i]; + const base = Math.pow(10, pow); + + if ((money / base) > 1) { + return `${Math.floor(((money / base) * 100)) / 100} ${name}`; + } + } + + return money; +} + +export function formatTime(unixTimestamp: number) { + const date = new Date(unixTimestamp * 1000); + + const formattedDate = date.toLocaleString(); + + return formattedDate; } \ No newline at end of file From d2e124087a88c16b02ca027c9e3cfb6252e90207 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 14:28:17 +1000 Subject: [PATCH 66/87] Hide management page --- frontend/src/components/nav/Nav.tsx | 50 ++++++++++++++++------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index eeb3597..b89e5db 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { PropsWithChildren, useEffect, useState } from 'react'; import { useLoaderData, useLocation } from 'react-router-dom'; import CheckerImage from '@/components/images/CheckerImage'; @@ -49,20 +49,11 @@ export default function Nav(): JSX.Element { return ( <nav className='z-[100] sticky top-0 left-0 dark:bg-bg-dark-1000 bg-white grow shadow-grey flex flex-col justify-between rounded-b-xl'> <div> - <div className='flex items-center flex-col border-text-white dark:border-border-light relative'> + <BaseNavItems name={loaderData.name}> <button className='absolute left-0 top-0 p-6 text-3xl' onClick={closeNav} > x </button> - <div className='pt-12 font-bold text-[26px]'> - <Svg svgName={SvgNames.Logo} sizeClass='w-[80px]' /> - </div> - <div className='py-6 dark:text-sky-300'> - {loaderData.name} - </div> - </div> - <NavItem Image={HouseImage} text='Home' className='pt-2' page='home' /> - <NavItem Image={CheckerImage} text='Confirmation Checker' page='checker' /> - <NavItem Image={ManagementImage} text='Management' page='management' /> + </BaseNavItems> </div> </nav> ); @@ -71,19 +62,32 @@ export default function Nav(): JSX.Element { return ( <nav id='nav' className={`sticky top-0 dark:bg-bg-dark-1000 w-[246px] ${isDesktopL ? 'h-[1024px]' : 'h-[600px]'} max-h-screen shrink-0 shadow-grey flex flex-col justify-between`}> <div> - <div className='flex items-center flex-col border-text-white dark:border-border-light'> - <div className='pt-12 font-bold text-[26px]'> - <Svg svgName={SvgNames.Logo} sizeClass='w-[80px]' /> - </div> - <div className='py-6 dark:text-sky-300'> - {loaderData.name} - </div> - </div> - <NavItem Image={HouseImage} text='Home' className='pt-2' page='home' /> - <NavItem Image={CheckerImage} text='Confirmation Checker' page='checker' /> - <NavItem Image={ManagementImage} text='Management' page='management' /> + <BaseNavItems name={loaderData.name} /> </div> <ThemeSwitch /> </nav> ); } + +interface BaseNavItemsProps extends PropsWithChildren { + name: string; +} + +function BaseNavItems({ name, children }: BaseNavItemsProps) { + return ( + <> + <div className='flex items-center flex-col border-text-white dark:border-border-light'> + {children} + <div className='pt-12 font-bold text-[26px]'> + <Svg svgName={SvgNames.Logo} sizeClass='w-[80px]' /> + </div> + <div className='py-6 dark:text-sky-300'> + {name} + </div> + </div> + <NavItem Image={HouseImage} text='Home' className='pt-2' page='home' /> + <NavItem Image={CheckerImage} text='Confirmation Checker' page='checker' /> + {/* <NavItem Image={ManagementImage} text='Management' page='management' /> */} + </> + ); +} From 0e54e85598b8e2ca40ad4bd07aaace2cc6b59e2d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 14:33:44 +1000 Subject: [PATCH 67/87] Fix checker page card title --- frontend/src/pages/CheckerPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 4d80840..040824b 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -139,7 +139,7 @@ interface InfoListCardProps { info?: InfoItemBaseProps[]; } -function InfoListCard({ info }: InfoListCardProps) { +function InfoListCard({ title, info }: InfoListCardProps) { if (!info) { return <></>; } @@ -147,7 +147,7 @@ function InfoListCard({ info }: InfoListCardProps) { return ( <Card className={info ? '' : 'border-none px-0 py-0'}> <InfoList - title='Subnet Block Info' + title={title} info={info} /> </Card> From 8156be8ea69e48896a403a6e6ac51d90d3be7575 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 13 Jul 2023 14:39:21 +1000 Subject: [PATCH 68/87] Fix build error --- frontend/src/components/nav/Nav.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/nav/Nav.tsx b/frontend/src/components/nav/Nav.tsx index b89e5db..28f874a 100644 --- a/frontend/src/components/nav/Nav.tsx +++ b/frontend/src/components/nav/Nav.tsx @@ -3,7 +3,6 @@ import { useLoaderData, useLocation } from 'react-router-dom'; import CheckerImage from '@/components/images/CheckerImage'; import HouseImage from '@/components/images/HouseImage'; -import ManagementImage from '@/components/images/ManagementImage'; import Svg, { SvgNames } from '@/components/images/Svg'; import NavItem from '@/components/nav-item/NavItem'; import ThemeSwitch from '@/components/theme-switch/ThemeSwitch'; From b79b6da0ac3c384c9da67cf6f6e4d96cd48db2cc Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 14 Jul 2023 13:22:52 +1000 Subject: [PATCH 69/87] Handle api error --- frontend/src/components/Blocks.tsx | 12 +- .../src/components/blocks-info/BlocksInfo.tsx | 5 +- .../ConfirmationStatus.tsx | 10 +- .../src/components/error-state/ErrorState.tsx | 23 +++ .../error-state.module.scss} | 0 .../components/infinite-list/InfiniteList.tsx | 2 +- .../src/components/info-cards/InfoCards.tsx | 112 +++++++------ .../src/components/info-list/InfoList.tsx | 57 ++----- .../info-list/components/InfoListEmpty.tsx | 32 ++++ .../components/block-cards/BlockCards.tsx | 157 ++++++++++++++++++ .../components/info-list-empty.module.scss | 3 + frontend/src/main.tsx | 2 +- frontend/src/pages/CheckerPage.tsx | 16 +- frontend/src/pages/HomePage.tsx | 144 +--------------- frontend/src/types/checkerPageData.d.ts | 2 +- frontend/src/types/info.d.ts | 18 +- frontend/src/types/loaderData.d.ts | 22 +-- frontend/src/types/searchResult.d.ts | 8 +- 18 files changed, 343 insertions(+), 282 deletions(-) create mode 100644 frontend/src/components/error-state/ErrorState.tsx rename frontend/src/components/{info-list/info-list.module.scss => error-state/error-state.module.scss} (100%) create mode 100644 frontend/src/components/info-list/components/InfoListEmpty.tsx create mode 100644 frontend/src/components/info-list/components/block-cards/BlockCards.tsx create mode 100644 frontend/src/components/info-list/components/info-list-empty.module.scss diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 5c5604a..98f5b1b 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -10,13 +10,18 @@ export interface Block { } interface BlocksProps { - lastBlock: number; - lastConfirmedBlock: number; + name: string; blockNumber: number; blocks: Block[]; + lastBlock?: number; + lastConfirmedBlock?: number; } -export default function Blocks({ lastBlock, lastConfirmedBlock, blockNumber, blocks }: BlocksProps) { +export default function Blocks({ lastBlock, lastConfirmedBlock, blockNumber, blocks, name }: BlocksProps) { + if (!(lastBlock && lastConfirmedBlock && blocks)) { + return <h1 className='text-xl'>Failed to load blocks data</h1>; + } + {/* n * block-width + (n - 1) * spacing */ } const blockSize = 35 + 18; const allBlocksNotConfirmed = lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber; @@ -37,6 +42,7 @@ export default function Blocks({ lastBlock, lastConfirmedBlock, blockNumber, blo return ( <> + <h1 className='pb-4 text-xl font-medium'>{name}</h1> {/* Ex: 20 blocks + spacing = (35 + 18) * 20 - 18 = 1042px */} <div className='pt-[60px] h-[150px] overflow-hidden relative' style={{ width: `${BlockSizeWithGap * blockNumber - BlockGapSize}px` }}> <div className='flex relative'> diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index ff7efb1..d3e7258 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -3,6 +3,7 @@ import { } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import { cellWith } from '@/components/blocks-info/constants'; import { MasterNodeTitle } from '@/components/blocks-info/master-node-title/MasterNodeTitle'; +import ErrorState from '@/components/error-state/ErrorState'; import InfiniteList from '@/components/infinite-list/InfiniteList'; import Title from '@/components/title/Title'; @@ -10,14 +11,14 @@ import Title from '@/components/title/Title'; interface BlocksInfoProps { title: string; - data: BlocksInfoItem[]; + data?: BlocksInfoItem[]; fetchMoreData?: () => void; enableInfinite?: boolean; } export default function BlocksInfo({ title, data, fetchMoreData }: BlocksInfoProps) { if (!data || !data.length) { - return <></>; + return <ErrorState title={title} />; } return ( diff --git a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx index 17a3f88..06d3358 100644 --- a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx +++ b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx @@ -3,9 +3,9 @@ import { twMerge } from 'tailwind-merge'; import { SearchResult } from '@/types/searchResult'; interface ConfirmationStatusProps { - subnetStatus: boolean; - parentChainStatus: boolean; - inputType: SearchResult.InputType; + subnetStatus?: boolean; + parentChainStatus?: boolean; + inputType?: SearchResult.InputType; className?: string; } @@ -22,6 +22,10 @@ export default function ConfirmationStatus({ className, inputType, subnetStatus, } } + if (!(inputType && subnetStatus && parentChainStatus)) { + return <></>; + } + return ( <div className={twMerge(className, '')}> <ConfirmationItem name='Result Type' text={getInputTypeText(inputType)} /> diff --git a/frontend/src/components/error-state/ErrorState.tsx b/frontend/src/components/error-state/ErrorState.tsx new file mode 100644 index 0000000..7544f2e --- /dev/null +++ b/frontend/src/components/error-state/ErrorState.tsx @@ -0,0 +1,23 @@ +import { useContext } from 'react'; + +import Svg, { SvgNames } from '@/components/images/Svg'; +import { ThemeContext } from '@/contexts/ThemeContext'; + +import styles from './error-state.module.scss'; + +interface InfoListErrorProps { + title: string; +} + +export default function ErrorState({ title }: InfoListErrorProps) { + const { theme } = useContext(ThemeContext); + const svgName = theme === 'dark' ? SvgNames.InfoDark : SvgNames.Info; + + return ( + <div className='h-[319px] bg-white dark:bg-bg-dark-800 rounded-b-3xl flex flex-col items-center justify-center'> + <Svg svgName={svgName} className={`rounded-full ${styles.svgFilter}`} sizeClass='w-[75px] h-[75px]' /> + <p className='dark:text-text-white text-text-dark text-xl pt-9'>Failed to load {title.toLowerCase()}</p> + </div> + ); +} + diff --git a/frontend/src/components/info-list/info-list.module.scss b/frontend/src/components/error-state/error-state.module.scss similarity index 100% rename from frontend/src/components/info-list/info-list.module.scss rename to frontend/src/components/error-state/error-state.module.scss diff --git a/frontend/src/components/infinite-list/InfiniteList.tsx b/frontend/src/components/infinite-list/InfiniteList.tsx index c9affcf..ebc7501 100644 --- a/frontend/src/components/infinite-list/InfiniteList.tsx +++ b/frontend/src/components/infinite-list/InfiniteList.tsx @@ -31,7 +31,7 @@ export default function InfiniteList({ fetchData, children }: InfiniteListProps) observer.unobserve(currentTarget); } }; - }, [observerTarget]); + }, [fetchData, observerTarget]); return ( <> diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 52ef2cc..1a8ee7e 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; import { - BlocksInfoItem, MasterNode + BlocksInfoItem, MasterNode } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; @@ -14,10 +14,10 @@ import { formatHash, formatMoney } from '@/utils/formatter'; export default function InfoCards() { const loaderData = useLoaderData() as HomeLoaderData; - const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getInitRecentBlocks()); + const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[] | undefined>(getInitRecentBlocks()); function getNetworkStatus(): InfoListHealth { - if (loaderData.network.health.status === 'UP') { + if (loaderData.network?.health.status === 'UP') { return 'Normal'; } @@ -25,45 +25,21 @@ export default function InfoCards() { } function getRelayerStatus(): InfoListHealth { - if (loaderData.relayer.health.status === 'UP') { + if (loaderData.relayer?.health.status === 'UP') { return 'Normal'; } return 'Abnormal'; } - function getInitRecentBlocks(): BlocksInfoItem[] { - return loaderData.blocks.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ + function getInitRecentBlocks(): BlocksInfoItem[] | undefined { + return loaderData.blocks?.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ type: 'recent-block', ...block })); } - const mappedInfo: Info = { - network: loaderData.network ? { - health: getNetworkStatus(), - data: [ - { name: 'Block Time', value: `${loaderData.network.subnet.block.averageBlockTime}s` }, - { name: 'TX Throughput', value: `${Math.round(loaderData.network.subnet.block.txThroughput * 100) / 100} txs/s` }, - { name: 'Checkpointed to', value: loaderData.network.parentChain.name }, - ] - } : null, - relayer: loaderData.relayer ? { - health: getRelayerStatus(), - data: [ - { name: 'Smart Contract', value: formatHash(loaderData.relayer.account.walletAddress) }, - { name: 'Backlog', value: `${loaderData.relayer.backlog} Subnet Headers` }, - { name: 'Remaining Balance', value: formatMoney(parseInt(loaderData.relayer.account.balance)) }, - ] - } : null, - masterNodes: loaderData.masterNodes ? { - data: [ - { name: 'Current committee size', value: loaderData.masterNodes?.summary?.committee }, - { name: 'Activity(active / inactive)', value: `${loaderData.masterNodes?.summary?.activeNodes} / ${loaderData.masterNodes.summary.committee - loaderData.masterNodes?.summary?.activeNodes}` }, - { name: 'Number of standby nodes', value: loaderData.masterNodes?.summary?.inActiveNodes }, - ], - } : null, - }; + const mappedInfo: Info = getMappedInfo(loaderData, getNetworkStatus, getRelayerStatus); const masterNodes = loaderData.masterNodes?.nodes?.map<MasterNode>((v, i: number) => ({ ...v, @@ -81,7 +57,7 @@ export default function InfoCards() { const data: MasterNode[] = []; setRecentBlocks(recentBlocks => { - return [...recentBlocks, ...data]; + return [...recentBlocks ?? [], ...data]; }); }; @@ -89,32 +65,22 @@ export default function InfoCards() { <> <div className='grid lg:grid-cols-2 llg:grid-cols-3 gap-6'> <Card className='max-w-[400px]'> - {mappedInfo.network ? ( - <InfoList - title='Network Info' - status={mappedInfo.network.health} - info={mappedInfo.network.data} - />) : ( - <div>Error state</div> - )} + <InfoList + title='Network Info' + info={mappedInfo.network} + /> </Card> <Card className='max-w-[400px]'> - {mappedInfo.relayer ? ( - <InfoList - title='Relayer Info' - status={mappedInfo.relayer.health} - info={mappedInfo.relayer.data} - /> - ) : (<>Error state</>)} + <InfoList + title='Relayer Info' + info={mappedInfo.relayer} + /> </Card> <Card className='max-w-[400px]'> - {mappedInfo.masterNodes ? ( - <InfoList - title='Master Nodes Info' - status={mappedInfo.masterNodes.health} - info={mappedInfo.masterNodes.data} - /> - ) : (<>Error state</>)} + <InfoList + title='Master Nodes Info' + info={mappedInfo.masterNodes} + /> </Card> </div> @@ -122,10 +88,46 @@ export default function InfoCards() { <Card className='max-w-[565px]'> <BlocksInfo title='Recent Blocks' data={recentBlocks} fetchMoreData={fetchMoreRecentBlocks} enableInfinite /> </Card> - {masterNodes && <Card className='max-w-[565px]'> + {<Card className='max-w-[565px]'> <BlocksInfo title='Master Nodes' data={masterNodes} /> </Card>} </div> </> ); } +function getMappedInfo(loaderData: HomeLoaderData, getNetworkStatus: () => InfoListHealth, getRelayerStatus: () => InfoListHealth): Info { + const info: Info = {}; + + if (loaderData.network) { + info.network = { + health: getNetworkStatus(), + data: [ + { name: 'Block Time', value: `${loaderData.network.subnet.block.averageBlockTime}s` }, + { name: 'TX Throughput', value: `${Math.round(loaderData.network.subnet.block.txThroughput * 100) / 100} txs/s` }, + { name: 'Checkpointed to', value: loaderData.network.parentChain.name }, + ] + }; + } + if (loaderData.relayer) { + info.relayer = { + health: getRelayerStatus(), + data: [ + { name: 'Smart Contract', value: formatHash(loaderData.relayer.account.walletAddress) }, + { name: 'Backlog', value: `${loaderData.relayer.backlog} Subnet Headers` }, + { name: 'Remaining Balance', value: formatMoney(parseInt(loaderData.relayer.account.balance)) }, + ] + }; + } + if (loaderData.masterNodes) { + info.masterNodes = { + data: [ + { name: 'Current committee size', value: loaderData.masterNodes?.summary?.committee }, + { name: 'Activity(active / inactive)', value: `${loaderData.masterNodes?.summary?.activeNodes} / ${loaderData.masterNodes.summary.committee - loaderData.masterNodes?.summary?.activeNodes}` }, + { name: 'Number of standby nodes', value: loaderData.masterNodes?.summary?.inActiveNodes }, + ], + }; + } + + return info; +} + diff --git a/frontend/src/components/info-list/InfoList.tsx b/frontend/src/components/info-list/InfoList.tsx index f562257..0d58523 100644 --- a/frontend/src/components/info-list/InfoList.tsx +++ b/frontend/src/components/info-list/InfoList.tsx @@ -1,25 +1,26 @@ -import { useContext } from 'react'; - -import { Dot } from '@/components/dot/Dot'; +import ErrorState from '@/components/error-state/ErrorState'; import Svg, { SvgNames } from '@/components/images/Svg'; +import InfoListEmpty from '@/components/info-list/components/InfoListEmpty'; import Title from '@/components/title/Title'; -import { ThemeContext } from '@/contexts/ThemeContext'; - -import styles from './info-list.module.scss'; -import type { InfoListHealth } from '@/types/info'; +import type { InfoItem } from '@/types/info'; interface InfoListProps { title: string; - info?: InfoItemBaseProps[]; - status?: InfoListHealth; + info?: InfoItem; } -export default function InfoList({ title, status, info }: InfoListProps) { +export default function InfoList({ title, info }: InfoListProps) { if (!info) { + return <ErrorState title={title} />; + } + + if (!info.data) { return <InfoListEmpty title={title} />; } + const { health: status, data } = info; + return ( <> <div className='flex justify-between items-center pb-6'> @@ -37,19 +38,14 @@ export default function InfoList({ title, status, info }: InfoListProps) { </div> )} </div> - {info.map((item, index) => { + {data.map((item, index) => { return <InfoItem key={index} {...item} isFirst={index === 0} />; })} </> ); } -export interface InfoItemBaseProps { - name: string; - value: number | string; -} - -interface InfoItemProps extends InfoItemBaseProps { +interface InfoItemProps extends InfoItem.Data { isFirst?: boolean; } @@ -63,29 +59,4 @@ function InfoItem({ name, value, isFirst }: InfoItemProps) { <div className='font-bold text-sm leading-[120%]'>{value}</div> </div> ); -} - -interface InfoListEmptyProps { - title: string; -} - -function InfoListEmpty({ title }: InfoListEmptyProps) { - const { theme } = useContext(ThemeContext); - const svgName = theme === 'dark' ? SvgNames.InfoDark : SvgNames.Info; - - return ( - <> - <div className='flex items-center pl-6 h-[80px] rounded-t-3xl rounded-b bg-text-dark-100 dark:bg-bg-dark-700'> - <Dot className='bg-text-dark-300 dark:bg-text-dark-600' /> - <Dot className='bg-text-dark-300 dark:bg-text-dark-600 ml-3' /> - <Dot className='bg-text-dark-300 dark:bg-text-dark-600 ml-3' /> - </div> - <div className='h-[319px] bg-white dark:bg-bg-dark-800 rounded-b-3xl flex flex-col items-center justify-center'> - - <Svg svgName={svgName} className={`rounded-full ${styles.svgFilter}`} sizeClass='w-[75px] h-[75px]' /> - <p className='dark:text-text-white text-text-dark text-xl pt-9'>No {title.toLowerCase()}</p> - </div> - </> - ); -} - +} \ No newline at end of file diff --git a/frontend/src/components/info-list/components/InfoListEmpty.tsx b/frontend/src/components/info-list/components/InfoListEmpty.tsx new file mode 100644 index 0000000..0fa7355 --- /dev/null +++ b/frontend/src/components/info-list/components/InfoListEmpty.tsx @@ -0,0 +1,32 @@ +import { useContext } from 'react'; + +import { Dot } from '@/components/dot/Dot'; +import Svg, { SvgNames } from '@/components/images/Svg'; +import { ThemeContext } from '@/contexts/ThemeContext'; + +import styles from './info-list-empty.module.scss'; + +interface InfoListEmptyProps { + title: string; +} + +export default function InfoListEmpty({ title }: InfoListEmptyProps) { + const { theme } = useContext(ThemeContext); + const svgName = theme === 'dark' ? SvgNames.InfoDark : SvgNames.Info; + + return ( + <> + <div className='flex items-center pl-6 h-[80px] rounded-t-3xl rounded-b bg-text-dark-100 dark:bg-bg-dark-700'> + <Dot className='bg-text-dark-300 dark:bg-text-dark-600' /> + <Dot className='bg-text-dark-300 dark:bg-text-dark-600 ml-3' /> + <Dot className='bg-text-dark-300 dark:bg-text-dark-600 ml-3' /> + </div> + <div className='h-[319px] bg-white dark:bg-bg-dark-800 rounded-b-3xl flex flex-col items-center justify-center'> + + <Svg svgName={svgName} className={`rounded-full ${styles.svgFilter}`} sizeClass='w-[75px] h-[75px]' /> + <p className='dark:text-text-white text-text-dark text-xl pt-9'>No {title.toLowerCase()}</p> + </div> + </> + ); +} + diff --git a/frontend/src/components/info-list/components/block-cards/BlockCards.tsx b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx new file mode 100644 index 0000000..70558a5 --- /dev/null +++ b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx @@ -0,0 +1,157 @@ +import axios from 'axios'; +import { useContext, useEffect, useState } from 'react'; +import { useLoaderData } from 'react-router-dom'; + +import Blocks, { Block } from '@/components/Blocks'; +import Card from '@/components/card/Card'; +import InfoList from '@/components/info-list/InfoList'; +import { + BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, + StandardScreenBlockNumber, WideScreenBlockNumber +} from '@/constants/config'; +import { baseUrl } from '@/constants/urls'; +import { TimeContext } from '@/contexts/TimeContext'; +import { useIsTablet, useWindowWidth } from '@/hooks/useMediaQuery'; +import { Info } from '@/types/info'; +import { HomeLoaderData } from '@/types/loaderData'; + +function getBlocks(lastBlock: number | undefined, lastConfirmedBlock: number | undefined, blockNumber: number) { + if (!lastBlock || !lastConfirmedBlock) { + return []; + } + + const blocks = []; + // To allow animation when the furtherest left block move out + const allBlockNumber = blockNumber + FakedConfirmedBlockNumber; + const unconfirmedCount = lastBlock - lastConfirmedBlock; + const confirmedCount = allBlockNumber - unconfirmedCount > 0 ? allBlockNumber - unconfirmedCount : 0; + const firstBlockHeight = lastBlock - allBlockNumber + 1; + + if (lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber) { + // unconfirmed blocks + for (let i = 0; i < blockNumber + FakedConfirmedBlockNumber + FakedNotConfirmedBlockNumber; i++) { + blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); + } + + return blocks; + } + + // confirmed blocks + for (let i = 0; i < confirmedCount; i++) { + blocks.push({ number: firstBlockHeight + i, confirmed: true }); + } + + // unconfirmed blocks + for (let i = 0; i < unconfirmedCount + FakedNotConfirmedBlockNumber; i++) { + blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); + } + + return blocks; +} + +function getBlockNumber(windowWidth: number) { + if (windowWidth >= 1440) { + return WideScreenBlockNumber; + } + + if (windowWidth < 1024) { + const diff = 1024 - windowWidth; + return StandardScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); + } + + const diff = 1440 - windowWidth; + return WideScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); +} + +export default function BlockCards() { + const loaderData = useLoaderData() as HomeLoaderData; + const windowWidth = useWindowWidth(); + const isTablet = useIsTablet(); + + const blockNumber = getBlockNumber(windowWidth); + + const [lastSubnetBlock, setLastSubnetBlock] = useState(loaderData.blocks?.subnet.latestMinedBlock.number); + const [lastParentBlock, setLastParentBlock] = useState(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number); + const [lastSubnetConfirmedBlock, setLastSubnetConfirmedBlock] = useState(loaderData.blocks?.subnet.latestCommittedBlock.number); + const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number); + const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.subnet.latestMinedBlock.number, loaderData.blocks?.subnet.latestCommittedBlock.number, blockNumber)); + const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); + const { currentUnixTime } = useContext(TimeContext); + + useEffect(() => { + async function getData() { + const { data: { subnet, checkpoint } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); + setLastSubnetBlock(subnet.latestMinedBlock.number); + setLastSubnetConfirmedBlock(subnet.latestCommittedBlock.number); + setLastParentBlock(checkpoint.latestSubmittedSubnetBlock.number); + setLastParentConfirmedBlock(checkpoint.latestCommittedSubnetBlock.number); + + const blocks = getBlocks(subnet.latestMinedBlock.number, subnet.latestCommittedBlock.number, blockNumber); + const parentBlocks = getBlocks(checkpoint.latestSubmittedSubnetBlock.number, checkpoint.latestCommittedSubnetBlock.number, blockNumber); + setBlocks(blocks); + setParentBlocks(parentBlocks); + } + + getData(); + }, [blockNumber, currentUnixTime]); + + const mappedInfo: Info = { + subnet: { + data: [{ + name: 'Last committed block number', value: lastSubnetConfirmedBlock + }, { + name: 'Last mined block number', value: lastSubnetBlock + }] + }, + parentChain: { + data: [{ + name: 'Last committed block number', value: lastParentConfirmedBlock + }, { + name: 'Last mined block number', value: lastSubnetBlock + }] + } + }; + + return ( + <> + { + isTablet ? ( + <Card> + <Blocks + lastBlock={lastSubnetBlock} + lastConfirmedBlock={lastSubnetConfirmedBlock} + blockNumber={blockNumber} + blocks={blocks} + name='subnet blockchain' + /> + </Card >) : ( + <Card className='max-w-[400px]'> + <InfoList + title='subnet blockchain' + info={mappedInfo.parentChain} + /> + </Card> + ) + } + { + isTablet ? ( + <Card> + <Blocks + lastBlock={lastParentBlock} + lastConfirmedBlock={lastParentConfirmedBlock} + blockNumber={blockNumber} + blocks={parentBlocks} + name='checkpoints at the parent chain' + /> + </Card>) : ( + <Card className='max-w-[400px]'> + <InfoList + title='copy at the parent chain' + info={mappedInfo.parentChain} + /> + </Card> + ) + } + </> + ); +} \ No newline at end of file diff --git a/frontend/src/components/info-list/components/info-list-empty.module.scss b/frontend/src/components/info-list/components/info-list-empty.module.scss new file mode 100644 index 0000000..f340903 --- /dev/null +++ b/frontend/src/components/info-list/components/info-list-empty.module.scss @@ -0,0 +1,3 @@ +.svgFilter { + filter: drop-shadow(4px 7px 24px rgba(0, 0, 0, 0.24)); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 1cd06b9..e558132 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -43,7 +43,7 @@ export async function homeLoader() { const data = apiResponses.map(response => { if (response.status === 'rejected') { console.error(response.reason); - return null; + return undefined; } return response.value.data; diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 040824b..721bcda 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -3,13 +3,13 @@ import { useState } from 'react'; import Card from '@/components/card/Card'; import ConfirmationStatus from '@/components/confirmation-status/ConfirmationStatus'; -import InfoList, { InfoItemBaseProps } from '@/components/info-list/InfoList'; +import InfoList from '@/components/info-list/InfoList'; import Loader from '@/components/loader/Loader'; import SearchBar from '@/components/search-bar/SearchBar'; import SearchNotFound from '@/components/search-not-found/SearchNotFound'; import { formatHash, formatMoney, formatTime } from '@/utils/formatter'; -import type { Info } from '@/types/info'; +import type { Info, InfoItem } from '@/types/info'; import type { SearchResult } from '@/types/searchResult'; export default function CheckerPage() { @@ -112,22 +112,22 @@ function SearchResult({ searchText, searchResult }: SearchResultProps) { <> <ConfirmationStatus className='pt-8' - subnetStatus={subnet.isConfirmed} - parentChainStatus={parentChain.isConfirmed} + subnetStatus={subnet?.isConfirmed} + parentChainStatus={parentChain?.isConfirmed} inputType={inputType} /> <div className='pt-8 grid grid-cols-2 llg:grid-cols-3 gap-6'> <InfoListCard title='Transaction Info' - info={mappedInfo.transaction?.data} + info={mappedInfo.transaction} /> <InfoListCard title='Subnet Block Info' - info={mappedInfo.subnetBlock?.data} + info={mappedInfo.subnetBlock} /> <InfoListCard title='Checkpointing parent chain block' - info={mappedInfo.parentChain?.data} + info={mappedInfo.parentChain} /> </div> </> @@ -136,7 +136,7 @@ function SearchResult({ searchText, searchResult }: SearchResultProps) { interface InfoListCardProps { title: string; - info?: InfoItemBaseProps[]; + info?: InfoItem; } function InfoListCard({ title, info }: InfoListCardProps) { diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 595e698..36a917a 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,151 +1,11 @@ -import axios from 'axios'; -import { useContext, useEffect, useState } from 'react'; -import { useLoaderData } from 'react-router-dom'; - -import Blocks, { Block } from '@/components/Blocks'; -import Card from '@/components/card/Card'; import InfoCards from '@/components/info-cards/InfoCards'; -import InfoList from '@/components/info-list/InfoList'; -import { - BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, - StandardScreenBlockNumber, WideScreenBlockNumber -} from '@/constants/config'; -import { baseUrl } from '@/constants/urls'; -import { TimeContext } from '@/contexts/TimeContext'; -import { useIsTablet, useWindowWidth } from '@/hooks/useMediaQuery'; - -import type { HomeLoaderData } from '@/types/loaderData'; - -function getBlocks(lastBlock: number, lastConfirmedBlock: number, blockNumber: number) { - const blocks = []; - // To allow animation when the furtherest left block move out - const allBlockNumber = blockNumber + FakedConfirmedBlockNumber; - const unconfirmedCount = lastBlock - lastConfirmedBlock; - const confirmedCount = allBlockNumber - unconfirmedCount > 0 ? allBlockNumber - unconfirmedCount : 0; - const firstBlockHeight = lastBlock - allBlockNumber + 1; - - if (lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber) { - // unconfirmed blocks - for (let i = 0; i < blockNumber + FakedConfirmedBlockNumber + FakedNotConfirmedBlockNumber; i++) { - blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); - } - - return blocks; - } - - // confirmed blocks - for (let i = 0; i < confirmedCount; i++) { - blocks.push({ number: firstBlockHeight + i, confirmed: true }); - } - - // unconfirmed blocks - for (let i = 0; i < unconfirmedCount + FakedNotConfirmedBlockNumber; i++) { - blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); - } - - return blocks; -} - -function getBlockNumber(windowWidth: number) { - if (windowWidth >= 1440) { - return WideScreenBlockNumber; - } - - if (windowWidth < 1024) { - const diff = 1024 - windowWidth; - return StandardScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); - } - - const diff = 1440 - windowWidth; - return WideScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); -} +import BlockCards from '@/components/info-list/components/block-cards/BlockCards'; export default function HomePage() { - const isTablet = useIsTablet(); - const windowWidth = useWindowWidth(); - - const blockNumber = getBlockNumber(windowWidth); - const loaderData = useLoaderData() as HomeLoaderData; - - const [lastSubnetBlock, setLastSubnetBlock] = useState(loaderData.blocks.subnet.latestMinedBlock.number); - const [lastParentBlock, setLastParentBlock] = useState(loaderData.blocks.checkpoint.latestSubmittedSubnetBlock.number); - const [lastSubnetConfirmedBlock, setLastSubnetConfirmedBlock] = useState(loaderData.blocks.subnet.latestCommittedBlock.number); - const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks.checkpoint.latestCommittedSubnetBlock.number); - const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.subnet.latestMinedBlock.number, loaderData.blocks.subnet.latestCommittedBlock.number, blockNumber)); - const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); - const { currentUnixTime } = useContext(TimeContext); - - useEffect(() => { - async function getData() { - const { data: { subnet, checkpoint } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); - setLastSubnetBlock(subnet.latestMinedBlock.number); - setLastSubnetConfirmedBlock(subnet.latestCommittedBlock.number); - setLastParentBlock(checkpoint.latestSubmittedSubnetBlock.number); - setLastParentConfirmedBlock(checkpoint.latestCommittedSubnetBlock.number); - - const blocks = getBlocks(subnet.latestMinedBlock.number, subnet.latestCommittedBlock.number, blockNumber); - const parentBlocks = getBlocks(checkpoint.latestSubmittedSubnetBlock.number, checkpoint.latestCommittedSubnetBlock.number, blockNumber); - setBlocks(blocks); - setParentBlocks(parentBlocks); - } - - getData(); - }, [blockNumber, currentUnixTime]); - - const mappedInfo = { - subnet: { - data: [{ - name: 'Last committed block number', value: lastSubnetConfirmedBlock - }, { - name: 'Last mined block number', value: lastSubnetBlock - }] - }, - parentChain: { - data: [{ - name: 'Last committed block number', value: lastParentConfirmedBlock - }, { - name: 'Last mined block number', value: lastSubnetBlock - }] - } - }; return ( <div className='grid gap-6 grid-col-1'> - {isTablet ? ( - <Card> - <h1 className='pb-4 text-xl font-medium'>subnet blockchain</h1> - <Blocks - lastBlock={lastSubnetBlock} - lastConfirmedBlock={lastSubnetConfirmedBlock} - blockNumber={blockNumber} - blocks={blocks} - /> - </Card>) : ( - <Card className='max-w-[400px]'> - <InfoList - title='subnet blockchain' - info={mappedInfo.parentChain.data} - /> - </Card> - )} - {isTablet ? ( - <Card> - <h1 className='pb-4 text-xl font-medium'>checkpoints at the parent chain</h1> - <Blocks - lastBlock={lastParentBlock} - lastConfirmedBlock={lastParentConfirmedBlock} - blockNumber={blockNumber} - blocks={parentBlocks} - /> - </Card>) : ( - <Card className='max-w-[400px]'> - <InfoList - title='copy at the parent chain' - info={mappedInfo.parentChain.data} - /> - </Card> - )} - + <BlockCards /> <InfoCards /> </div> ); diff --git a/frontend/src/types/checkerPageData.d.ts b/frontend/src/types/checkerPageData.d.ts index 7a83781..d1d9a83 100644 --- a/frontend/src/types/checkerPageData.d.ts +++ b/frontend/src/types/checkerPageData.d.ts @@ -4,7 +4,7 @@ export interface CheckerPageData { } namespace CheckerPageData { - interface Data { + export interface Data { value: string; } } \ No newline at end of file diff --git a/frontend/src/types/info.d.ts b/frontend/src/types/info.d.ts index 75154df..63a37a0 100644 --- a/frontend/src/types/info.d.ts +++ b/frontend/src/types/info.d.ts @@ -1,15 +1,17 @@ -export interface Info { - [x: string]: { - data?: Info.Data[]; - health?: InfoListHealth; - } | null; +export type Info = Partial<Record<InfoNames, InfoItem | undefined>>; + +interface InfoItem { + data?: InfoItem.Data[]; + health?: InfoListHealth; } -namespace Info { - interface Data { +namespace InfoItem { + export interface Data { name: string; - value: string | number; + value?: string | number; } } export type InfoListHealth = 'Normal' | 'Abnormal'; + +export type InfoNames = 'masterNodes' | 'relayer' | 'network' | 'parentChain' | 'transaction' | 'subnetBlock' | 'subnet'; \ No newline at end of file diff --git a/frontend/src/types/loaderData.d.ts b/frontend/src/types/loaderData.d.ts index 0a724e9..bff8d35 100644 --- a/frontend/src/types/loaderData.d.ts +++ b/frontend/src/types/loaderData.d.ts @@ -3,14 +3,14 @@ export interface AppLoaderData { } export interface HomeLoaderData { - masterNodes: HomeLoaderData.MasterNodes; - blocks: HomeLoaderData.Blocks; - network: HomeLoaderData.Network; - relayer: HomeLoaderData.Relayer; + masterNodes?: HomeLoaderData.MasterNodes; + blocks?: HomeLoaderData.Blocks; + network?: HomeLoaderData.Network; + relayer?: HomeLoaderData.Relayer; } export namespace HomeLoaderData { - interface MasterNodes { + export interface MasterNodes { summary: { committee: number; activeNodes: number; @@ -22,13 +22,13 @@ export namespace HomeLoaderData { } namespace MasterNodes { - interface Node { + export interface Node { address: string; role: 'CANDIDATE', 'MASTERNODE', 'PENALTY'; } } - interface Blocks { + export interface Blocks { /** A list of recently mined blocks. Sorted by block number */ blocks: Blocks.Block[]; @@ -54,7 +54,7 @@ export namespace HomeLoaderData { number: number; } - interface Block extends BaseBlock { + export interface Block extends BaseBlock { /** The subnet block's parentHash */ parentHash: string; /** The masternode address who mined this block */ @@ -68,7 +68,7 @@ export namespace HomeLoaderData { } } - interface Relayer { + export interface Relayer { /** The admin/super account information */ account: { /** The super/admin account remaining balance in XDC */ @@ -76,7 +76,7 @@ export namespace HomeLoaderData { /** The wallet address of the account */ walletAddress: string; }; - /** The current gap between audited block in smartcontract (parent chain) and latest minded block in subnet */ + /** The current gap between audited block in smart contract (parent chain) and latest minded block in subnet */ backlog: number; contractAddress: string; health: { @@ -88,7 +88,7 @@ export namespace HomeLoaderData { averageTXfee: number; } - interface Network { + export interface Network { subnet: { name: string; /** block metadata, such as mining frequency */ diff --git a/frontend/src/types/searchResult.d.ts b/frontend/src/types/searchResult.d.ts index af51daf..aef0bcd 100644 --- a/frontend/src/types/searchResult.d.ts +++ b/frontend/src/types/searchResult.d.ts @@ -1,8 +1,8 @@ export interface SearchResult { - inputType: SearchResult.InputType; - parentChain: SearchResult.Chain; - subnet: SearchResult.Chain; - transaction: SearchResult.Transaction; + inputType?: SearchResult.InputType; + parentChain?: SearchResult.Chain; + subnet?: SearchResult.Chain; + transaction?: SearchResult.Transaction; } namespace SearchResult { From 45adf7213f22c6578b67cf308b2e7d787ace808d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 14 Jul 2023 13:49:44 +1000 Subject: [PATCH 70/87] Click hash will go to checker page --- .../blocks-info-item/BlocksInfoItem.tsx | 18 ++++-------------- frontend/src/main.tsx | 2 +- frontend/src/pages/CheckerPage.tsx | 4 +++- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index e5d6f32..ce6c349 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -1,9 +1,8 @@ import { PropsWithChildren, useContext } from 'react'; +import { Link } from 'react-router-dom'; import { cellWith } from '@/components/blocks-info/constants'; -import Svg, { - InlineSvg, InlineSvgColours, InlineSvgNames, SvgNames -} from '@/components/images/Svg'; +import Svg, { SvgNames } from '@/components/images/Svg'; import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; import { formatHash } from '@/utils/formatter'; @@ -34,7 +33,6 @@ type MasterNodeRoles = 'MASTERNODE' | 'CANDIDATE' | 'PENALTY'; export function BlocksInfoItem(data: BlocksInfoItemProps) { const { currentUnixTime } = useContext(TimeContext); - const { theme } = useContext(ThemeContext); function getTimeDiff(timestamp: number): string { const timeDiff = Math.floor(currentUnixTime - timestamp); @@ -47,11 +45,6 @@ export function BlocksInfoItem(data: BlocksInfoItemProps) { return '>1hr'; } - function copyToClipboard(hash: string) { - window.navigator.clipboard.writeText(hash); - // showAlert('Copied!'); - } - if (data.type === 'recent-block') { return ( @@ -59,12 +52,9 @@ export function BlocksInfoItem(data: BlocksInfoItemProps) { <BlockCell className={cellWith.recentBlocks.height}>{data.number}</BlockCell> <BlockCell className={cellWith.recentBlocks.hash}> <div className=''> - <button onClick={() => copyToClipboard(data.hash)} className='shrink-0 flex justify-between group'> + <Link to={`/checker/${data.hash}`}> {formatHash(data.hash)} - <div className='hidden group-hover:block'> - <InlineSvg svgName={InlineSvgNames.Copy} colour={theme === 'light' ? InlineSvgColours.Primary : InlineSvgColours.White} /> - </div> - </button> + </Link> </div> </BlockCell> <BlockCell className={cellWith.recentBlocks.proposedBy}>{formatHash(data.miner)}</BlockCell> diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index e558132..5399f3d 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -68,7 +68,7 @@ const router = createBrowserRouter([ loader: homeLoader, element: <HomePage />, }, { - path: 'checker', + path: 'checker/:id', element: <CheckerPage />, }, { path: 'management', diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 721bcda..3a2d138 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -1,5 +1,6 @@ import { AxiosResponse } from 'axios'; import { useState } from 'react'; +import { useLocation, useMatch } from 'react-router-dom'; import Card from '@/components/card/Card'; import ConfirmationStatus from '@/components/confirmation-status/ConfirmationStatus'; @@ -13,7 +14,8 @@ import type { Info, InfoItem } from '@/types/info'; import type { SearchResult } from '@/types/searchResult'; export default function CheckerPage() { - const [searchText, setSearchText] = useState(''); + const match = useMatch('/checker/:id'); + const [searchText, setSearchText] = useState(match?.params.id ?? ''); const [searchResult, setSearchResult] = useState<AxiosResponse<SearchResult>>(); return ( From 45cb518d9e5d0b20f2c9b86be705d6e0dce7d18d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 14 Jul 2023 13:51:32 +1000 Subject: [PATCH 71/87] Add proposer to checker result --- frontend/src/pages/CheckerPage.tsx | 2 +- frontend/src/types/searchResult.d.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 3a2d138..4acff1f 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -71,7 +71,7 @@ function getMappedInfo(searchResult: SearchResult) { value: formatHash(parentChain.blockHash) }, { name: 'Proposer', - value: '' + value: formatHash(parentChain.proposer) }] }; } diff --git a/frontend/src/types/searchResult.d.ts b/frontend/src/types/searchResult.d.ts index aef0bcd..928bb9f 100644 --- a/frontend/src/types/searchResult.d.ts +++ b/frontend/src/types/searchResult.d.ts @@ -10,6 +10,7 @@ namespace SearchResult { blockHash: string; blockHeight: string; isConfirmed: boolean; + proposer: string; } export interface Transaction { From 75bc4a49320aed88b51c2c618a1dbfe223276cc9 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 14 Jul 2023 13:52:23 +1000 Subject: [PATCH 72/87] Fix build error --- .../components/blocks-info/blocks-info-item/BlocksInfoItem.tsx | 1 - frontend/src/pages/CheckerPage.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index ce6c349..5a6a770 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'; import { cellWith } from '@/components/blocks-info/constants'; import Svg, { SvgNames } from '@/components/images/Svg'; -import { ThemeContext } from '@/contexts/ThemeContext'; import { TimeContext } from '@/contexts/TimeContext'; import { formatHash } from '@/utils/formatter'; diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index 4acff1f..b17184e 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -1,6 +1,6 @@ import { AxiosResponse } from 'axios'; import { useState } from 'react'; -import { useLocation, useMatch } from 'react-router-dom'; +import { useMatch } from 'react-router-dom'; import Card from '@/components/card/Card'; import ConfirmationStatus from '@/components/confirmation-status/ConfirmationStatus'; From 067989fc18c2a0894e50738c22d4d790d895b899 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 14 Jul 2023 13:59:32 +1000 Subject: [PATCH 73/87] Make block height clickable --- .../blocks-info/blocks-info-item/BlocksInfoItem.tsx | 10 ++++------ .../confirmation-status/ConfirmationStatus.tsx | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx index 5a6a770..4a3af46 100644 --- a/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx +++ b/frontend/src/components/blocks-info/blocks-info-item/BlocksInfoItem.tsx @@ -48,13 +48,11 @@ export function BlocksInfoItem(data: BlocksInfoItemProps) { return ( <div className='flex'> - <BlockCell className={cellWith.recentBlocks.height}>{data.number}</BlockCell> + <BlockCell className={cellWith.recentBlocks.height}> + <Link to={`/checker/${data.number}`}>{data.number}</Link> + </BlockCell> <BlockCell className={cellWith.recentBlocks.hash}> - <div className=''> - <Link to={`/checker/${data.hash}`}> - {formatHash(data.hash)} - </Link> - </div> + <Link to={`/checker/${data.hash}`}>{formatHash(data.hash)}</Link> </BlockCell> <BlockCell className={cellWith.recentBlocks.proposedBy}>{formatHash(data.miner)}</BlockCell> <BlockImageCell className={cellWith.recentBlocks.status}> diff --git a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx index 06d3358..a20c551 100644 --- a/frontend/src/components/confirmation-status/ConfirmationStatus.tsx +++ b/frontend/src/components/confirmation-status/ConfirmationStatus.tsx @@ -22,7 +22,7 @@ export default function ConfirmationStatus({ className, inputType, subnetStatus, } } - if (!(inputType && subnetStatus && parentChainStatus)) { + if (!inputType || subnetStatus === undefined || parentChainStatus === undefined) { return <></>; } From 1f849e0d2b113ee9b0a7b39133599f3e120dc99d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 17 Jul 2023 15:30:29 +1000 Subject: [PATCH 74/87] Error handling when confirmed number is greater than mined number --- frontend/src/components/Blocks.tsx | 6 +++++- .../components/block-cards/BlockCards.tsx | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Blocks.tsx b/frontend/src/components/Blocks.tsx index 98f5b1b..60acfa5 100644 --- a/frontend/src/components/Blocks.tsx +++ b/frontend/src/components/Blocks.tsx @@ -1,7 +1,7 @@ import BlockConnectLine from '@/components/BlockConnectLine'; import BlockImage from '@/components/images/block-image/BlockImage'; import { - BlockGapSize, BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber + BlockGapSize, BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber } from '@/constants/config'; export interface Block { @@ -40,6 +40,10 @@ export default function Blocks({ lastBlock, lastConfirmedBlock, blockNumber, blo return (confirmedNumber - 1) / 2; } + if (blocks.length === 0) { + return <h1 className='pb-4 text-xl font-medium'>There is an error in {name}</h1>; + } + return ( <> <h1 className='pb-4 text-xl font-medium'>{name}</h1> diff --git a/frontend/src/components/info-list/components/block-cards/BlockCards.tsx b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx index 70558a5..b8dca1d 100644 --- a/frontend/src/components/info-list/components/block-cards/BlockCards.tsx +++ b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx @@ -6,8 +6,8 @@ import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; import { - BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, - StandardScreenBlockNumber, WideScreenBlockNumber + BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, + StandardScreenBlockNumber, WideScreenBlockNumber } from '@/constants/config'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; @@ -27,6 +27,15 @@ function getBlocks(lastBlock: number | undefined, lastConfirmedBlock: number | u const confirmedCount = allBlockNumber - unconfirmedCount > 0 ? allBlockNumber - unconfirmedCount : 0; const firstBlockHeight = lastBlock - allBlockNumber + 1; + /** + * abnormal cases + **/ + if (lastConfirmedBlock - lastBlock > 0) { + // This can't happen + return []; + } + + // handle huge gap between last mined and last confirmed block if (lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber) { // unconfirmed blocks for (let i = 0; i < blockNumber + FakedConfirmedBlockNumber + FakedNotConfirmedBlockNumber; i++) { @@ -36,6 +45,9 @@ function getBlocks(lastBlock: number | undefined, lastConfirmedBlock: number | u return blocks; } + /** + * normal cases + **/ // confirmed blocks for (let i = 0; i < confirmedCount; i++) { blocks.push({ number: firstBlockHeight + i, confirmed: true }); From d49fda1e7fc4d78dac9d17e6acdd9006683c53b8 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 17 Jul 2023 15:32:47 +1000 Subject: [PATCH 75/87] Handle checker page without param --- frontend/src/main.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 5399f3d..94a2868 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -68,6 +68,10 @@ const router = createBrowserRouter([ loader: homeLoader, element: <HomePage />, }, { + path: 'checker', + element: <CheckerPage />, + }, + { path: 'checker/:id', element: <CheckerPage />, }, { From b191df9863f6802973f0b0755fd38282c974f39d Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 20 Jul 2023 10:11:31 +1000 Subject: [PATCH 76/87] Add timestamp to confirmation checker --- frontend/src/pages/CheckerPage.tsx | 8 +++++++- frontend/src/types/searchResult.d.ts | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index b17184e..4465c04 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -72,6 +72,9 @@ function getMappedInfo(searchResult: SearchResult) { }, { name: 'Proposer', value: formatHash(parentChain.proposer) + }, { + name: 'Timestamp', + value: formatTime(parentChain.timestamp) }] }; } @@ -86,7 +89,10 @@ function getMappedInfo(searchResult: SearchResult) { value: formatHash(subnet.blockHash) }, { name: 'Proposer', - value: '' + value: formatHash(subnet.proposer) + }, { + name: 'Timestamp', + value: formatTime(subnet.timestamp) }] }; } diff --git a/frontend/src/types/searchResult.d.ts b/frontend/src/types/searchResult.d.ts index 928bb9f..dc5e84d 100644 --- a/frontend/src/types/searchResult.d.ts +++ b/frontend/src/types/searchResult.d.ts @@ -11,8 +11,9 @@ namespace SearchResult { blockHeight: string; isConfirmed: boolean; proposer: string; + timestamp: number; } - + export interface Transaction { from: string; to: string; From c26023a4d573179a1ecf29989cbb01eea56e3fc2 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 20 Jul 2023 11:10:31 +1000 Subject: [PATCH 77/87] Move blocks logic to home page layer --- .../components/block-cards/BlockCards.tsx | 126 ++++-------------- frontend/src/pages/HomePage.tsx | 111 ++++++++++++++- 2 files changed, 133 insertions(+), 104 deletions(-) diff --git a/frontend/src/components/info-list/components/block-cards/BlockCards.tsx b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx index b8dca1d..1961717 100644 --- a/frontend/src/components/info-list/components/block-cards/BlockCards.tsx +++ b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx @@ -1,112 +1,32 @@ -import axios from 'axios'; -import { useContext, useEffect, useState } from 'react'; -import { useLoaderData } from 'react-router-dom'; - import Blocks, { Block } from '@/components/Blocks'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; -import { - BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, - StandardScreenBlockNumber, WideScreenBlockNumber -} from '@/constants/config'; -import { baseUrl } from '@/constants/urls'; -import { TimeContext } from '@/contexts/TimeContext'; -import { useIsTablet, useWindowWidth } from '@/hooks/useMediaQuery'; -import { Info } from '@/types/info'; -import { HomeLoaderData } from '@/types/loaderData'; - -function getBlocks(lastBlock: number | undefined, lastConfirmedBlock: number | undefined, blockNumber: number) { - if (!lastBlock || !lastConfirmedBlock) { - return []; - } - - const blocks = []; - // To allow animation when the furtherest left block move out - const allBlockNumber = blockNumber + FakedConfirmedBlockNumber; - const unconfirmedCount = lastBlock - lastConfirmedBlock; - const confirmedCount = allBlockNumber - unconfirmedCount > 0 ? allBlockNumber - unconfirmedCount : 0; - const firstBlockHeight = lastBlock - allBlockNumber + 1; - - /** - * abnormal cases - **/ - if (lastConfirmedBlock - lastBlock > 0) { - // This can't happen - return []; - } - - // handle huge gap between last mined and last confirmed block - if (lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber) { - // unconfirmed blocks - for (let i = 0; i < blockNumber + FakedConfirmedBlockNumber + FakedNotConfirmedBlockNumber; i++) { - blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); - } - - return blocks; - } - - /** - * normal cases - **/ - // confirmed blocks - for (let i = 0; i < confirmedCount; i++) { - blocks.push({ number: firstBlockHeight + i, confirmed: true }); - } - - // unconfirmed blocks - for (let i = 0; i < unconfirmedCount + FakedNotConfirmedBlockNumber; i++) { - blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); - } - - return blocks; +import { useIsTablet } from '@/hooks/useMediaQuery'; + +import type { Info } from '@/types/info'; + +interface BlockCardsProps { + blocks: Block[]; + parentBlocks: Block[]; + blockNumber: number; + lastSubnetConfirmedBlock?: number; + lastSubnetBlock?: number; + lastParentConfirmedBlock?: number; + lastParentBlock?: number; } -function getBlockNumber(windowWidth: number) { - if (windowWidth >= 1440) { - return WideScreenBlockNumber; - } - - if (windowWidth < 1024) { - const diff = 1024 - windowWidth; - return StandardScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); - } - - const diff = 1440 - windowWidth; - return WideScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); -} - -export default function BlockCards() { - const loaderData = useLoaderData() as HomeLoaderData; - const windowWidth = useWindowWidth(); +export default function BlockCards(props: BlockCardsProps) { + const { + lastSubnetConfirmedBlock, + lastSubnetBlock, + lastParentConfirmedBlock, + lastParentBlock, + blockNumber, + blocks, + parentBlocks + } = props; const isTablet = useIsTablet(); - const blockNumber = getBlockNumber(windowWidth); - - const [lastSubnetBlock, setLastSubnetBlock] = useState(loaderData.blocks?.subnet.latestMinedBlock.number); - const [lastParentBlock, setLastParentBlock] = useState(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number); - const [lastSubnetConfirmedBlock, setLastSubnetConfirmedBlock] = useState(loaderData.blocks?.subnet.latestCommittedBlock.number); - const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number); - const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.subnet.latestMinedBlock.number, loaderData.blocks?.subnet.latestCommittedBlock.number, blockNumber)); - const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); - const { currentUnixTime } = useContext(TimeContext); - - useEffect(() => { - async function getData() { - const { data: { subnet, checkpoint } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); - setLastSubnetBlock(subnet.latestMinedBlock.number); - setLastSubnetConfirmedBlock(subnet.latestCommittedBlock.number); - setLastParentBlock(checkpoint.latestSubmittedSubnetBlock.number); - setLastParentConfirmedBlock(checkpoint.latestCommittedSubnetBlock.number); - - const blocks = getBlocks(subnet.latestMinedBlock.number, subnet.latestCommittedBlock.number, blockNumber); - const parentBlocks = getBlocks(checkpoint.latestSubmittedSubnetBlock.number, checkpoint.latestCommittedSubnetBlock.number, blockNumber); - setBlocks(blocks); - setParentBlocks(parentBlocks); - } - - getData(); - }, [blockNumber, currentUnixTime]); - const mappedInfo: Info = { subnet: { data: [{ @@ -119,7 +39,7 @@ export default function BlockCards() { data: [{ name: 'Last committed block number', value: lastParentConfirmedBlock }, { - name: 'Last mined block number', value: lastSubnetBlock + name: 'Last mined block number', value: lastParentBlock }] } }; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 36a917a..be760b8 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,11 +1,120 @@ +import axios from 'axios'; +import { useContext, useEffect, useState } from 'react'; +import { useLoaderData } from 'react-router-dom'; + +import { Block } from '@/components/Blocks'; import InfoCards from '@/components/info-cards/InfoCards'; import BlockCards from '@/components/info-list/components/block-cards/BlockCards'; +import { + BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, + StandardScreenBlockNumber, WideScreenBlockNumber +} from '@/constants/config'; +import { baseUrl } from '@/constants/urls'; +import { TimeContext } from '@/contexts/TimeContext'; +import { useWindowWidth } from '@/hooks/useMediaQuery'; + +import type { HomeLoaderData } from '@/types/loaderData'; + +function getBlockNumber(windowWidth: number) { + if (windowWidth >= 1440) { + return WideScreenBlockNumber; + } + + if (windowWidth < 1024) { + const diff = 1024 - windowWidth; + return StandardScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); + } + + const diff = 1440 - windowWidth; + return WideScreenBlockNumber - Math.floor(diff / BlockSizeWithGap); +} + +function getBlocks(lastBlock: number | undefined, lastConfirmedBlock: number | undefined, blockNumber: number) { + if (!lastBlock || !lastConfirmedBlock) { + return []; + } + + const blocks = []; + // To allow animation when the furtherest left block move out + const allBlockNumber = blockNumber + FakedConfirmedBlockNumber; + const unconfirmedCount = lastBlock - lastConfirmedBlock; + const confirmedCount = allBlockNumber - unconfirmedCount > 0 ? allBlockNumber - unconfirmedCount : 0; + const firstBlockHeight = lastBlock - allBlockNumber + 1; + + /** + * abnormal cases + **/ + if (lastConfirmedBlock - lastBlock > 0) { + // This can't happen + return []; + } + + // handle huge gap between last mined and last confirmed block + if (lastBlock - lastConfirmedBlock > blockNumber + FakedConfirmedBlockNumber) { + // unconfirmed blocks + for (let i = 0; i < blockNumber + FakedConfirmedBlockNumber + FakedNotConfirmedBlockNumber; i++) { + blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); + } + + return blocks; + } + + /** + * normal cases + **/ + // confirmed blocks + for (let i = 0; i < confirmedCount; i++) { + blocks.push({ number: firstBlockHeight + i, confirmed: true }); + } + + // unconfirmed blocks + for (let i = 0; i < unconfirmedCount + FakedNotConfirmedBlockNumber; i++) { + blocks.push({ number: firstBlockHeight + confirmedCount + i, confirmed: false }); + } + + return blocks; +} export default function HomePage() { + const loaderData = useLoaderData() as HomeLoaderData; + const windowWidth = useWindowWidth(); + const blockNumber = getBlockNumber(windowWidth); + + const [lastSubnetBlock, setLastSubnetBlock] = useState(loaderData.blocks?.subnet.latestMinedBlock.number); + const [lastParentBlock, setLastParentBlock] = useState(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number); + const [lastSubnetConfirmedBlock, setLastSubnetConfirmedBlock] = useState(loaderData.blocks?.subnet.latestCommittedBlock.number); + const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number); + const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.subnet.latestMinedBlock.number, loaderData.blocks?.subnet.latestCommittedBlock.number, blockNumber)); + const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); + const { currentUnixTime } = useContext(TimeContext); + + useEffect(() => { + async function getData() { + const { data: { subnet, checkpoint } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); + setLastSubnetBlock(subnet.latestMinedBlock.number); + setLastParentBlock(checkpoint.latestSubmittedSubnetBlock.number); + setLastSubnetConfirmedBlock(subnet.latestCommittedBlock.number); + setLastParentConfirmedBlock(checkpoint.latestCommittedSubnetBlock.number); + + const blocks = getBlocks(subnet.latestMinedBlock.number, subnet.latestCommittedBlock.number, blockNumber); + const parentBlocks = getBlocks(checkpoint.latestSubmittedSubnetBlock.number, checkpoint.latestCommittedSubnetBlock.number, blockNumber); + setBlocks(blocks); + setParentBlocks(parentBlocks); + } + getData(); + }, [blockNumber, currentUnixTime]); return ( <div className='grid gap-6 grid-col-1'> - <BlockCards /> + <BlockCards + lastSubnetBlock={lastSubnetBlock} + lastParentBlock={lastParentBlock} + lastSubnetConfirmedBlock={lastSubnetConfirmedBlock} + lastParentConfirmedBlock={lastParentConfirmedBlock} + blockNumber={blockNumber} + blocks={blocks} + parentBlocks={parentBlocks} + /> <InfoCards /> </div> ); From d3f82ef8417d8044f5a0c6f7834991d24dfa4b11 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 20 Jul 2023 11:10:46 +1000 Subject: [PATCH 78/87] Update recent blocks data with polling data --- .../src/components/info-cards/InfoCards.tsx | 26 +++++++++---------- .../components/block-cards/BlockCards.tsx | 14 +++++----- frontend/src/pages/HomePage.tsx | 21 ++++++++++----- frontend/src/pages/utils/BlockHelper.ts | 13 ++++++++++ 4 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 frontend/src/pages/utils/BlockHelper.ts diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 1a8ee7e..cc22ad2 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -1,4 +1,3 @@ -import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; import { @@ -7,14 +6,19 @@ import { import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; +import { getSortedRecentBlocks } from '@/pages/utils/BlockHelper'; import { Info, InfoListHealth } from '@/types/info'; import { HomeLoaderData } from '@/types/loaderData'; import { formatHash, formatMoney } from '@/utils/formatter'; -export default function InfoCards() { - const loaderData = useLoaderData() as HomeLoaderData; +interface InfoCardsProps { + recentBlocks: BlocksInfoItem[]; + setRecentBlocks: React.Dispatch<React.SetStateAction<BlocksInfoItem[]>>; +} - const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[] | undefined>(getInitRecentBlocks()); +export default function InfoCards(props: InfoCardsProps) { + const { recentBlocks, setRecentBlocks } = props; + const loaderData = useLoaderData() as HomeLoaderData; function getNetworkStatus(): InfoListHealth { if (loaderData.network?.health.status === 'UP') { @@ -32,13 +36,6 @@ export default function InfoCards() { return 'Abnormal'; } - function getInitRecentBlocks(): BlocksInfoItem[] | undefined { - return loaderData.blocks?.blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ - type: 'recent-block', - ...block - })); - } - const mappedInfo: Info = getMappedInfo(loaderData, getNetworkStatus, getRelayerStatus); const masterNodes = loaderData.masterNodes?.nodes?.map<MasterNode>((v, i: number) => ({ @@ -54,10 +51,11 @@ export default function InfoCards() { } // TODO: From api - const data: MasterNode[] = []; + const data: HomeLoaderData.Blocks.Block[] = []; - setRecentBlocks(recentBlocks => { - return [...recentBlocks ?? [], ...data]; + // concat data from api in the end of list since it would be the 'previous' data + setRecentBlocks((recentBlocks: BlocksInfoItem[]) => { + return [...recentBlocks, ...getSortedRecentBlocks(data)]; }); }; diff --git a/frontend/src/components/info-list/components/block-cards/BlockCards.tsx b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx index 1961717..8447709 100644 --- a/frontend/src/components/info-list/components/block-cards/BlockCards.tsx +++ b/frontend/src/components/info-list/components/block-cards/BlockCards.tsx @@ -6,9 +6,9 @@ import { useIsTablet } from '@/hooks/useMediaQuery'; import type { Info } from '@/types/info'; interface BlockCardsProps { - blocks: Block[]; - parentBlocks: Block[]; blockNumber: number; + subnetBlocks: Block[]; + parentBlocks: Block[]; lastSubnetConfirmedBlock?: number; lastSubnetBlock?: number; lastParentConfirmedBlock?: number; @@ -17,12 +17,12 @@ interface BlockCardsProps { export default function BlockCards(props: BlockCardsProps) { const { - lastSubnetConfirmedBlock, + blockNumber, lastSubnetBlock, - lastParentConfirmedBlock, lastParentBlock, - blockNumber, - blocks, + lastSubnetConfirmedBlock, + lastParentConfirmedBlock, + subnetBlocks, parentBlocks } = props; const isTablet = useIsTablet(); @@ -53,7 +53,7 @@ export default function BlockCards(props: BlockCardsProps) { lastBlock={lastSubnetBlock} lastConfirmedBlock={lastSubnetConfirmedBlock} blockNumber={blockNumber} - blocks={blocks} + blocks={subnetBlocks} name='subnet blockchain' /> </Card >) : ( diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index be760b8..f2a9ffe 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -3,6 +3,7 @@ import { useContext, useEffect, useState } from 'react'; import { useLoaderData } from 'react-router-dom'; import { Block } from '@/components/Blocks'; +import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import InfoCards from '@/components/info-cards/InfoCards'; import BlockCards from '@/components/info-list/components/block-cards/BlockCards'; import { @@ -12,9 +13,9 @@ import { import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; import { useWindowWidth } from '@/hooks/useMediaQuery'; +import { getSortedRecentBlocks } from '@/pages/utils/BlockHelper'; import type { HomeLoaderData } from '@/types/loaderData'; - function getBlockNumber(windowWidth: number) { if (windowWidth >= 1440) { return WideScreenBlockNumber; @@ -84,26 +85,32 @@ export default function HomePage() { const [lastParentBlock, setLastParentBlock] = useState(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number); const [lastSubnetConfirmedBlock, setLastSubnetConfirmedBlock] = useState(loaderData.blocks?.subnet.latestCommittedBlock.number); const [lastParentConfirmedBlock, setLastParentConfirmedBlock] = useState(loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number); - const [blocks, setBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.subnet.latestMinedBlock.number, loaderData.blocks?.subnet.latestCommittedBlock.number, blockNumber)); + const [subnetBlocks, setSubnetBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.subnet.latestMinedBlock.number, loaderData.blocks?.subnet.latestCommittedBlock.number, blockNumber)); const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); + const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getSortedRecentBlocks(loaderData.blocks?.blocks)); const { currentUnixTime } = useContext(TimeContext); useEffect(() => { async function getData() { - const { data: { subnet, checkpoint } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); + const { data: { subnet, checkpoint, blocks } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); setLastSubnetBlock(subnet.latestMinedBlock.number); setLastParentBlock(checkpoint.latestSubmittedSubnetBlock.number); setLastSubnetConfirmedBlock(subnet.latestCommittedBlock.number); setLastParentConfirmedBlock(checkpoint.latestCommittedSubnetBlock.number); - const blocks = getBlocks(subnet.latestMinedBlock.number, subnet.latestCommittedBlock.number, blockNumber); + const subnetBlocks = getBlocks(subnet.latestMinedBlock.number, subnet.latestCommittedBlock.number, blockNumber); const parentBlocks = getBlocks(checkpoint.latestSubmittedSubnetBlock.number, checkpoint.latestCommittedSubnetBlock.number, blockNumber); - setBlocks(blocks); + setSubnetBlocks(subnetBlocks); setParentBlocks(parentBlocks); + + const newRecentBlocks = [...recentBlocks, ...getSortedRecentBlocks(blocks)]; + setRecentBlocks(newRecentBlocks); } getData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [blockNumber, currentUnixTime]); + return ( <div className='grid gap-6 grid-col-1'> <BlockCards @@ -112,10 +119,10 @@ export default function HomePage() { lastSubnetConfirmedBlock={lastSubnetConfirmedBlock} lastParentConfirmedBlock={lastParentConfirmedBlock} blockNumber={blockNumber} - blocks={blocks} + subnetBlocks={subnetBlocks} parentBlocks={parentBlocks} /> - <InfoCards /> + <InfoCards recentBlocks={recentBlocks} setRecentBlocks={setRecentBlocks} /> </div> ); } diff --git a/frontend/src/pages/utils/BlockHelper.ts b/frontend/src/pages/utils/BlockHelper.ts new file mode 100644 index 0000000..1d6c756 --- /dev/null +++ b/frontend/src/pages/utils/BlockHelper.ts @@ -0,0 +1,13 @@ +import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; +import { HomeLoaderData } from '@/types/loaderData'; + +export function getSortedRecentBlocks(blocks?: HomeLoaderData.Blocks.Block[]): BlocksInfoItem[] { + if (!blocks) { + return []; + } + + return blocks.sort((a, b) => b.number - a.number).map<BlocksInfoItem>(block => ({ + ...block, + type: 'recent-block', + })); +} \ No newline at end of file From b6714d4bc835b9a8d450fc88eb3e9bd5431fd994 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 20 Jul 2023 12:49:21 +1000 Subject: [PATCH 79/87] Fix duplicate element in array --- .../src/components/info-cards/InfoCards.tsx | 6 ++-- frontend/src/pages/HomePage.tsx | 4 +-- frontend/src/pages/utils/BlockHelper.ts | 28 ++++++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index cc22ad2..74ca93b 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -6,7 +6,9 @@ import { import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; -import { getSortedRecentBlocks } from '@/pages/utils/BlockHelper'; +import { + getSortedRecentBlocks, uniqReplaceByName as uniqReplaceByName +} from '@/pages/utils/BlockHelper'; import { Info, InfoListHealth } from '@/types/info'; import { HomeLoaderData } from '@/types/loaderData'; import { formatHash, formatMoney } from '@/utils/formatter'; @@ -55,7 +57,7 @@ export default function InfoCards(props: InfoCardsProps) { // concat data from api in the end of list since it would be the 'previous' data setRecentBlocks((recentBlocks: BlocksInfoItem[]) => { - return [...recentBlocks, ...getSortedRecentBlocks(data)]; + return uniqReplaceByName(recentBlocks, getSortedRecentBlocks(data)); }); }; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index f2a9ffe..914cdcc 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -13,7 +13,7 @@ import { import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; import { useWindowWidth } from '@/hooks/useMediaQuery'; -import { getSortedRecentBlocks } from '@/pages/utils/BlockHelper'; +import { getSortedRecentBlocks, uniqReplaceByName } from '@/pages/utils/BlockHelper'; import type { HomeLoaderData } from '@/types/loaderData'; function getBlockNumber(windowWidth: number) { @@ -103,7 +103,7 @@ export default function HomePage() { setSubnetBlocks(subnetBlocks); setParentBlocks(parentBlocks); - const newRecentBlocks = [...recentBlocks, ...getSortedRecentBlocks(blocks)]; + const newRecentBlocks = uniqReplaceByName(recentBlocks, getSortedRecentBlocks(blocks)); setRecentBlocks(newRecentBlocks); } diff --git a/frontend/src/pages/utils/BlockHelper.ts b/frontend/src/pages/utils/BlockHelper.ts index 1d6c756..a3e347c 100644 --- a/frontend/src/pages/utils/BlockHelper.ts +++ b/frontend/src/pages/utils/BlockHelper.ts @@ -1,6 +1,32 @@ -import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import { HomeLoaderData } from '@/types/loaderData'; +/** + * Iterate each item in new array + * if it exists in old array, update old array value and discord the item + * if it does not exist in old array, concat it in the end of old array + * @param oldArray old array + * @param newArray new array + */ +export function uniqReplaceByName(oldArray: any[], newArray: any[]) { + const copyOfOldArray = [...oldArray]; + for (let i = 0; i < newArray.length; i++) { + const newArrayElement = newArray[i]; + if (!('number' in newArrayElement)) { + return []; + } + + const duplicateIndex = oldArray.findIndex(oldArrayElement => oldArrayElement['number'] === newArrayElement['number']); + + if (duplicateIndex === -1) { + copyOfOldArray.push(newArrayElement); + } + + copyOfOldArray[duplicateIndex] = newArrayElement; + } + + return copyOfOldArray; +} + export function getSortedRecentBlocks(blocks?: HomeLoaderData.Blocks.Block[]): BlocksInfoItem[] { if (!blocks) { return []; From 6d7008f2947d72075362b110ebbd7967b216c4ce Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sat, 22 Jul 2023 19:57:17 +1000 Subject: [PATCH 80/87] Remove auto refresh recent blocks code --- frontend/src/pages/HomePage.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 914cdcc..f302784 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -13,7 +13,7 @@ import { import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; import { useWindowWidth } from '@/hooks/useMediaQuery'; -import { getSortedRecentBlocks, uniqReplaceByName } from '@/pages/utils/BlockHelper'; +import { getSortedRecentBlocks } from '@/pages/utils/BlockHelper'; import type { HomeLoaderData } from '@/types/loaderData'; function getBlockNumber(windowWidth: number) { @@ -92,7 +92,7 @@ export default function HomePage() { useEffect(() => { async function getData() { - const { data: { subnet, checkpoint, blocks } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); + const { data: { subnet, checkpoint } } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); setLastSubnetBlock(subnet.latestMinedBlock.number); setLastParentBlock(checkpoint.latestSubmittedSubnetBlock.number); setLastSubnetConfirmedBlock(subnet.latestCommittedBlock.number); @@ -102,9 +102,6 @@ export default function HomePage() { const parentBlocks = getBlocks(checkpoint.latestSubmittedSubnetBlock.number, checkpoint.latestCommittedSubnetBlock.number, blockNumber); setSubnetBlocks(subnetBlocks); setParentBlocks(parentBlocks); - - const newRecentBlocks = uniqReplaceByName(recentBlocks, getSortedRecentBlocks(blocks)); - setRecentBlocks(newRecentBlocks); } getData(); From aca1e0976dc39f9e799e960421dc1e7e52cb7837 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sat, 22 Jul 2023 20:35:34 +1000 Subject: [PATCH 81/87] Format hash from non zero --- frontend/src/components/info-cards/InfoCards.tsx | 2 +- frontend/src/pages/CheckerPage.tsx | 7 +++---- frontend/src/utils/formatter.ts | 4 ++++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 74ca93b..db63407 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -102,7 +102,7 @@ function getMappedInfo(loaderData: HomeLoaderData, getNetworkStatus: () => InfoL info.network = { health: getNetworkStatus(), data: [ - { name: 'Block Time', value: `${loaderData.network.subnet.block.averageBlockTime}s` }, + { name: 'Block Time', value: `${Math.floor(loaderData.network.subnet.block.averageBlockTime * 100) / 100}s` }, { name: 'TX Throughput', value: `${Math.round(loaderData.network.subnet.block.txThroughput * 100) / 100} txs/s` }, { name: 'Checkpointed to', value: loaderData.network.parentChain.name }, ] diff --git a/frontend/src/pages/CheckerPage.tsx b/frontend/src/pages/CheckerPage.tsx index b17184e..9e2f665 100644 --- a/frontend/src/pages/CheckerPage.tsx +++ b/frontend/src/pages/CheckerPage.tsx @@ -8,7 +8,7 @@ import InfoList from '@/components/info-list/InfoList'; import Loader from '@/components/loader/Loader'; import SearchBar from '@/components/search-bar/SearchBar'; import SearchNotFound from '@/components/search-not-found/SearchNotFound'; -import { formatHash, formatMoney, formatTime } from '@/utils/formatter'; +import { formatHash, formatHashFromNonZero, formatMoney, formatTime } from '@/utils/formatter'; import type { Info, InfoItem } from '@/types/info'; import type { SearchResult } from '@/types/searchResult'; @@ -71,11 +71,10 @@ function getMappedInfo(searchResult: SearchResult) { value: formatHash(parentChain.blockHash) }, { name: 'Proposer', - value: formatHash(parentChain.proposer) + value: formatHashFromNonZero(parentChain.proposer) }] }; } - if (subnet) { mappedInfo.subnetBlock = { data: [{ @@ -86,7 +85,7 @@ function getMappedInfo(searchResult: SearchResult) { value: formatHash(subnet.blockHash) }, { name: 'Proposer', - value: '' + value: formatHashFromNonZero(subnet.proposer) }] }; } diff --git a/frontend/src/utils/formatter.ts b/frontend/src/utils/formatter.ts index 4523717..330af22 100644 --- a/frontend/src/utils/formatter.ts +++ b/frontend/src/utils/formatter.ts @@ -1,3 +1,7 @@ +export function formatHashFromNonZero(hash: string): string { + return formatHash(hash.replace(/0x[0]*/, 'xdc')); +} + export function formatHash(hash: string): string { if (hash?.length >= 15) { return `${hash.slice(0, 6)}...${hash.slice(-6)}`; From 28a2ad3c476685e00c53e4d3a2fbcfa40ca6ebee Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Sat, 22 Jul 2023 20:42:38 +1000 Subject: [PATCH 82/87] Fix build error --- frontend/src/pages/utils/BlockHelper.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/utils/BlockHelper.ts b/frontend/src/pages/utils/BlockHelper.ts index a3e347c..46e22f9 100644 --- a/frontend/src/pages/utils/BlockHelper.ts +++ b/frontend/src/pages/utils/BlockHelper.ts @@ -1,4 +1,6 @@ -import { HomeLoaderData } from '@/types/loaderData'; +import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; + +import type { HomeLoaderData } from '@/types/loaderData'; /** * Iterate each item in new array From a13e2cf4ec73fa030254de9516211989841a3698 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 27 Jul 2023 15:17:12 +1000 Subject: [PATCH 83/87] Allow fetch more recent blocks when scroll to the end of the list --- .../src/components/blocks-info/BlocksInfo.tsx | 7 ++-- .../components/infinite-list/InfiniteList.tsx | 12 ++++-- .../src/components/info-cards/InfoCards.tsx | 37 ++++++++++++++----- frontend/src/pages/HomePage.tsx | 7 +++- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index d3e7258..f4afaa1 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -1,5 +1,5 @@ import { - BlockCell, BlocksInfoItem + BlockCell, BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import { cellWith } from '@/components/blocks-info/constants'; import { MasterNodeTitle } from '@/components/blocks-info/master-node-title/MasterNodeTitle'; @@ -14,9 +14,10 @@ interface BlocksInfoProps { data?: BlocksInfoItem[]; fetchMoreData?: () => void; enableInfinite?: boolean; + isFetchingMoreRecentBlocks?: boolean; } -export default function BlocksInfo({ title, data, fetchMoreData }: BlocksInfoProps) { +export default function BlocksInfo({ title, data, fetchMoreData, isFetchingMoreRecentBlocks }: BlocksInfoProps) { if (!data || !data.length) { return <ErrorState title={title} />; } @@ -32,7 +33,7 @@ export default function BlocksInfo({ title, data, fetchMoreData }: BlocksInfoPro <> <BlocksInfoHeading type={data[0].type} /> {fetchMoreData ? ( - <InfiniteList data={data} fetchData={fetchMoreData}> + <InfiniteList data={data} fetchData={fetchMoreData} isFetchingMoreRecentBlocks={isFetchingMoreRecentBlocks}> <BlocksInfoItems data={data} title={title} /> </InfiniteList> ) : ( diff --git a/frontend/src/components/infinite-list/InfiniteList.tsx b/frontend/src/components/infinite-list/InfiniteList.tsx index ebc7501..ed32c23 100644 --- a/frontend/src/components/infinite-list/InfiniteList.tsx +++ b/frontend/src/components/infinite-list/InfiniteList.tsx @@ -5,9 +5,10 @@ import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/Blocks interface InfiniteListProps extends PropsWithChildren { data: BlocksInfoItem[]; fetchData: () => void; + isFetchingMoreRecentBlocks?: boolean; } -export default function InfiniteList({ fetchData, children }: InfiniteListProps) { +export default function InfiniteList({ fetchData, children, isFetchingMoreRecentBlocks }: InfiniteListProps) { const observerTarget = useRef(null); useEffect(() => { @@ -15,7 +16,7 @@ export default function InfiniteList({ fetchData, children }: InfiniteListProps) const observer = new IntersectionObserver( entries => { - if (entries[0].isIntersecting) { + if (entries[0].isIntersecting && !isFetchingMoreRecentBlocks) { fetchData(); } }, @@ -31,13 +32,16 @@ export default function InfiniteList({ fetchData, children }: InfiniteListProps) observer.unobserve(currentTarget); } }; - }, [fetchData, observerTarget]); + }, [fetchData, observerTarget, isFetchingMoreRecentBlocks]); return ( <> {children} + {isFetchingMoreRecentBlocks && ( + <div className='text-bg-dark-800 dark:text-white p-5 pl-0'>Loading more data...</div> + )} <div ref={observerTarget}></div> - {/* An extra div is essential for infinitely scrolling */} + {/* The following extra div is essential for infinitely scrolling */} <div className='dark:text-bg-dark-800 text-white'>End of list</div> </ > ); diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index db63407..681baa3 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -1,3 +1,5 @@ +import axios from 'axios'; +import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; import { @@ -6,22 +8,25 @@ import { import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; import InfoList from '@/components/info-list/InfoList'; -import { - getSortedRecentBlocks, uniqReplaceByName as uniqReplaceByName -} from '@/pages/utils/BlockHelper'; +import { baseUrl } from '@/constants/urls'; +import { getSortedRecentBlocks, uniqReplaceByName } from '@/pages/utils/BlockHelper'; import { Info, InfoListHealth } from '@/types/info'; import { HomeLoaderData } from '@/types/loaderData'; import { formatHash, formatMoney } from '@/utils/formatter'; interface InfoCardsProps { + nextFetchRecentBlocksIndex: number; + setNextFetchRecentBlocksIndex: React.Dispatch<React.SetStateAction<number>>; recentBlocks: BlocksInfoItem[]; setRecentBlocks: React.Dispatch<React.SetStateAction<BlocksInfoItem[]>>; } export default function InfoCards(props: InfoCardsProps) { - const { recentBlocks, setRecentBlocks } = props; + const { nextFetchRecentBlocksIndex, setNextFetchRecentBlocksIndex, recentBlocks, setRecentBlocks } = props; const loaderData = useLoaderData() as HomeLoaderData; + const [isFetchingMoreRecentBlocks, setIsLoadingMoreRecentBlocks] = useState(false); + function getNetworkStatus(): InfoListHealth { if (loaderData.network?.health.status === 'UP') { return 'Normal'; @@ -47,17 +52,25 @@ export default function InfoCards(props: InfoCardsProps) { number: i + 1 })); - const fetchMoreRecentBlocks = () => { + const fetchMoreRecentBlocks = async () => { if (!recentBlocks) { return; } - // TODO: From api - const data: HomeLoaderData.Blocks.Block[] = []; + setIsLoadingMoreRecentBlocks(true); + const { data } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks?blockNumIndex=${nextFetchRecentBlocksIndex}`); + + if (!data.blocks[0].number) { + return; + } + + setNextFetchRecentBlocksIndex(data.blocks[0].number); // concat data from api in the end of list since it would be the 'previous' data setRecentBlocks((recentBlocks: BlocksInfoItem[]) => { - return uniqReplaceByName(recentBlocks, getSortedRecentBlocks(data)); + const newRecentBlocks = uniqReplaceByName(recentBlocks, getSortedRecentBlocks(data.blocks)); + setIsLoadingMoreRecentBlocks(false); + return newRecentBlocks; }); }; @@ -86,7 +99,13 @@ export default function InfoCards(props: InfoCardsProps) { <div className='grid grid-cols-1 llg:grid-cols-2 gap-6'> <Card className='max-w-[565px]'> - <BlocksInfo title='Recent Blocks' data={recentBlocks} fetchMoreData={fetchMoreRecentBlocks} enableInfinite /> + <BlocksInfo + title='Recent Blocks' + data={recentBlocks} + fetchMoreData={fetchMoreRecentBlocks} + isFetchingMoreRecentBlocks={isFetchingMoreRecentBlocks} + enableInfinite + /> </Card> {<Card className='max-w-[565px]'> <BlocksInfo title='Master Nodes' data={masterNodes} /> diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index f302784..3205ccb 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -88,6 +88,7 @@ export default function HomePage() { const [subnetBlocks, setSubnetBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.subnet.latestMinedBlock.number, loaderData.blocks?.subnet.latestCommittedBlock.number, blockNumber)); const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getSortedRecentBlocks(loaderData.blocks?.blocks)); + const [nextFetchRecentBlocksIndex, setNextFetchRecentBlocksIndex] = useState(loaderData.blocks ? loaderData.blocks.subnet.latestMinedBlock.number - 50 : 0); const { currentUnixTime } = useContext(TimeContext); useEffect(() => { @@ -119,7 +120,11 @@ export default function HomePage() { subnetBlocks={subnetBlocks} parentBlocks={parentBlocks} /> - <InfoCards recentBlocks={recentBlocks} setRecentBlocks={setRecentBlocks} /> + <InfoCards + nextFetchRecentBlocksIndex={nextFetchRecentBlocksIndex} + setNextFetchRecentBlocksIndex={setNextFetchRecentBlocksIndex} + recentBlocks={recentBlocks} + setRecentBlocks={setRecentBlocks} /> </div> ); } From 82f70732e9f3306211d72afdc8faa1a26f570b88 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 27 Jul 2023 15:38:43 +1000 Subject: [PATCH 84/87] Do not call api when reach api limit (300 blocks) --- .../src/components/blocks-info/BlocksInfo.tsx | 12 ++++++++--- .../components/infinite-list/InfiniteList.tsx | 12 +++++++---- .../src/components/info-cards/InfoCards.tsx | 20 +++++++++++++++---- frontend/src/pages/HomePage.tsx | 4 ++-- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index f4afaa1..8e1e40f 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -1,5 +1,5 @@ import { - BlockCell, BlocksInfoItem + BlockCell, BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import { cellWith } from '@/components/blocks-info/constants'; import { MasterNodeTitle } from '@/components/blocks-info/master-node-title/MasterNodeTitle'; @@ -15,9 +15,10 @@ interface BlocksInfoProps { fetchMoreData?: () => void; enableInfinite?: boolean; isFetchingMoreRecentBlocks?: boolean; + isReachApiEndOfRecentBlocks?: boolean; } -export default function BlocksInfo({ title, data, fetchMoreData, isFetchingMoreRecentBlocks }: BlocksInfoProps) { +export default function BlocksInfo({ title, data, fetchMoreData, isFetchingMoreRecentBlocks, isReachApiEndOfRecentBlocks }: BlocksInfoProps) { if (!data || !data.length) { return <ErrorState title={title} />; } @@ -33,7 +34,12 @@ export default function BlocksInfo({ title, data, fetchMoreData, isFetchingMoreR <> <BlocksInfoHeading type={data[0].type} /> {fetchMoreData ? ( - <InfiniteList data={data} fetchData={fetchMoreData} isFetchingMoreRecentBlocks={isFetchingMoreRecentBlocks}> + <InfiniteList + data={data} + fetchData={fetchMoreData} + isFetchingMoreRecentBlocks={isFetchingMoreRecentBlocks} + isReachApiEndOfRecentBlocks={isReachApiEndOfRecentBlocks} + > <BlocksInfoItems data={data} title={title} /> </InfiniteList> ) : ( diff --git a/frontend/src/components/infinite-list/InfiniteList.tsx b/frontend/src/components/infinite-list/InfiniteList.tsx index ed32c23..434ec62 100644 --- a/frontend/src/components/infinite-list/InfiniteList.tsx +++ b/frontend/src/components/infinite-list/InfiniteList.tsx @@ -6,9 +6,10 @@ interface InfiniteListProps extends PropsWithChildren { data: BlocksInfoItem[]; fetchData: () => void; isFetchingMoreRecentBlocks?: boolean; + isReachApiEndOfRecentBlocks?: boolean; } -export default function InfiniteList({ fetchData, children, isFetchingMoreRecentBlocks }: InfiniteListProps) { +export default function InfiniteList({ fetchData, children, isFetchingMoreRecentBlocks, isReachApiEndOfRecentBlocks }: InfiniteListProps) { const observerTarget = useRef(null); useEffect(() => { @@ -37,12 +38,15 @@ export default function InfiniteList({ fetchData, children, isFetchingMoreRecent return ( <> {children} - {isFetchingMoreRecentBlocks && ( - <div className='text-bg-dark-800 dark:text-white p-5 pl-0'>Loading more data...</div> + {(isReachApiEndOfRecentBlocks || isFetchingMoreRecentBlocks) && ( + <div className='text-bg-dark-800 dark:text-white p-5 pl-0'> + {isFetchingMoreRecentBlocks && <>Loading more data...</>} + {isReachApiEndOfRecentBlocks && <>The end of list...</>} + </div> )} <div ref={observerTarget}></div> {/* The following extra div is essential for infinitely scrolling */} - <div className='dark:text-bg-dark-800 text-white'>End of list</div> + <div className='dark:text-bg-dark-800 text-white'>Detection helper</div> </ > ); } \ No newline at end of file diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 681baa3..4b78edf 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; import { - BlocksInfoItem, MasterNode + BlocksInfoItem, MasterNode } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; @@ -25,7 +25,9 @@ export default function InfoCards(props: InfoCardsProps) { const { nextFetchRecentBlocksIndex, setNextFetchRecentBlocksIndex, recentBlocks, setRecentBlocks } = props; const loaderData = useLoaderData() as HomeLoaderData; + const [veryFirstSubnetBlock] = useState(loaderData.blocks?.blocks[0].number); const [isFetchingMoreRecentBlocks, setIsLoadingMoreRecentBlocks] = useState(false); + const [isReachApiEndOfRecentBlocks, setIsReachApiEndOfRecentBlocks] = useState(false); function getNetworkStatus(): InfoListHealth { if (loaderData.network?.health.status === 'UP') { @@ -53,23 +55,31 @@ export default function InfoCards(props: InfoCardsProps) { })); const fetchMoreRecentBlocks = async () => { - if (!recentBlocks) { + if (!recentBlocks || !veryFirstSubnetBlock) { return; } setIsLoadingMoreRecentBlocks(true); const { data } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks?blockNumIndex=${nextFetchRecentBlocksIndex}`); - if (!data.blocks[0].number) { + const firstBlockNumber = data.blocks[0].number; + + if (!firstBlockNumber) { return; } - setNextFetchRecentBlocksIndex(data.blocks[0].number); + setNextFetchRecentBlocksIndex(firstBlockNumber); // concat data from api in the end of list since it would be the 'previous' data setRecentBlocks((recentBlocks: BlocksInfoItem[]) => { const newRecentBlocks = uniqReplaceByName(recentBlocks, getSortedRecentBlocks(data.blocks)); setIsLoadingMoreRecentBlocks(false); + + // Reach API limit + if (veryFirstSubnetBlock - firstBlockNumber >= 251) { + setIsReachApiEndOfRecentBlocks(true); + } + return newRecentBlocks; }); }; @@ -103,6 +113,7 @@ export default function InfoCards(props: InfoCardsProps) { title='Recent Blocks' data={recentBlocks} fetchMoreData={fetchMoreRecentBlocks} + isReachApiEndOfRecentBlocks={isReachApiEndOfRecentBlocks} isFetchingMoreRecentBlocks={isFetchingMoreRecentBlocks} enableInfinite /> @@ -114,6 +125,7 @@ export default function InfoCards(props: InfoCardsProps) { </> ); } + function getMappedInfo(loaderData: HomeLoaderData, getNetworkStatus: () => InfoListHealth, getRelayerStatus: () => InfoListHealth): Info { const info: Info = {}; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 3205ccb..d6b0ff3 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -7,8 +7,8 @@ import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/Blocks import InfoCards from '@/components/info-cards/InfoCards'; import BlockCards from '@/components/info-list/components/block-cards/BlockCards'; import { - BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, - StandardScreenBlockNumber, WideScreenBlockNumber + BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, + StandardScreenBlockNumber, WideScreenBlockNumber } from '@/constants/config'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; From 2e3a93240ddedbb9fd6c49c2fdbd6571bd7d3403 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Thu, 27 Jul 2023 16:19:56 +1000 Subject: [PATCH 85/87] Add refresh button --- frontend/src/assets/refresh.svg | 3 ++ .../src/components/blocks-info/BlocksInfo.tsx | 24 ++++++++---- .../recent-blocks-title/RecentBlocksTitle.tsx | 38 +++++++++++++++++++ frontend/src/components/images/Svg.tsx | 7 ++++ .../components/infinite-list/InfiniteList.tsx | 23 +++++++---- .../src/components/info-cards/InfoCards.tsx | 20 ++++++++-- frontend/src/pages/HomePage.tsx | 10 +++-- 7 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 frontend/src/assets/refresh.svg create mode 100644 frontend/src/components/blocks-info/recent-blocks-title/RecentBlocksTitle.tsx diff --git a/frontend/src/assets/refresh.svg b/frontend/src/assets/refresh.svg new file mode 100644 index 0000000..1ed535b --- /dev/null +++ b/frontend/src/assets/refresh.svg @@ -0,0 +1,3 @@ +<svg height="64px" width="64px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 392.598 392.598" xml:space="preserve" + fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <path style="fill:#194F82;" d="M382.488,286.384c-3.62-3.62-9.438-4.267-13.77-1.422c-30.19,20.105-65.552,30.707-102.077,30.707 c-45.64,0-88.76-16.485-122.634-46.61l23.725-23.725c7.37-8.21,1.487-18.166-7.758-18.618H40.96 c-6.012,0-10.925,4.848-10.925,10.925v118.95c0.517,10.279,12.024,14.222,18.618,7.758l24.76-24.76 c37.947,34.263,86.626,53.01,138.085,53.01c55.143,0,125.285-23.014,172.154-92.444 C386.69,295.822,386.108,290.069,382.488,286.384z M211.692,370.747c-49.325,0-95.741-19.265-130.586-54.109 c-4.073-4.073-11.378-4.073-15.451,0L51.95,330.343v-81.842h81.778l-13.059,13.059c-4.202,4.59-4.461,10.99,0,15.451 c39.046,39.047,90.893,60.509,146.036,60.509c21.85,0,43.378-3.426,63.806-10.02C297.219,355.426,255.651,370.747,211.692,370.747z "></path> <path style="fill:#194F82;" d="M248.605,123.475L224.88,147.2c-7.37,8.21-1.487,18.166,7.758,18.618h119.014 c6.012,0,10.925-4.848,10.925-10.925V35.943c-0.517-10.279-12.024-14.222-18.618-7.758l-24.76,24.76 C281.122,18.683,232.508,0,180.985,0C125.841,0,55.829,23.014,8.831,92.444c-2.909,4.331-2.327,10.02,1.422,13.77 c3.62,3.62,9.438,4.267,13.77,1.422C54.213,87.531,89.575,76.929,126.1,76.929C171.611,76.865,214.795,93.349,248.605,123.475z M180.92,21.786c49.325,0,95.741,19.265,130.586,54.109c4.073,4.073,11.378,4.073,15.451,0l13.705-13.705v81.778h-81.778 l13.059-13.059c4.202-4.59,4.461-10.99,0-15.451c-39.046-39.046-90.893-60.509-146.036-60.509c-21.851,0-43.378,3.426-63.806,10.02 C95.393,37.107,136.96,21.786,180.92,21.786z"></path> </g> <path style="fill:#FFC10D;" d="M340.661,62.255L326.956,75.96c-4.073,4.073-11.378,4.073-15.451,0 c-34.844-34.844-81.261-54.109-130.586-54.109c-43.96,0-85.592,15.192-118.82,43.248c20.428-6.659,41.891-10.02,63.806-10.02 c55.143,0,107.055,21.463,146.036,60.509c4.461,4.396,4.202,10.796,0,15.451l-13.059,12.994h81.778L340.661,62.255L340.661,62.255z"></path> <path style="fill:#56ACE0;" d="M81.041,316.638c34.844,34.844,81.261,54.109,130.586,54.109c43.96,0,85.592-15.192,118.82-43.249 c-20.428,6.659-41.891,10.02-63.806,10.02c-55.143,0-107.055-21.463-146.036-60.509c-4.461-4.396-4.202-10.796,0-15.451 l13.059-13.059H51.95v81.778l13.705-13.705C69.728,312.566,76.968,312.566,81.041,316.638z"></path> </g></svg> \ No newline at end of file diff --git a/frontend/src/components/blocks-info/BlocksInfo.tsx b/frontend/src/components/blocks-info/BlocksInfo.tsx index 8e1e40f..7fc19e3 100644 --- a/frontend/src/components/blocks-info/BlocksInfo.tsx +++ b/frontend/src/components/blocks-info/BlocksInfo.tsx @@ -1,24 +1,27 @@ import { - BlockCell, BlocksInfoItem + BlockCell, BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import { cellWith } from '@/components/blocks-info/constants'; import { MasterNodeTitle } from '@/components/blocks-info/master-node-title/MasterNodeTitle'; +import RecentBlocksTitle from '@/components/blocks-info/recent-blocks-title/RecentBlocksTitle'; import ErrorState from '@/components/error-state/ErrorState'; import InfiniteList from '@/components/infinite-list/InfiniteList'; -import Title from '@/components/title/Title'; // import Tooltip from '@/components/tooltip/Tooltip'; interface BlocksInfoProps { title: string; data?: BlocksInfoItem[]; + setData?: React.Dispatch<React.SetStateAction<BlocksInfoItem[]>>; fetchMoreData?: () => void; enableInfinite?: boolean; - isFetchingMoreRecentBlocks?: boolean; - isReachApiEndOfRecentBlocks?: boolean; + isFetchingMore?: boolean; + isReachApiEnd?: boolean; + isLoading?: boolean; + setIsLoading?: React.Dispatch<React.SetStateAction<boolean>>; } -export default function BlocksInfo({ title, data, fetchMoreData, isFetchingMoreRecentBlocks, isReachApiEndOfRecentBlocks }: BlocksInfoProps) { +export default function BlocksInfo({ title, data, setData, fetchMoreData, isFetchingMore, isReachApiEnd, isLoading, setIsLoading }: BlocksInfoProps) { if (!data || !data.length) { return <ErrorState title={title} />; } @@ -28,7 +31,11 @@ export default function BlocksInfo({ title, data, fetchMoreData, isFetchingMoreR {(title === 'Master Nodes') ? ( <MasterNodeTitle title={title} /> ) : ( - <Title title={title} /> + <RecentBlocksTitle + title={title} + setData={setData} + setIsLoading={setIsLoading} + /> )} <div className='mt-0 h-[400px] overflow-hidden hover:overflow-auto relative dark:text-text-dark-100'> <> @@ -37,8 +44,9 @@ export default function BlocksInfo({ title, data, fetchMoreData, isFetchingMoreR <InfiniteList data={data} fetchData={fetchMoreData} - isFetchingMoreRecentBlocks={isFetchingMoreRecentBlocks} - isReachApiEndOfRecentBlocks={isReachApiEndOfRecentBlocks} + isFetchingMore={isFetchingMore} + isReachApiEnd={isReachApiEnd} + isLoading={isLoading} > <BlocksInfoItems data={data} title={title} /> </InfiniteList> diff --git a/frontend/src/components/blocks-info/recent-blocks-title/RecentBlocksTitle.tsx b/frontend/src/components/blocks-info/recent-blocks-title/RecentBlocksTitle.tsx new file mode 100644 index 0000000..2394ed3 --- /dev/null +++ b/frontend/src/components/blocks-info/recent-blocks-title/RecentBlocksTitle.tsx @@ -0,0 +1,38 @@ +import axios from 'axios'; + +import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; +import Svg, { SvgNames } from '@/components/images/Svg'; +import Title from '@/components/title/Title'; +import { baseUrl } from '@/constants/urls'; +import { getSortedRecentBlocks } from '@/pages/utils/BlockHelper'; +import { HomeLoaderData } from '@/types/loaderData'; + +interface RecentBlocksTitleProps { + title: string; + setData?: React.Dispatch<React.SetStateAction<BlocksInfoItem[]>>; + setIsLoading?: React.Dispatch<React.SetStateAction<boolean>>; +} + +export default function RecentBlocksTitle({ title, setData, setIsLoading }: RecentBlocksTitleProps) { + async function refreshData() { + if (setData && setIsLoading) { + setIsLoading(true); + const { data } = await axios.get<HomeLoaderData.Blocks>(`${baseUrl}/information/blocks`); + + setData(getSortedRecentBlocks(data.blocks)); + setIsLoading(false); + } + } + + return ( + <div className='flex justify-between'> + <Title title={title} /> + <div> + <button className='flex' onClick={refreshData}> + <Svg svgName={SvgNames.Refresh} /> + <div className='pl-2'>Refresh</div> + </button> + </div> + </div> + ); +} \ No newline at end of file diff --git a/frontend/src/components/images/Svg.tsx b/frontend/src/components/images/Svg.tsx index eff7f76..b12d64c 100644 --- a/frontend/src/components/images/Svg.tsx +++ b/frontend/src/components/images/Svg.tsx @@ -8,6 +8,7 @@ import Miner from '@/assets/miner.svg'; import NoResultDark from '@/assets/no-results-dark.svg'; import NoResult from '@/assets/no-results.svg'; import Penalty from '@/assets/penalty.svg'; +import Refresh from '@/assets/refresh.svg'; import Rhombus from '@/assets/rhombus.svg'; import Search from '@/assets/search.svg'; import Standby from '@/assets/standby.svg'; @@ -209,6 +210,11 @@ export default function Svg({ svgName, size, sizeClass: userDefinedSizeClass, cl SvgComponent = Menu; alt = 'Menu'; break; + + case SvgNames.Refresh: + SvgComponent = Refresh; + alt = 'Refresh'; + break; } return ( @@ -243,4 +249,5 @@ export enum SvgNames { InfoDark = 'InfoDark', Logo = 'Logo', Menu = 'Menu', + Refresh = 'Refresh', } diff --git a/frontend/src/components/infinite-list/InfiniteList.tsx b/frontend/src/components/infinite-list/InfiniteList.tsx index 434ec62..08261b2 100644 --- a/frontend/src/components/infinite-list/InfiniteList.tsx +++ b/frontend/src/components/infinite-list/InfiniteList.tsx @@ -5,11 +5,12 @@ import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/Blocks interface InfiniteListProps extends PropsWithChildren { data: BlocksInfoItem[]; fetchData: () => void; - isFetchingMoreRecentBlocks?: boolean; - isReachApiEndOfRecentBlocks?: boolean; + isLoading?: boolean; + isFetchingMore?: boolean; + isReachApiEnd?: boolean; } -export default function InfiniteList({ fetchData, children, isFetchingMoreRecentBlocks, isReachApiEndOfRecentBlocks }: InfiniteListProps) { +export default function InfiniteList({ fetchData, children, isFetchingMore, isReachApiEnd, isLoading }: InfiniteListProps) { const observerTarget = useRef(null); useEffect(() => { @@ -17,7 +18,7 @@ export default function InfiniteList({ fetchData, children, isFetchingMoreRecent const observer = new IntersectionObserver( entries => { - if (entries[0].isIntersecting && !isFetchingMoreRecentBlocks) { + if (entries[0].isIntersecting && !isFetchingMore) { fetchData(); } }, @@ -33,15 +34,21 @@ export default function InfiniteList({ fetchData, children, isFetchingMoreRecent observer.unobserve(currentTarget); } }; - }, [fetchData, observerTarget, isFetchingMoreRecentBlocks]); + }, [fetchData, observerTarget, isFetchingMore]); + + if (isLoading) { + return ( + <div className='pt-20 text-center'>Loading...</div> + ); + } return ( <> {children} - {(isReachApiEndOfRecentBlocks || isFetchingMoreRecentBlocks) && ( + {(isReachApiEnd || isFetchingMore) && ( <div className='text-bg-dark-800 dark:text-white p-5 pl-0'> - {isFetchingMoreRecentBlocks && <>Loading more data...</>} - {isReachApiEndOfRecentBlocks && <>The end of list...</>} + {isFetchingMore && <>Loading more data...</>} + {isReachApiEnd && <>The end of list...</>} </div> )} <div ref={observerTarget}></div> diff --git a/frontend/src/components/info-cards/InfoCards.tsx b/frontend/src/components/info-cards/InfoCards.tsx index 4b78edf..efbe376 100644 --- a/frontend/src/components/info-cards/InfoCards.tsx +++ b/frontend/src/components/info-cards/InfoCards.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { useLoaderData } from 'react-router-dom'; import { - BlocksInfoItem, MasterNode + BlocksInfoItem, MasterNode } from '@/components/blocks-info/blocks-info-item/BlocksInfoItem'; import BlocksInfo from '@/components/blocks-info/BlocksInfo'; import Card from '@/components/card/Card'; @@ -19,10 +19,19 @@ interface InfoCardsProps { setNextFetchRecentBlocksIndex: React.Dispatch<React.SetStateAction<number>>; recentBlocks: BlocksInfoItem[]; setRecentBlocks: React.Dispatch<React.SetStateAction<BlocksInfoItem[]>>; + isLoadingRecentBlocks: boolean; + setIsLoadingRecentBlocks: React.Dispatch<React.SetStateAction<boolean>>; } export default function InfoCards(props: InfoCardsProps) { - const { nextFetchRecentBlocksIndex, setNextFetchRecentBlocksIndex, recentBlocks, setRecentBlocks } = props; + const { + nextFetchRecentBlocksIndex + , setNextFetchRecentBlocksIndex + , recentBlocks + , setRecentBlocks + , isLoadingRecentBlocks + , setIsLoadingRecentBlocks + } = props; const loaderData = useLoaderData() as HomeLoaderData; const [veryFirstSubnetBlock] = useState(loaderData.blocks?.blocks[0].number); @@ -112,9 +121,12 @@ export default function InfoCards(props: InfoCardsProps) { <BlocksInfo title='Recent Blocks' data={recentBlocks} + setData={setRecentBlocks} fetchMoreData={fetchMoreRecentBlocks} - isReachApiEndOfRecentBlocks={isReachApiEndOfRecentBlocks} - isFetchingMoreRecentBlocks={isFetchingMoreRecentBlocks} + isReachApiEnd={isReachApiEndOfRecentBlocks} + isFetchingMore={isFetchingMoreRecentBlocks} + isLoading={isLoadingRecentBlocks} + setIsLoading={setIsLoadingRecentBlocks} enableInfinite /> </Card> diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index d6b0ff3..8412582 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -7,8 +7,8 @@ import { BlocksInfoItem } from '@/components/blocks-info/blocks-info-item/Blocks import InfoCards from '@/components/info-cards/InfoCards'; import BlockCards from '@/components/info-list/components/block-cards/BlockCards'; import { - BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, - StandardScreenBlockNumber, WideScreenBlockNumber + BlockSizeWithGap, FakedConfirmedBlockNumber, FakedNotConfirmedBlockNumber, + StandardScreenBlockNumber, WideScreenBlockNumber } from '@/constants/config'; import { baseUrl } from '@/constants/urls'; import { TimeContext } from '@/contexts/TimeContext'; @@ -88,6 +88,7 @@ export default function HomePage() { const [subnetBlocks, setSubnetBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.subnet.latestMinedBlock.number, loaderData.blocks?.subnet.latestCommittedBlock.number, blockNumber)); const [parentBlocks, setParentBlocks] = useState<Block[]>(getBlocks(loaderData.blocks?.checkpoint.latestSubmittedSubnetBlock.number, loaderData.blocks?.checkpoint.latestCommittedSubnetBlock.number, blockNumber)); const [recentBlocks, setRecentBlocks] = useState<BlocksInfoItem[]>(getSortedRecentBlocks(loaderData.blocks?.blocks)); + const [isLoadingRecentBlocks, setIsLoadingRecentBlocks] = useState(false); const [nextFetchRecentBlocksIndex, setNextFetchRecentBlocksIndex] = useState(loaderData.blocks ? loaderData.blocks.subnet.latestMinedBlock.number - 50 : 0); const { currentUnixTime } = useContext(TimeContext); @@ -124,7 +125,10 @@ export default function HomePage() { nextFetchRecentBlocksIndex={nextFetchRecentBlocksIndex} setNextFetchRecentBlocksIndex={setNextFetchRecentBlocksIndex} recentBlocks={recentBlocks} - setRecentBlocks={setRecentBlocks} /> + setRecentBlocks={setRecentBlocks} + isLoadingRecentBlocks={isLoadingRecentBlocks} + setIsLoadingRecentBlocks={setIsLoadingRecentBlocks} + /> </div> ); } From 5df358b997c3d95f36bba7b34389110de22764c3 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Fri, 28 Jul 2023 08:57:41 +1000 Subject: [PATCH 86/87] Fix loading status wording --- frontend/src/components/infinite-list/InfiniteList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/infinite-list/InfiniteList.tsx b/frontend/src/components/infinite-list/InfiniteList.tsx index 08261b2..ec84db7 100644 --- a/frontend/src/components/infinite-list/InfiniteList.tsx +++ b/frontend/src/components/infinite-list/InfiniteList.tsx @@ -47,8 +47,8 @@ export default function InfiniteList({ fetchData, children, isFetchingMore, isRe {children} {(isReachApiEnd || isFetchingMore) && ( <div className='text-bg-dark-800 dark:text-white p-5 pl-0'> - {isFetchingMore && <>Loading more data...</>} - {isReachApiEnd && <>The end of list...</>} + {isFetchingMore && !isReachApiEnd && <>Loading more data...</>} + {isReachApiEnd && <>The end of list</>} </div> )} <div ref={observerTarget}></div> From 353f410ad0f08cc806b4fd016f0b8d9d55530ee2 Mon Sep 17 00:00:00 2001 From: Choco <trust2065@gmail.com> Date: Mon, 31 Jul 2023 08:48:28 +1000 Subject: [PATCH 87/87] Fix TS errors --- .../src/services/grandmaster-manager/index.ts | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/frontend/src/services/grandmaster-manager/index.ts b/frontend/src/services/grandmaster-manager/index.ts index a7f21a5..4507b19 100644 --- a/frontend/src/services/grandmaster-manager/index.ts +++ b/frontend/src/services/grandmaster-manager/index.ts @@ -1,5 +1,6 @@ -import Web3 from "web3"; -import { ManagerError } from "@/services/grandmaster-manager/errors"; +import Web3 from 'web3'; + +import { ManagerError } from '@/services/grandmaster-manager/errors'; interface AccountDetails { accountAddress: string; @@ -13,17 +14,26 @@ interface CandidateDetails { address: string; delegation: number; rank: number; - status: 'MASTERNODE' | 'PROPOSED' | 'SLASHED' + status: 'MASTERNODE' | 'PROPOSED' | 'SLASHED'; } export class GrandMasterManager { - private web3Client; + private _web3Client: any; + + public get web3Client() { + return this._web3Client; + } + + public set web3Client(value) { + this._web3Client = value; + } + constructor() { const win = window as any; this.web3Client = new Web3(win.xdc ? win.xdc : win.ethereum); } - + /** * * This method will detect XDC-Pay and verify if customer has alraedy loggin. * @returns Account details will be returned if all good, otherwise, relevant error message and type will be returned such as WALLET_NOT_LOGIN @@ -36,37 +46,37 @@ export class GrandMasterManager { networkId: chainId, denom: "hxdc", rpcAddress: "https://devnetstats.apothem.network/subnet" - } + }; } - + /** * Remove a masternode from the manager view list * @param address The master node to be removed * @returns If transaction is successful, return. Otherwise, an error details will be returned */ - async removeMasterNode(address: string): Promise<true | ManagerError> { + async removeMasterNode(_address: string): Promise<true | ManagerError> { return true; } - + /** * Propose to add a new masternode for being a mining candidate from the next epoch * @param address The master node to be added * @returns If transaction is successful, return. Otherwise, an error details will be returned */ - async addNewMasterNode(address: string): Promise<true | ManagerError> { + async addNewMasterNode(_address: string): Promise<true | ManagerError> { return true; } - + /** * Change the voting/ranking power/order of a particular masternode. * @param address The targeted masternode * @param value The xdc value that will be applied to the targeted address. Postive number means increase power, negative means decrease the power * @returns If transaction is successful, return. Otherwise, an error details will be returned */ - async changeVote(address: string, value: number): Promise<true | ManagerError> { + async changeVote(_address: string, _value: number): Promise<true | ManagerError> { return true; } - + /** * A event listener on wallet account. If user switch to a different account, this method will update notify the provided handler * @param changeHandler The handler function to process when the accounts changed. The provided value will be the new wallet address. @@ -76,7 +86,7 @@ export class GrandMasterManager { // TODO: 1. Handle the account change via accountsChanged // TODO: 2. Handle the chain change via chainChanged. This could happen if switch from testnet to mainnet etc. } - + /** * A method to return subnet candidates and its status. * @returns The address and its current status and stake.