-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #794 from autonomys/feat/improveStakingAndDomain
Improve Domains and Staking sections
- Loading branch information
Showing
51 changed files
with
7,098 additions
and
2,153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Domain } from 'components/Domain/Domain' | ||
import { indexers } from 'constants/indexers' | ||
import { metadata } from 'constants/metadata' | ||
import { Metadata } from 'next' | ||
import { FC } from 'react' | ||
import type { AccountIdPageProps, ChainPageProps } from 'types/app' | ||
|
||
export async function generateMetadata({ | ||
params: { chain, accountId }, | ||
}: ChainPageProps & AccountIdPageProps): Promise<Metadata> { | ||
const chainTitle = indexers.find((c) => c.network === chain)?.title || 'Unknown chain' | ||
const title = `${metadata.title} - ${chainTitle} - Operator #${accountId}` | ||
return { | ||
...metadata, | ||
title, | ||
openGraph: { | ||
...metadata.openGraph, | ||
title, | ||
}, | ||
twitter: { | ||
...metadata.twitter, | ||
title, | ||
}, | ||
} | ||
} | ||
|
||
const Page: FC = () => { | ||
return <Domain /> | ||
} | ||
|
||
export default Page |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { NominationsTable } from '@/components/Staking/NominationsTable' | ||
import { indexers } from 'constants/indexers' | ||
import { metadata } from 'constants/metadata' | ||
import { Metadata } from 'next' | ||
import { FC } from 'react' | ||
import type { ChainPageProps } from 'types/app' | ||
|
||
export async function generateMetadata({ params: { chain } }: ChainPageProps): Promise<Metadata> { | ||
const chainTitle = indexers.find((c) => c.network === chain)?.title || 'Unknown chain' | ||
const title = `${metadata.title} - ${chainTitle} - Nominations` | ||
return { | ||
...metadata, | ||
title, | ||
openGraph: { | ||
...metadata.openGraph, | ||
title, | ||
}, | ||
twitter: { | ||
...metadata.twitter, | ||
title, | ||
}, | ||
} | ||
} | ||
|
||
const Page: FC = () => { | ||
return <NominationsTable /> | ||
} | ||
|
||
export default Page |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
'use client' | ||
|
||
import { Spinner } from 'components/common/Spinner' | ||
import { NotFound } from 'components/layout/NotFound' | ||
import { Routes } from 'constants/routes' | ||
import type { DomainByIdQuery, DomainByIdQueryVariables } from 'gql/types/staking' | ||
import useMediaQuery from 'hooks/useMediaQuery' | ||
import { useSquidQuery } from 'hooks/useSquidQuery' | ||
import { useWindowFocus } from 'hooks/useWindowFocus' | ||
import { useParams, useRouter } from 'next/navigation' | ||
import { FC, useEffect, useMemo } from 'react' | ||
import { useInView } from 'react-intersection-observer' | ||
import { hasValue, isLoading, useQueryStates } from 'states/query' | ||
import { OperatorsList } from '../Staking/OperatorsList' | ||
import { DomainDetailsCard } from './DomainDetailsCard' | ||
import { QUERY_DOMAIN_BY_ID } from './staking.query' | ||
|
||
export const Domain: FC = () => { | ||
const { ref, inView } = useInView() | ||
const { domainId } = useParams<{ domainId?: string }>() | ||
const { push } = useRouter() | ||
const inFocus = useWindowFocus() | ||
const isDesktop = useMediaQuery('(min-width: 1024px)') | ||
|
||
// eslint | ||
const variables = useMemo(() => ({ domainId: domainId ?? '' }), [domainId]) | ||
const { setIsVisible } = useSquidQuery<DomainByIdQuery, DomainByIdQueryVariables>( | ||
QUERY_DOMAIN_BY_ID, | ||
{ | ||
variables, | ||
skip: !inFocus, | ||
context: { clientName: 'staking' }, | ||
}, | ||
Routes.domains, | ||
'domain', | ||
) | ||
|
||
const { | ||
domains: { domain }, | ||
} = useQueryStates() | ||
|
||
const domainDetails = useMemo(() => hasValue(domain) && domain.value.domain_by_pk, [domain]) | ||
|
||
const noData = useMemo(() => { | ||
if (isLoading(domain)) return <Spinner isSmall /> | ||
if (!hasValue(domain)) return <NotFound /> | ||
return null | ||
}, [domain]) | ||
|
||
useEffect(() => { | ||
setIsVisible(inView) | ||
}, [inView, setIsVisible]) | ||
|
||
return ( | ||
<div className='flex w-full flex-col space-y-4' ref={ref}> | ||
{domainDetails ? ( | ||
<> | ||
<DomainDetailsCard domain={domainDetails} isDesktop={isDesktop} /> | ||
<div className='mt-5 flex w-full flex-col align-middle'> | ||
<div className='mb-5 flex justify-between'> | ||
<button | ||
className='rounded-full bg-grayDarker p-4 px-4 py-2 text-white dark:bg-purpleAccent' | ||
onClick={() => push((parseInt(domainDetails.id) - 1).toString())} | ||
disabled={parseInt(domainDetails.id) === 0} | ||
> | ||
Prev Domain | ||
</button> | ||
<button | ||
className='rounded-full bg-grayDarker p-4 px-4 py-2 text-white dark:bg-purpleAccent' | ||
onClick={() => push((parseInt(domainDetails.id) + 1).toString())} | ||
> | ||
Next Domain | ||
</button> | ||
</div> | ||
<OperatorsList domainId={domainDetails.id} /> | ||
</div> | ||
</> | ||
) : ( | ||
noData | ||
)} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
import { Spinner } from 'components/common/Spinner' | ||
import { NotFound } from 'components/layout/NotFound' | ||
import { | ||
DomainsStatusQuery, | ||
DomainsStatusQueryVariables, | ||
Order_By as OrderBy, | ||
} from 'gql/types/staking' | ||
import { useSquidQuery } from 'hooks/useSquidQuery' | ||
import { useWindowFocus } from 'hooks/useWindowFocus' | ||
import { FC, useMemo } from 'react' | ||
import { useInView } from 'react-intersection-observer' | ||
import { capitalizeFirstLetter } from 'utils/string' | ||
import { formatSeconds } from 'utils/time' | ||
import { Tooltip } from '../common/Tooltip' | ||
import { QUERY_DOMAIN_STATUS } from './staking.query' | ||
|
||
interface Stat { | ||
title: string | ||
intervalSeconds: bigint | null | ||
} | ||
|
||
interface DomainData { | ||
domain: string | ||
stats: Stat[] | ||
} | ||
|
||
type DomainBlockTimeProgressProps = { | ||
domain: string | ||
currentEpochDuration: string | null | ||
blockCount: number | null | ||
lastEpochDuration: string | null | ||
last6EpochsDuration: string | null | ||
last144EpochsDuration: string | null | ||
last1kEpochDuration: string | null | ||
} | ||
|
||
export const DomainBlockTimeProgress: FC<DomainBlockTimeProgressProps> = ({ | ||
domain, | ||
currentEpochDuration, | ||
blockCount, | ||
lastEpochDuration, | ||
last6EpochsDuration, | ||
last144EpochsDuration, | ||
last1kEpochDuration, | ||
}) => { | ||
const domainData: DomainData[] = useMemo(() => { | ||
return [ | ||
{ | ||
domain, | ||
stats: [ | ||
{ | ||
title: 'Current Epoch', | ||
intervalSeconds: | ||
currentEpochDuration && blockCount | ||
? BigInt(currentEpochDuration) / | ||
BigInt( | ||
blockCount % 100 > 0 | ||
? Math.min(blockCount % 100, 100) * 1000 | ||
: 1, | ||
) | ||
: null, | ||
}, | ||
{ | ||
title: 'Last 1 Epoch', | ||
intervalSeconds: lastEpochDuration | ||
? BigInt(lastEpochDuration) / BigInt(100 * 1000) | ||
: null, | ||
}, | ||
{ | ||
title: 'Last 6 Epochs', | ||
intervalSeconds: last6EpochsDuration | ||
? BigInt(last6EpochsDuration) / BigInt(6 * 100 * 1000) | ||
: null, | ||
}, | ||
{ | ||
title: 'Last 144 Epochs', | ||
intervalSeconds: last144EpochsDuration | ||
? BigInt(last144EpochsDuration) / BigInt(144 * 100 * 1000) | ||
: null, | ||
}, | ||
{ | ||
title: 'Last 1K Epochs', | ||
intervalSeconds: last1kEpochDuration | ||
? BigInt(last1kEpochDuration) / BigInt(1008 * 100 * 1000) | ||
: null, | ||
}, | ||
], | ||
}, | ||
] | ||
}, [ | ||
domain, | ||
currentEpochDuration, | ||
blockCount, | ||
lastEpochDuration, | ||
last6EpochsDuration, | ||
last144EpochsDuration, | ||
last1kEpochDuration, | ||
]) | ||
|
||
return domainData.length > 0 ? ( | ||
domainData.map(({ domain, stats }, domainIndex) => ( | ||
<div | ||
key={domainIndex} | ||
className='w-full min-w-80 rounded-[20px] bg-grayLight p-5 shadow dark:border-none dark:bg-gradient-to-r dark:from-gradientTwilight dark:via-gradientDusk dark:to-gradientSunset sm:w-1/2' | ||
> | ||
<h2 className='mb-4 text-lg font-bold text-grayDark dark:text-white sm:text-xl'> | ||
{capitalizeFirstLetter(domain)} | ||
</h2> | ||
{stats.map(({ title, intervalSeconds }, statIndex) => ( | ||
<div key={statIndex} className='mb-3 sm:mb-4'> | ||
<div className='flex items-center justify-between'> | ||
<span className='text-base font-semibold text-grayDark dark:text-white sm:text-lg'> | ||
<Tooltip | ||
text={ | ||
<div className='flex w-full flex-col gap-1'> | ||
<span className='block text-xs'> | ||
1 block = {formatSeconds(intervalSeconds ?? 0)} | ||
</span> | ||
<span className='block text-xs'> | ||
1 epoch = {formatSeconds(BigInt(intervalSeconds ?? 0) * BigInt(100))} | ||
</span> | ||
<span className='block text-xs'>1 epoch = 100 blocks</span> | ||
</div> | ||
} | ||
> | ||
{title} | ||
</Tooltip> | ||
</span> | ||
<span className='text-sm text-grayDark dark:text-white'> | ||
{intervalSeconds !== null ? intervalSeconds + 's per block' : 'N/A'} | ||
</span> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
)) | ||
) : ( | ||
<NotFound /> | ||
) | ||
} | ||
|
||
export const DomainBlockTime: FC = () => { | ||
const { ref } = useInView() | ||
const inFocus = useWindowFocus() | ||
|
||
const { data, loading, error } = useSquidQuery<DomainsStatusQuery, DomainsStatusQueryVariables>( | ||
QUERY_DOMAIN_STATUS, | ||
{ | ||
variables: { | ||
limit: 10, | ||
orderBy: [{ id: OrderBy.Asc }], | ||
where: {}, | ||
}, | ||
skip: !inFocus, | ||
pollInterval: 2000, | ||
context: { clientName: 'staking' }, | ||
}, | ||
) | ||
|
||
const cards = useMemo(() => { | ||
if (loading || error || !data) return [] | ||
|
||
return data.domain.map((domain, index) => ( | ||
<DomainBlockTimeProgress | ||
key={index} | ||
domain={domain.name} | ||
currentEpochDuration={domain.current_epoch_duration} | ||
blockCount={domain.last_domain_block_number} | ||
lastEpochDuration={domain.last_epoch_duration} | ||
last6EpochsDuration={domain.last6_epochs_duration} | ||
last144EpochsDuration={domain.last144_epoch_duration} | ||
last1kEpochDuration={domain.last1k_epoch_duration} | ||
/> | ||
)) | ||
}, [data, loading, error]) | ||
|
||
const noData = useMemo(() => { | ||
if (loading) return <Spinner isXSmall /> | ||
if (!data) return <NotFound /> | ||
return null | ||
}, [data, loading]) | ||
|
||
return ( | ||
<div | ||
className='flex w-full flex-col items-center justify-center gap-2 px-2 sm:flex-row sm:gap-5 sm:px-4' | ||
ref={ref} | ||
> | ||
{data ? <>{cards}</> : noData} | ||
</div> | ||
) | ||
} |
Oops, something went wrong.