From ab87be173eb41c9ab51cd56de7dcc7e80ba2df0f Mon Sep 17 00:00:00 2001 From: KacperKoza343 Date: Wed, 21 Aug 2024 14:24:53 +0200 Subject: [PATCH 1/2] feat(app/home/leaderboard): integrate with api --- .../src/components/Home/Leaderboard.tsx | 639 ------------------ .../Home/Leaderboard/Leaderboard.tsx | 41 ++ .../Leaderboard/components/EntityIcon.tsx | 37 + .../components/ReputationLabel.tsx | 36 + .../Leaderboard/components/SelectNetwork.tsx | 54 ++ .../Leaderboard/components/Table/Table.tsx | 114 ++++ .../components/Table/TableHead.tsx | 121 ++++ .../Leaderboard/components/Table/sorting.ts | 44 ++ .../ui-2024/src/components/Home/index.ts | 2 +- .../src/components/NetworkIcon/index.tsx | 44 ++ .../dashboard/ui-2024/src/pages/Home/Home.tsx | 4 +- .../ui-2024/src/services/api-paths.ts | 6 + .../services/api/use-leaderboard-details.tsx | 56 ++ .../src/utils/hooks/use-leaderboard-search.ts | 29 + 14 files changed, 585 insertions(+), 642 deletions(-) delete mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/EntityIcon.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/ReputationLabel.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/SelectNetwork.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/sorting.ts create mode 100644 packages/apps/dashboard/ui-2024/src/components/NetworkIcon/index.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/utils/hooks/use-leaderboard-search.ts diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard.tsx deleted file mode 100644 index 7fe9d37c61..0000000000 --- a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard.tsx +++ /dev/null @@ -1,639 +0,0 @@ -import React, { Dispatch, SetStateAction, useMemo, useState } from 'react'; -import Select, { SelectChangeEvent } from '@mui/material/Select'; -import FormControl from '@mui/material/FormControl'; -import InputLabel from '@mui/material/InputLabel'; -import MenuItem from '@mui/material/MenuItem'; -import HumanIcon from '@components/Icons/HumanIcon'; -import EthereumIcon from '@components/Icons/EthereumIcon'; -import BinanceSmartChainIcon from '@components/Icons/BinanceSmartChainIcon'; -import PolygonIcon from '@components/Icons/PolygonIcon'; -import MoonbeamIcon from '@components/Icons/MoonbeamIcon'; -import MoonbaseAlphaIcon from '@components/Icons/MoonbaseAlphaIcon'; -import SvgIcon from '@mui/material/SvgIcon'; -import CeloIcon from '@assets/icons/celo.svg'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import Tooltip from '@mui/material/Tooltip'; -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; -import recording from '@assets/recording.png'; -import reputation from '@assets/reputation.png'; -import exchange from '@assets/exchange.png'; -import human from '@assets/human.png'; -import TableContainer from '@mui/material/TableContainer'; -import Paper from '@mui/material/Paper'; -import SimpleBar from 'simplebar-react'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import Grid from '@mui/material/Grid'; -import AbbreviateClipboard from '@components/SearchResults/AbbreviateClipboard'; -import { useNavigate } from 'react-router-dom'; -import { TablePagination } from '@mui/material'; - -type networkTypes = - | 'ethereum' - | 'goerli' - | 'binance' - | 'testnet' - | 'polygon' - | 'mumbai' - | 'moonbeam' - | 'alpha'; - -interface Item { - id: number; - role: string; - address: string; - stake: string; - network: networkTypes; - reputation: string; - operator: string; -} - -function createData( - id: number, - role: string, - address: string, - stake: string, - network: networkTypes, - reputation: string, - operator: string -) { - return { id, role, address, stake, network, reputation, operator }; -} - -const rows = [ - createData( - 1, - 'Element 1', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '1e-18 HMT', - 'ethereum', - 'Medium', - '1%' - ), - createData( - 2, - 'Element 2', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '1e-18 HMT', - 'goerli', - 'High', - '1%' - ), - createData( - 3, - 'Element 3', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '3e-18 HMT', - 'binance', - 'Medium', - '2%' - ), - createData( - 4, - 'Element 4', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '4e-18 HMT', - 'testnet', - 'Low', - '1%' - ), - createData( - 5, - 'Element 5', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '2e-18 HMT', - 'testnet', - 'Coming soon', - '5%' - ), - createData( - 6, - 'Element 6', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '2e-18 HMT', - 'mumbai', - 'Coming soon', - '5%' - ), - createData( - 7, - 'Element 7', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '2e-18 HMT', - 'moonbeam', - 'Coming soon', - '5%' - ), - createData( - 8, - 'Element 8', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '2e-18 HMT', - 'alpha', - 'Coming soon', - '5%' - ), - createData( - 9, - 'Element 9', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '2e-18 HMT', - 'alpha', - 'Coming soon', - '5%' - ), - createData( - 10, - 'Element 10', - '0x67499f129433b82e5a4e412143a395e032e76c0dc0f83606031', - '2e-18 HMT', - 'alpha', - 'Coming soon', - '5%' - ), -]; - -function descendingComparator(a: T, b: T, orderBy: keyof T) { - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; -} - -type Order = 'asc' | 'desc'; - -function getComparator( - order: Order, - orderBy: Key -): ( - a: { [key in Key]: number | string }, - b: { [key in Key]: number | string } -) => number { - return order === 'desc' - ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy); -} - -function stableSort( - array: readonly T[], - comparator: (a: T, b: T) => number -) { - const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); - stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]); - if (order !== 0) { - return order; - } - return a[1] - b[1]; - }); - return stabilizedThis.map((el) => el[0]); -} - -const SelectNetwork = ({ - value, - setNetwork, -}: { - value: string; - setNetwork: Dispatch>; -}) => { - const handleChange = (event: SelectChangeEvent) => { - setNetwork(event.target.value as networkTypes); - }; - - return ( - - By Network - - - ); -}; - -interface EnhancedTableHeadProps { - onRequestSort: ( - event: React.MouseEvent, - property: keyof Item - ) => void; - order: Order; - orderBy: string; - rowCount: number; - network: string; - setNetwork: Dispatch>; -} - -const EnhancedTableHead = ({ - orderBy, - order, - onRequestSort, - network, - setNetwork, -}: EnhancedTableHeadProps) => { - const createSortHandler = - (property: keyof Item) => (event: React.MouseEvent) => { - onRequestSort(event, property); - }; - - return ( - - - - - ROLE - - - - -
- - - - ADDRESS -
-
-
- - -
- - - - STAKE -
-
-
- - - NETWORK - - - -
- - - - REPUTATION SCORE -
-
-
- - - OPERATOR FEE - - -
-
- ); -}; - -const renderIcon: React.FC = (item) => { - let src = ''; - switch (item.role) { - case 'Job Launcher': - return ( -
- JL -
- ); - case 'Recording Oracle': - src = recording; - break; - case 'Reputation Oracle': - src = reputation; - break; - case 'Exchange Oracle': - src = exchange; - break; - case 'HUMAN App': - src = human; - break; - default: - src = human; - break; - } - - return ( -
- logo -
- ); -}; - -const Leaderboard = ({ pagination = false }: { pagination?: boolean }) => { - const navigate = useNavigate(); - const [network, setNetwork] = useState('all'); - const [order, setOrder] = useState('asc'); - const [orderBy, setOrderBy] = useState('role'); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(5); - - const handleRequestSort = ( - _event: React.MouseEvent, - property: keyof Item - ) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - // Avoid a layout jump when reaching the last page with empty rows. - const emptyRows = - page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0; - - const visibleRows = useMemo(() => { - let filteredRows = rows; - if (network !== 'all') { - filteredRows = rows.filter((elem) => elem.network === network); - } - - const sortedRows = stableSort(filteredRows, getComparator(order, orderBy)); - - return sortedRows.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ); - }, [order, orderBy, page, rowsPerPage, network]); - - const renderNetworkDetails = (network: networkTypes) => { - const networkDetails = { - ethereum: { - title: 'Ethereum', - icon: , - }, - goerli: { - title: 'Ethereum Goreli', - icon: , - }, - binance: { - title: 'Binance Smart Chain', - icon: , - }, - testnet: { - title: 'Smart Chain (Testnet)', - icon: , - }, - polygon: { - title: 'Polygon', - icon: , - }, - mumbai: { - title: 'Polygon Mumbai', - icon: , - }, - moonbeam: { - title: 'Moonbeam', - icon: , - }, - alpha: { - title: 'Moonbase Alpha', - icon: , - }, - celo: { - title: 'Celo', - icon: , - }, - alfajores: { - title: 'alfajores', - icon: , - }, - }; - - if (network === undefined) { - return null; - } - - return ( - <> - {networkDetails[network].icon} - {networkDetails[network].title} - - ); - }; - - const renderReputation: React.FC = (item) => { - switch (item.reputation) { - case 'Medium': - return ( -
- {item.reputation} -
- ); - case 'High': - return ( -
- {item.reputation} -
- ); - case 'Low': - return ( -
- {item.reputation} -
- ); - case 'Coming soon': - return ( -
- {item.reputation} -
- ); - default: - return ( -
- {item.reputation} -
- ); - } - }; - - const handleChangePage = (_event: unknown, newPage: number) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = ( - event: React.ChangeEvent - ) => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - - return ( - <> - -
- -
- - - - - {visibleRows.map((row) => ( - navigate(`/search/${row.address}`)} - key={row.id} - className="home-page-table-row" - > - - {renderIcon(row)} - {row.role} - - - - - - - {row.stake} - - - {renderNetworkDetails(row.network)} - - - {renderReputation(row)} - {row.operator} - - ))} - {emptyRows > 0 && ( - - - - )} - -
-
-
- {pagination && ( - - )} - - ); -}; - -export default Leaderboard; diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx new file mode 100644 index 0000000000..e4191d37e2 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx @@ -0,0 +1,41 @@ +import TableContainer from '@mui/material/TableContainer'; +import Paper from '@mui/material/Paper'; +import SimpleBar from 'simplebar-react'; +import { Table } from '@components/Home/Leaderboard/components/Table/Table'; +import { SelectNetwork } from '@components/Home/Leaderboard/components/SelectNetwork'; +import { useLeaderboardDetails } from '@services/api/use-leaderboard-details'; +import Loader from '@components/Loader'; +import Stack from '@mui/material/Stack'; +import { handleErrorMessage } from '@services/handle-error-message'; + +const Leaderboard = () => { + const { data, status, error } = useLeaderboardDetails(); + + if (status === 'pending') { + return ; + } + + if (status === 'error') { + return ( + {handleErrorMessage(error)} + ); + } + + return ( + <> + +
+ +
+ + + + + + ); +}; + +export default Leaderboard; diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/EntityIcon.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/EntityIcon.tsx new file mode 100644 index 0000000000..b37e8b3ca1 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/EntityIcon.tsx @@ -0,0 +1,37 @@ +import recording from '@assets/recording.png'; +import reputation from '@assets/reputation.png'; +import exchange from '@assets/exchange.png'; +import human from '@assets/human.png'; + +export const EntityIcon: React.FC<{ role: string }> = ({ role }) => { + let src = ''; + switch (role) { + case 'Job Launcher': + return ( +
+ JL +
+ ); + case 'Recording Oracle': + src = recording; + break; + case 'Reputation Oracle': + src = reputation; + break; + case 'Exchange Oracle': + src = exchange; + break; + case 'HUMAN App': + src = human; + break; + default: + src = human; + break; + } + + return ( +
+ logo +
+ ); +}; diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/ReputationLabel.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/ReputationLabel.tsx new file mode 100644 index 0000000000..293c23ec05 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/ReputationLabel.tsx @@ -0,0 +1,36 @@ +export const ReputationLabel: React.FC<{ reputation: string }> = ({ + reputation, +}) => { + switch (reputation) { + case 'High': + return ( +
+ {reputation} +
+ ); + case 'Medium': + return ( +
+ {reputation} +
+ ); + case 'Low': + return ( +
+ {reputation} +
+ ); + case 'Coming soon': + return ( +
+ {reputation} +
+ ); + default: + return ( +
+ Coming soon +
+ ); + } +}; diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/SelectNetwork.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/SelectNetwork.tsx new file mode 100644 index 0000000000..c19c851b40 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/SelectNetwork.tsx @@ -0,0 +1,54 @@ +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import HumanIcon from '@components/Icons/HumanIcon'; +import { + leaderboardSearchSelectConfig, + useLeaderboardSearch, +} from '@utils/hooks/use-leaderboard-search'; +import { NetworkIcon } from '@components/NetworkIcon'; + +export const SelectNetwork = () => { + const { + setChainId, + filterParams: { chainId }, + } = useLeaderboardSearch(); + const handleChange = (event: SelectChangeEvent) => { + const value = event.target.value; + if (typeof value === 'number') { + setChainId(value); + } + }; + + return ( + + By Network + + labelId="network-select-label" + id="network-select" + value={chainId} + label="By Network" + onChange={handleChange} + > + {leaderboardSearchSelectConfig.map((selectItem) => { + if ('allNetworksId' in selectItem) { + return ( + + + All Networks + + ); + } + + return ( + + + {selectItem.name} + + ); + })} + + + ); +}; diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx new file mode 100644 index 0000000000..a986b7eb3c --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx @@ -0,0 +1,114 @@ +import React, { useMemo, useState } from 'react'; +import TableRow from '@mui/material/TableRow'; +import TableCell, { tableCellClasses } from '@mui/material/TableCell'; +import MuiTable from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import Grid from '@mui/material/Grid'; +import AbbreviateClipboard from '@components/SearchResults/AbbreviateClipboard'; +import { useNavigate } from 'react-router-dom'; +import { ReputationLabel } from '@components/Home/Leaderboard/components/ReputationLabel'; +import { EntityIcon } from '@components/Home/Leaderboard/components/EntityIcon'; +import { TableHead } from '@components/Home/Leaderboard/components/Table/TableHead'; +import { LeaderBoardData } from '@services/api/use-leaderboard-details'; +import { + getComparator, + Order, + SortableFieldsInLeaderBoardData, + stableSort, +} from '@components/Home/Leaderboard/components/Table/sorting'; +import { useLeaderboardSearch } from '@utils/hooks/use-leaderboard-search'; +import { getNetwork } from '@utils/config/networks'; +import { NetworkIcon } from '@components/NetworkIcon'; +import { colorPalette } from '@assets/styles/color-palette'; + +export const Table = ({ data }: { data: LeaderBoardData }) => { + const navigate = useNavigate(); + const { + filterParams: { chainId }, + } = useLeaderboardSearch(); + const [order, setOrder] = useState('asc'); + const [orderBy, setOrderBy] = + useState('role'); + + const handleRequestSort = ( + _event: React.MouseEvent, + property: SortableFieldsInLeaderBoardData + ) => { + const isAsc = orderBy === property && order === 'asc'; + setOrder(isAsc ? 'desc' : 'asc'); + setOrderBy(property); + }; + + const visibleRows = useMemo(() => { + let filteredRows = data; + if (chainId !== -1) { + filteredRows = data.filter((elem) => elem.chainId === chainId); + } + + return stableSort(filteredRows, getComparator(order, orderBy)); + }, [chainId, data, order, orderBy]); + + return ( + + + + {visibleRows.map((row) => ( + navigate(`/search/${row.chainId}/${row.address}`)} + key={row.address} + className="home-page-table-row" + sx={{ + ':hover': { + backgroundColor: colorPalette.overlay.light, + }, + }} + > + + + {row.role} + + + + + + + {row.amountStaked} HMT + + + + {getNetwork(row.chainId)?.name} + + + + + + {row.fee}% + + ))} + + + ); +}; diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx new file mode 100644 index 0000000000..8774c22d60 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import MuiTableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import TableCell from '@mui/material/TableCell'; +import TableSortLabel from '@mui/material/TableSortLabel'; +import Tooltip from '@mui/material/Tooltip'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; + +import { + Order, + SortableFieldsInLeaderBoardData, +} from '@components/Home/Leaderboard/components/Table/sorting'; +import { SelectNetwork } from '@components/Home/Leaderboard/components/SelectNetwork'; + +interface TableHeadProps { + onRequestSort: ( + event: React.MouseEvent, + property: SortableFieldsInLeaderBoardData + ) => void; + order: Order; + orderBy: string; + rowCount: number; +} + +export const TableHead = ({ + orderBy, + order, + onRequestSort, +}: TableHeadProps) => { + const createSortHandler = + (property: SortableFieldsInLeaderBoardData) => + (event: React.MouseEvent) => { + onRequestSort(event, property); + }; + + return ( + + + + + ROLE + + + + +
+ + + + ADDRESS +
+
+
+ + +
+ + + + STAKE +
+
+
+ + + NETWORK + + + +
+ + + + REPUTATION SCORE +
+
+
+ + + OPERATOR FEE + + +
+
+ ); +}; diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/sorting.ts b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/sorting.ts new file mode 100644 index 0000000000..7b5f52e5fe --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/sorting.ts @@ -0,0 +1,44 @@ +import { LeaderBoardData } from '@services/api/use-leaderboard-details'; + +export type Order = 'asc' | 'desc'; +export type SortableFieldsInLeaderBoardData = Exclude< + keyof LeaderBoardData[number], + 'jobTypes' | 'url' +>; + +function descendingComparator(a: T, b: T, orderBy: keyof T) { + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + return 0; +} + +export function getComparator( + order: Order, + orderBy: Key +): ( + a: { [key in Key]: number | string }, + b: { [key in Key]: number | string } +) => number { + return order === 'desc' + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +} + +export function stableSort( + array: readonly T[], + comparator: (a: T, b: T) => number +) { + const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) { + return order; + } + return a[1] - b[1]; + }); + return stabilizedThis.map((el) => el[0]); +} diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/index.ts b/packages/apps/dashboard/ui-2024/src/components/Home/index.ts index b493d6359a..da3976ed97 100644 --- a/packages/apps/dashboard/ui-2024/src/components/Home/index.ts +++ b/packages/apps/dashboard/ui-2024/src/components/Home/index.ts @@ -1,3 +1,3 @@ export * from './SmallGraph'; export * from './GraphSwiper'; -export * from './Leaderboard'; +export * from './Leaderboard/Leaderboard'; diff --git a/packages/apps/dashboard/ui-2024/src/components/NetworkIcon/index.tsx b/packages/apps/dashboard/ui-2024/src/components/NetworkIcon/index.tsx new file mode 100644 index 0000000000..7cfb88fc43 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/components/NetworkIcon/index.tsx @@ -0,0 +1,44 @@ +import EthereumIcon from '@components/Icons/EthereumIcon'; +import BinanceSmartChainIcon from '@components/Icons/BinanceSmartChainIcon'; +import PolygonIcon from '@components/Icons/PolygonIcon'; +import MoonbeamIcon from '@components/Icons/MoonbeamIcon'; +import MoonbaseAlphaIcon from '@components/Icons/MoonbaseAlphaIcon'; +import CeloIcon from '@assets/icons/celo.svg'; +import SvgIcon from '@mui/material/SvgIcon'; +import HumanIcon from '@components/Icons/HumanIcon'; + +export const NetworkIcon = ({ chainId }: { chainId: number }) => { + const icon = (() => { + switch (chainId) { + case 1: + case 4: + case 5: + case 11155111: + return ; + case 56: + case 97: + return ; + case 137: + case 80001: + case 80002: + return ; + case 1284: + return ; + case 1287: + return ; + case 42220: + case 44787: + return ; + case 43113: // Avalanche Fuji Testnet + case 43114: // Avalanche C-Chain + case 195: // X Layer Testnet + case 196: // X Layer Mainnet + case 1338: // Elysium Testnet + return ; + default: + return ; + } + })(); + + return {icon}; +}; diff --git a/packages/apps/dashboard/ui-2024/src/pages/Home/Home.tsx b/packages/apps/dashboard/ui-2024/src/pages/Home/Home.tsx index 81428282c8..ccce7710d3 100644 --- a/packages/apps/dashboard/ui-2024/src/pages/Home/Home.tsx +++ b/packages/apps/dashboard/ui-2024/src/pages/Home/Home.tsx @@ -17,7 +17,7 @@ import bing from '@assets/bing.png'; import coinlist from '@assets/coinlist.png'; import lbank from '@assets/lbank.png'; import cup from '@assets/cup.png'; -import Leaderboard from '@components/Home/Leaderboard'; +import Leaderboard from '@components/Home/Leaderboard/Leaderboard'; import GraphSwiper from '@components/Home/GraphSwiper'; import { HMTPrice } from '@pages/Home/HMTPrice'; import { TotalNumberOfTasks } from '@pages/Home/TotalNumberOfTasks'; @@ -134,7 +134,7 @@ const Home: React.FC = () => { img={cup} /> - + ); }; diff --git a/packages/apps/dashboard/ui-2024/src/services/api-paths.ts b/packages/apps/dashboard/ui-2024/src/services/api-paths.ts index 6f34497662..608ab85c2c 100644 --- a/packages/apps/dashboard/ui-2024/src/services/api-paths.ts +++ b/packages/apps/dashboard/ui-2024/src/services/api-paths.ts @@ -14,6 +14,12 @@ export const apiPaths = { hcaptchaStatsDaily: { path: '/stats/hcaptcha/daily', }, + leaderboardDetails: { + path: '/details/leaders', + }, + leaderboardDetailsAll: { + path: '/details/leaders/all', + }, addressDetails: { path: '/details', }, diff --git a/packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx b/packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx new file mode 100644 index 0000000000..7cedf4abf1 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx @@ -0,0 +1,56 @@ +import { useQuery } from '@tanstack/react-query'; +import { z } from 'zod'; +import { httpService } from '../http-service'; +import { apiPaths } from '../api-paths'; +import { validateResponse } from '@services/validate-response'; +import { useLeaderboardSearch } from '@utils/hooks/use-leaderboard-search'; + +const leaderBoardEntity = z.object({ + address: z.string(), + role: z.string(), + amountStaked: z.string().transform((value, ctx) => { + const valueAsNumber = Number(value); + + if (Number.isNaN(valueAsNumber)) { + ctx.addIssue({ + path: ['amountStaked'], + code: z.ZodIssueCode.custom, + }); + } + + return valueAsNumber; + }), + reputation: z.union([z.string(), z.number()]).transform((value) => { + return `${value}`; + }), + fee: z.number(), + jobTypes: z.array(z.string()), + url: z.string(), + chainId: z.number(), +}); + +const leaderBoardSuccessResponseSchema = z.array(leaderBoardEntity); +export type LeaderBoardEntity = z.infer; +export type LeaderBoardData = z.infer; + +export function useLeaderboardDetails() { + const { + filterParams: { chainId }, + } = useLeaderboardSearch(); + + return useQuery({ + queryFn: async () => { + const { data } = await httpService.get(apiPaths.leaderboardDetails.path, { + params: { chainId }, + }); + + const validResponse = validateResponse( + data, + leaderBoardSuccessResponseSchema + ); + + return validResponse; + }, + queryKey: ['useLeaderboardDetails', chainId], + }); +} diff --git a/packages/apps/dashboard/ui-2024/src/utils/hooks/use-leaderboard-search.ts b/packages/apps/dashboard/ui-2024/src/utils/hooks/use-leaderboard-search.ts new file mode 100644 index 0000000000..67c7b4e00d --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/utils/hooks/use-leaderboard-search.ts @@ -0,0 +1,29 @@ +import { networks } from '@utils/config/networks'; +import { create } from 'zustand'; +import type { Chain } from 'viem/chains'; + +export const leaderboardSearchSelectConfig: ( + | Chain + | { name: 'All Networks'; allNetworksId: -1 } +)[] = [{ name: 'All Networks', allNetworksId: -1 }, ...networks]; + +export interface LeaderboardSearchStore { + filterParams: { + chainId: number; + }; + setChainId: (chainId: number) => void; +} + +export const useLeaderboardSearch = create((set) => ({ + filterParams: { + chainId: -1, + }, + setChainId: (chainId) => { + set((state) => ({ + filterParams: { + ...state.filterParams, + chainId, + }, + })); + }, +})); From 6daeaed15ac24ef24d155811ebb0832317fccc9c Mon Sep 17 00:00:00 2001 From: KacperKoza343 <165884432+KacperKoza343@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:15:12 +0200 Subject: [PATCH 2/2] feat(app/leaderboard-page): add page (#221) --- packages/apps/dashboard/ui-2024/src/App.tsx | 2 + .../ui-2024/src/assets/styles/_home-page.scss | 6 - .../src/assets/styles/color-palette.ts | 3 + .../Home/Leaderboard/Leaderboard.tsx | 49 +++-- .../Leaderboard/components/Table/Table.tsx | 186 +++++++++++++----- .../components/Table/TableHead.tsx | 22 ++- .../SearchResults/AbbreviateClipboard.tsx | 4 +- .../ui-2024/src/pages/Leaderboard/index.tsx | 35 ++++ .../api/use-hcaptcha-general-stats.tsx | 1 - .../services/api/use-leaderboard-details.tsx | 2 +- .../ui-2024/src/utils/hooks/use-is-mobile.tsx | 15 ++ 11 files changed, 255 insertions(+), 70 deletions(-) create mode 100644 packages/apps/dashboard/ui-2024/src/pages/Leaderboard/index.tsx create mode 100644 packages/apps/dashboard/ui-2024/src/utils/hooks/use-is-mobile.tsx diff --git a/packages/apps/dashboard/ui-2024/src/App.tsx b/packages/apps/dashboard/ui-2024/src/App.tsx index d8e3122f4d..26d75bf7be 100644 --- a/packages/apps/dashboard/ui-2024/src/App.tsx +++ b/packages/apps/dashboard/ui-2024/src/App.tsx @@ -3,12 +3,14 @@ import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import Home from '@pages/Home'; import Graph from '@pages/Graph'; import SearchResults from '@pages/SearchResults'; +import { LeaderBoard } from '@pages/Leaderboard'; const App: React.FC = () => { return ( } /> + } /> } /> } /> Not find} /> diff --git a/packages/apps/dashboard/ui-2024/src/assets/styles/_home-page.scss b/packages/apps/dashboard/ui-2024/src/assets/styles/_home-page.scss index 8f94019b8f..23697e856a 100644 --- a/packages/apps/dashboard/ui-2024/src/assets/styles/_home-page.scss +++ b/packages/apps/dashboard/ui-2024/src/assets/styles/_home-page.scss @@ -106,12 +106,6 @@ padding: 32px 16px; font-size: 16px; } - td:first-child{ - font-size: 20px; - display: flex; - gap: 16px; - align-items: center; - } .icon-table{ background-color: $groundwaterOpacity; diff --git a/packages/apps/dashboard/ui-2024/src/assets/styles/color-palette.ts b/packages/apps/dashboard/ui-2024/src/assets/styles/color-palette.ts index f4f0ec8500..e36b7a32c0 100644 --- a/packages/apps/dashboard/ui-2024/src/assets/styles/color-palette.ts +++ b/packages/apps/dashboard/ui-2024/src/assets/styles/color-palette.ts @@ -1,5 +1,8 @@ export const colorPalette = { white: '#F9FAFF', + whiteBackground: '#FFF', + whiteSolid: '#F6F5FC', + skyOpacity: '#DADEF0CC', primary: { main: '#320a8d', light: '#320a8d', diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx index e4191d37e2..18f1667114 100644 --- a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/Leaderboard.tsx @@ -4,35 +4,56 @@ import SimpleBar from 'simplebar-react'; import { Table } from '@components/Home/Leaderboard/components/Table/Table'; import { SelectNetwork } from '@components/Home/Leaderboard/components/SelectNetwork'; import { useLeaderboardDetails } from '@services/api/use-leaderboard-details'; -import Loader from '@components/Loader'; -import Stack from '@mui/material/Stack'; -import { handleErrorMessage } from '@services/handle-error-message'; +import Box from '@mui/material/Box'; +import { useNavigate } from 'react-router-dom'; +import { colorPalette } from '@assets/styles/color-palette'; const Leaderboard = () => { const { data, status, error } = useLeaderboardDetails(); + const navigate = useNavigate(); - if (status === 'pending') { - return ; - } - - if (status === 'error') { - return ( - {handleErrorMessage(error)} - ); - } + const isMoreThatFiveEntries = data?.length && data.length > 5; return ( <>
-
+
+ {isMoreThatFiveEntries ? ( + { + navigate('/leaderboard'); + }} + > + View All + + ) : null} ); diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx index a986b7eb3c..d119588495 100644 --- a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/Table.tsx @@ -20,9 +20,43 @@ import { useLeaderboardSearch } from '@utils/hooks/use-leaderboard-search'; import { getNetwork } from '@utils/config/networks'; import { NetworkIcon } from '@components/NetworkIcon'; import { colorPalette } from '@assets/styles/color-palette'; +import { Typography } from '@mui/material'; +import Stack from '@mui/material/Stack'; +import { handleErrorMessage } from '@services/handle-error-message'; +import Loader from '@components/Loader'; +import { useBreakPoints } from '@utils/hooks/use-is-mobile'; -export const Table = ({ data }: { data: LeaderBoardData }) => { +const TableBodyWrapper = ({ children }: { children: JSX.Element | string }) => { + return ( + + {children} + + ); +}; + +export const Table = ({ + data = [], + status, + error, +}: { + data: LeaderBoardData | undefined; + status: 'success' | 'error' | 'pending'; + error: unknown; +}) => { const navigate = useNavigate(); + const { mobile } = useBreakPoints(); + const { filterParams: { chainId }, } = useLeaderboardSearch(); @@ -44,14 +78,18 @@ export const Table = ({ data }: { data: LeaderBoardData }) => { if (chainId !== -1) { filteredRows = data.filter((elem) => elem.chainId === chainId); } - return stableSort(filteredRows, getComparator(order, orderBy)); }, [chainId, data, order, orderBy]); + const tableIsEmpty = status === 'success' && visibleRows.length === 0; + const tableMinHeight = status === 'success' && !tableIsEmpty ? 'unset' : 400; + return ( { orderBy={orderBy} rowCount={data.length} /> - - {visibleRows.map((row) => ( - navigate(`/search/${row.chainId}/${row.address}`)} - key={row.address} - className="home-page-table-row" - sx={{ - ':hover': { - backgroundColor: colorPalette.overlay.light, - }, - }} - > - - - {row.role} - - - - - - - {row.amountStaked} HMT - - + {status === 'pending' ? ( + + + + ) : null} + + {status === 'error' ? ( + {handleErrorMessage(error)} + ) : null} + + {tableIsEmpty ? ( + No data + ) : ( + <> + {visibleRows.map((row, index) => ( + + navigate(`/search/${row.chainId}/${row.address}`, { + preventScrollReset: false, + }) + } + key={row.address + index} + className="home-page-table-row" + sx={{ + paddingTop: '1px', + ':hover': { + backgroundColor: colorPalette.overlay.light, + }, + }} > - - {getNetwork(row.chainId)?.name} - - - - - - {row.fee}% - - ))} + {mobile.isMobile ? null : ( + + {index + 1} + + )} + + + {mobile.isMobile ? null : } + + {row.role} + + + + + + + + + {row.amountStaked} HMT + + + + {getNetwork(row.chainId)?.name} + + + + + + {row.fee}% + + ))} + + )} ); diff --git a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx index 8774c22d60..483933142f 100644 --- a/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx +++ b/packages/apps/dashboard/ui-2024/src/components/Home/Leaderboard/components/Table/TableHead.tsx @@ -11,6 +11,8 @@ import { SortableFieldsInLeaderBoardData, } from '@components/Home/Leaderboard/components/Table/sorting'; import { SelectNetwork } from '@components/Home/Leaderboard/components/SelectNetwork'; +import { colorPalette } from '@assets/styles/color-palette'; +import { useBreakPoints } from '@utils/hooks/use-is-mobile'; interface TableHeadProps { onRequestSort: ( @@ -27,6 +29,7 @@ export const TableHead = ({ order, onRequestSort, }: TableHeadProps) => { + const { mobile } = useBreakPoints(); const createSortHandler = (property: SortableFieldsInLeaderBoardData) => (event: React.MouseEvent) => { @@ -35,8 +38,23 @@ export const TableHead = ({ return ( - - + + {mobile.isMobile ? null : } + ( - {abbreviateValue(value)} + + {abbreviateValue(value)} + { navigator.clipboard.writeText(value); diff --git a/packages/apps/dashboard/ui-2024/src/pages/Leaderboard/index.tsx b/packages/apps/dashboard/ui-2024/src/pages/Leaderboard/index.tsx new file mode 100644 index 0000000000..b04b1c8c62 --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/pages/Leaderboard/index.tsx @@ -0,0 +1,35 @@ +import Breadcrumbs from '@components/Breadcrumbs'; +import PageWrapper from '@components/PageWrapper'; +import ShadowIcon from '@components/ShadowIcon'; +import cup from '@assets/cup.png'; +import { useLeaderboardDetails } from '@services/api/use-leaderboard-details'; +import TableContainer from '@mui/material/TableContainer'; +import { SelectNetwork } from '@components/Home/Leaderboard/components/SelectNetwork'; +import SimpleBar from 'simplebar-react'; +import { Table } from '@components/Home/Leaderboard/components/Table/Table'; +import Paper from '@mui/material/Paper'; + +export const LeaderBoard = () => { + const { data, status, error } = useLeaderboardDetails(); + return ( + + + + +
+ +
+ +
+ + + + ); +}; diff --git a/packages/apps/dashboard/ui-2024/src/services/api/use-hcaptcha-general-stats.tsx b/packages/apps/dashboard/ui-2024/src/services/api/use-hcaptcha-general-stats.tsx index 55721f3823..d1d66fa73d 100644 --- a/packages/apps/dashboard/ui-2024/src/services/api/use-hcaptcha-general-stats.tsx +++ b/packages/apps/dashboard/ui-2024/src/services/api/use-hcaptcha-general-stats.tsx @@ -5,7 +5,6 @@ import { apiPaths } from '../api-paths'; import { validateResponse } from '@services/validate-response'; const successHcaptchaGeneralStatsResponseSchema = z.object({ - served: z.number(), solved: z.number(), }); diff --git a/packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx b/packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx index 7cedf4abf1..e3261251c5 100644 --- a/packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx +++ b/packages/apps/dashboard/ui-2024/src/services/api/use-leaderboard-details.tsx @@ -18,7 +18,7 @@ const leaderBoardEntity = z.object({ }); } - return valueAsNumber; + return valueAsNumber / 10 ** 18; }), reputation: z.union([z.string(), z.number()]).transform((value) => { return `${value}`; diff --git a/packages/apps/dashboard/ui-2024/src/utils/hooks/use-is-mobile.tsx b/packages/apps/dashboard/ui-2024/src/utils/hooks/use-is-mobile.tsx new file mode 100644 index 0000000000..86c07335cc --- /dev/null +++ b/packages/apps/dashboard/ui-2024/src/utils/hooks/use-is-mobile.tsx @@ -0,0 +1,15 @@ +import useMediaQuery from '@mui/material/useMediaQuery'; + +const breakpoints = { + mobile: `(max-width: 1100px)`, +}; + +export function useBreakPoints() { + const matchesMobile = useMediaQuery(breakpoints.mobile); + return { + mobile: { + isMobile: matchesMobile, + mediaQuery: `@media ${breakpoints.mobile}`, + }, + }; +}