From 4ec7e7d0f1808a39f0c54b2708250a8b683d4c85 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 9 Mar 2024 16:18:06 -0500 Subject: [PATCH] Finish delegatee table --- public/logos/delegatees/default.svg | 2 +- src/components/icons/Identicon.tsx | 31 +++++++++++++++++++ src/components/logos/SocialLinkLogo.tsx | 2 +- src/config/delegates.json | 12 +++---- src/features/delegation/DelegateeLogo.tsx | 31 +++++++++++++++++++ src/features/delegation/DelegateesTable.tsx | 22 ++++++++----- src/features/delegation/DelegationsTable.tsx | 4 +-- src/features/delegation/delegateeMetadata.ts | 21 +++++++++++++ src/features/delegation/types.ts | 2 +- src/features/delegation/useDelegatees.ts | 21 +++---------- .../validators/ValidatorGroupLogo.tsx | 24 ++------------ 11 files changed, 115 insertions(+), 57 deletions(-) create mode 100644 src/features/delegation/DelegateeLogo.tsx create mode 100644 src/features/delegation/delegateeMetadata.ts diff --git a/public/logos/delegatees/default.svg b/public/logos/delegatees/default.svg index 46d1a75..59fc8b5 100644 --- a/public/logos/delegatees/default.svg +++ b/public/logos/delegatees/default.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/src/components/icons/Identicon.tsx b/src/components/icons/Identicon.tsx index bb7bc60..d04fdfd 100644 --- a/src/components/icons/Identicon.tsx +++ b/src/components/icons/Identicon.tsx @@ -1,5 +1,8 @@ import jazzicon from '@metamask/jazzicon'; +import Image from 'next/image'; import { CSSProperties, PureComponent } from 'react'; +import { Circle } from 'src/components/icons/Circle'; +import { ZERO_ADDRESS } from 'src/config/consts'; import { isValidAddress, normalizeAddress } from 'src/utils/addresses'; type Props = { @@ -38,3 +41,31 @@ export class Identicon extends PureComponent { ); } } + +export function ImageOrIdenticon({ + imgSrc, + address, + size, +}: { + imgSrc?: string; + address: Address; + size: number; +}) { + return ( + <> + {imgSrc ? ( + + ) : !address || address === ZERO_ADDRESS ? ( + + ) : ( + + )} + + ); +} diff --git a/src/components/logos/SocialLinkLogo.tsx b/src/components/logos/SocialLinkLogo.tsx index 06587ab..16b4fff 100644 --- a/src/components/logos/SocialLinkLogo.tsx +++ b/src/components/logos/SocialLinkLogo.tsx @@ -26,7 +26,7 @@ export function SocialLinkLogo({ href, svgProps, type, className, size = 18 }: P if (!Logo) throw new Error(`No logo for type ${type}`); return ( - + e.stopPropagation()}> ); diff --git a/src/config/delegates.json b/src/config/delegates.json index d26de86..d03e1ec 100644 --- a/src/config/delegates.json +++ b/src/config/delegates.json @@ -1,15 +1,15 @@ -[ - { +{ + "0xef268b5C05452D63a17Da12f562368e88a036Ef1": { "name": "Celo Whale", "address": "0xef268b5C05452D63a17Da12f562368e88a036Ef1", "logoUri": "/logos/delegatees/default.svg", "date": "2024-03-08", "links": { - "website": "https://celowhale.com", - "twitter": "https://twitter.com/celowhale", - "github": "https://twitter.com/celowhale" + "website": "https://www.google.com/search?q=whale", + "twitter": "https://twitter.com", + "github": "https://github.com" }, "interests": ["Fish", "Water", "Swimming"], "description": "An unknown Celo whale account. One of the largest CELO holders in the world." } -] +} diff --git a/src/features/delegation/DelegateeLogo.tsx b/src/features/delegation/DelegateeLogo.tsx new file mode 100644 index 0000000..9651431 --- /dev/null +++ b/src/features/delegation/DelegateeLogo.tsx @@ -0,0 +1,31 @@ +import { ImageOrIdenticon } from 'src/components/icons/Identicon'; +import { getDelegateeMetadata } from 'src/features/delegation/delegateeMetadata'; +import { shortenAddress } from 'src/utils/addresses'; + +export function DelegateeLogo({ address, size }: { address: Address; size: number }) { + const metadata = getDelegateeMetadata(); + const imgSrc = metadata[address]?.logoUri; + return ; +} + +export function DelegateeLogoAndName({ + address, + name, + size = 30, + className, +}: { + address: Address; + name?: string; + size?: number; + className?: string; +}) { + return ( +
+ +
+ {name || 'Unknown Delegate'} + {shortenAddress(address)} +
+
+ ); +} diff --git a/src/features/delegation/DelegateesTable.tsx b/src/features/delegation/DelegateesTable.tsx index e940a6a..613d3bf 100644 --- a/src/features/delegation/DelegateesTable.tsx +++ b/src/features/delegation/DelegateesTable.tsx @@ -14,9 +14,11 @@ import { useEffect, useMemo, useState } from 'react'; import { TabHeaderButton } from 'src/components/buttons/TabHeaderButton'; import { TableSortChevron } from 'src/components/icons/TableSortChevron'; import { SearchField } from 'src/components/input/SearchField'; +import { SocialLinkLogo } from 'src/components/logos/SocialLinkLogo'; import { formatNumberString } from 'src/components/numbers/Amount'; +import { SocialLinkType } from 'src/config/types'; +import { DelegateeLogoAndName } from 'src/features/delegation/DelegateeLogo'; import { Delegatee } from 'src/features/delegation/types'; -import { ValidatorGroupLogoAndName } from 'src/features/validators/ValidatorGroupLogo'; import { useIsMobile } from 'src/styles/mediaQueries'; const DESKTOP_ONLY_COLUMNS = ['interests', 'links']; @@ -121,7 +123,11 @@ function useTableColumns() { columnHelper.accessor('name', { header: 'Name', cell: (props) => ( - + ), }), columnHelper.accessor('interests', { @@ -135,22 +141,22 @@ function useTableColumns() { ))} ), + enableSorting: false, }), columnHelper.accessor('links', { header: 'Social', cell: (props) => ( -
- {Object.entries(props.getValue()).map(([key, value], i) => ( - - {key + value} - +
+ {Object.entries(props.getValue()).map(([type, href], i) => ( + ))}
), + enableSorting: false, }), columnHelper.accessor('delegatedBalance', { header: 'Delegated', - cell: (props) =>
{formatNumberString(props.getValue(), 0, true)}
, + cell: (props) =>
{formatNumberString(props.getValue(), 0, true) + ' CELO'}
, }), ]; }, []); diff --git a/src/features/delegation/DelegationsTable.tsx b/src/features/delegation/DelegationsTable.tsx index 4aa357c..dada1e7 100644 --- a/src/features/delegation/DelegationsTable.tsx +++ b/src/features/delegation/DelegationsTable.tsx @@ -7,10 +7,10 @@ import { sortAndCombineChartData } from 'src/components/charts/chartData'; import { HeaderAndSubheader } from 'src/components/layout/HeaderAndSubheader'; import { DropdownMenu } from 'src/components/menus/Dropdown'; import { formatNumberString } from 'src/components/numbers/Amount'; +import { DelegateeLogoAndName } from 'src/features/delegation/DelegateeLogo'; import { DelegateActionType, Delegatee, DelegationAmount } from 'src/features/delegation/types'; import { TransactionFlowType } from 'src/features/transactions/TransactionFlowType'; import { useTransactionModal } from 'src/features/transactions/TransactionModal'; -import { ValidatorGroupLogoAndName } from 'src/features/validators/ValidatorGroupLogo'; import Ellipsis from 'src/images/icons/ellipsis.svg'; import { tableClasses } from 'src/styles/common'; import { fromWei } from 'src/utils/amount'; @@ -81,7 +81,7 @@ export function DelegationsTable({ {tableData.map(({ address, name, amount, percentage }) => ( - + {formatNumberString(amount, 2) + ' CELO'} {percentage + '%'} diff --git a/src/features/delegation/delegateeMetadata.ts b/src/features/delegation/delegateeMetadata.ts new file mode 100644 index 0000000..123b8e0 --- /dev/null +++ b/src/features/delegation/delegateeMetadata.ts @@ -0,0 +1,21 @@ +import DelegateeJsonData from 'src/config/delegates.json'; +import { DelegateeMetadata, DelegateeMetadataMapSchema } from 'src/features/delegation/types'; +import { logger } from 'src/utils/logger'; + +let cachedMetadata: AddressTo; + +export function getDelegateeMetadata(): AddressTo { + if (!cachedMetadata) { + cachedMetadata = parseDelegateeMetadata(); + } + return cachedMetadata; +} + +function parseDelegateeMetadata(): AddressTo { + try { + return DelegateeMetadataMapSchema.parse(DelegateeJsonData); + } catch (error) { + logger.error('Error parsing delegatee metadata', error); + throw new Error('Invalid delegatee metadata'); + } +} diff --git a/src/features/delegation/types.ts b/src/features/delegation/types.ts index f118904..f2d75ac 100644 --- a/src/features/delegation/types.ts +++ b/src/features/delegation/types.ts @@ -41,7 +41,7 @@ export const DelegateeMetadataSchema = z.object({ description: z.string().min(1).max(1500), }); -export const DelegateeMetadataListSchema = z.array(DelegateeMetadataSchema); +export const DelegateeMetadataMapSchema = z.record(z.string(), DelegateeMetadataSchema); export type DelegateeMetadata = z.infer; diff --git a/src/features/delegation/useDelegatees.ts b/src/features/delegation/useDelegatees.ts index f52d798..6070a20 100644 --- a/src/features/delegation/useDelegatees.ts +++ b/src/features/delegation/useDelegatees.ts @@ -2,12 +2,8 @@ import { lockedGoldABI } from '@celo/abis'; import { useQuery } from '@tanstack/react-query'; import { useToastError } from 'src/components/notifications/useToastError'; import { Addresses } from 'src/config/contracts'; -import DelegateeJsonData from 'src/config/delegates.json'; -import { - Delegatee, - DelegateeMetadata, - DelegateeMetadataListSchema, -} from 'src/features/delegation/types'; +import { getDelegateeMetadata } from 'src/features/delegation/delegateeMetadata'; +import { Delegatee, DelegateeMetadata } from 'src/features/delegation/types'; import { logger } from 'src/utils/logger'; import { MulticallReturnType, PublicClient } from 'viem'; import { usePublicClient } from 'wagmi'; @@ -20,8 +16,8 @@ export function useDelegatees() { queryFn: async () => { if (!publicClient) return null; logger.debug('Fetching delegatees'); - const metadata = parseDelegateeMetadata(); - const addressToDelegatee = await fetchDelegateeStats(publicClient, metadata); + const cachedMetadata = Object.values(getDelegateeMetadata()); + const addressToDelegatee = await fetchDelegateeStats(publicClient, cachedMetadata); const delegatees = Object.values(addressToDelegatee); return { addressToDelegatee, delegatees }; }, @@ -39,15 +35,6 @@ export function useDelegatees() { }; } -function parseDelegateeMetadata(): DelegateeMetadata[] { - try { - return DelegateeMetadataListSchema.parse(DelegateeJsonData); - } catch (error) { - logger.error('Error parsing delegatee metadata', error); - throw new Error('Invalid delegatee metadata'); - } -} - async function fetchDelegateeStats( publicClient: PublicClient, metadata: DelegateeMetadata[], diff --git a/src/features/validators/ValidatorGroupLogo.tsx b/src/features/validators/ValidatorGroupLogo.tsx index e52b07e..8d2f741 100644 --- a/src/features/validators/ValidatorGroupLogo.tsx +++ b/src/features/validators/ValidatorGroupLogo.tsx @@ -1,28 +1,10 @@ -import Image from 'next/image'; -import { Circle } from 'src/components/icons/Circle'; -import { Identicon } from 'src/components/icons/Identicon'; -import { ZERO_ADDRESS } from 'src/config/consts'; +import { ImageOrIdenticon } from 'src/components/icons/Identicon'; import { VALIDATOR_GROUPS } from 'src/config/validators'; import { shortenAddress } from 'src/utils/addresses'; export function ValidatorGroupLogo({ address, size }: { address: Address; size: number }) { - return ( - <> - {VALIDATOR_GROUPS[address] ? ( - - ) : !address || address === ZERO_ADDRESS ? ( - - ) : ( - - )} - - ); + const imgSrc = VALIDATOR_GROUPS[address]?.logo; + return ; } export function ValidatorGroupLogoAndName({