Skip to content

Commit

Permalink
Merge pull request #794 from autonomys/feat/improveStakingAndDomain
Browse files Browse the repository at this point in the history
Improve Domains and Staking sections
  • Loading branch information
marc-aurele-besner authored Aug 23, 2024
2 parents 8cd91aa + 6329917 commit 6f62c5f
Show file tree
Hide file tree
Showing 51 changed files with 7,098 additions and 2,153 deletions.
8 changes: 0 additions & 8 deletions explorer/gql/oldSquidTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2728,14 +2728,6 @@ export type ExtrinsicsSummaryQueryVariables = Exact<{

export type ExtrinsicsSummaryQuery = { __typename?: 'Query', extrinsics: { __typename?: 'ExtrinsicsConnection', totalCount: number, edges: Array<{ __typename?: 'ExtrinsicEdge', node: { __typename?: 'Extrinsic', id: string, hash: string, success: boolean, name: string, block: { __typename?: 'Block', id: string, timestamp: any, height: any } } }> } };

export type StakingSummaryQueryVariables = Exact<{
first: Scalars['Int']['input'];
subspaceAccount?: InputMaybe<Scalars['String']['input']>;
}>;


export type StakingSummaryQuery = { __typename?: 'Query', operators: { __typename?: 'OperatorsConnection', totalCount: number, edges: Array<{ __typename?: 'OperatorEdge', node: { __typename?: 'Operator', id: string, operatorOwner?: string | null, currentDomainId?: number | null, currentTotalStake?: any | null, totalShares?: any | null } }> }, nominators: { __typename?: 'NominatorsConnection', totalCount: number, edges: Array<{ __typename?: 'NominatorEdge', node: { __typename?: 'Nominator', id: string, shares?: any | null, account: { __typename?: 'Account', id: string }, operator: { __typename?: 'Operator', id: string, operatorOwner?: string | null, currentDomainId?: number | null, currentTotalStake?: any | null, totalShares?: any | null } } }> } };

export type CheckRoleQueryVariables = Exact<{
subspaceAccount: Scalars['String']['input'];
}>;
Expand Down
4,299 changes: 2,759 additions & 1,540 deletions explorer/gql/types/staking.ts

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions explorer/src/app/[chain]/domains/[domainId]/page.tsx
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
29 changes: 29 additions & 0 deletions explorer/src/app/[chain]/staking/nominations/page.tsx
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
83 changes: 83 additions & 0 deletions explorer/src/components/Domain/Domain.tsx
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>
)
}
191 changes: 191 additions & 0 deletions explorer/src/components/Domain/DomainBlockTime.tsx
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>
)
}
Loading

0 comments on commit 6f62c5f

Please sign in to comment.