diff --git a/src/components/DeploymentInfo/DeploymentInfo.tsx b/src/components/DeploymentInfo/DeploymentInfo.tsx index 6556f2495..82e0876fd 100644 --- a/src/components/DeploymentInfo/DeploymentInfo.tsx +++ b/src/components/DeploymentInfo/DeploymentInfo.tsx @@ -8,6 +8,7 @@ import { useGetIfUnsafeDeployment } from '@hooks/useGetIfUnsafeDeployment'; import { Spinner, Typography } from '@subql/components'; import { ProjectType } from '@subql/contract-sdk/types'; import { Tooltip } from 'antd'; +import { clsx } from 'clsx'; import { useProjectMetadata } from '../../containers'; import { useAsyncMemo } from '../../hooks'; @@ -42,7 +43,11 @@ export const DeploymentInfo: React.FC = ({ project, deploymentId, type })
{project?.name && ( - + {project?.name} )} @@ -58,13 +63,15 @@ export const DeploymentInfo: React.FC = ({ project, deploymentId, type })
)}
- + {versionHeader} - + - {deploymentId ?? '-'} + {deploymentId + ? `${deploymentId.slice(0, 5)}...${deploymentId.slice(deploymentId.length - 5, deploymentId.length)}` + : '-'}
diff --git a/src/components/DoAllocate/DoAllocate.tsx b/src/components/DoAllocate/DoAllocate.tsx index 61d3c9ead..63a136cde 100644 --- a/src/components/DoAllocate/DoAllocate.tsx +++ b/src/components/DoAllocate/DoAllocate.tsx @@ -14,7 +14,7 @@ import { SQNetworks } from '@subql/network-config'; import { ProjectType } from '@subql/network-query'; import { useAsyncMemo, useGetIndexerAllocationSummaryLazyQuery } from '@subql/react-hooks'; import { parseError, TOKEN } from '@utils'; -import { Button, Form } from 'antd'; +import { Button, Form, Radio } from 'antd'; import { useForm, useWatch } from 'antd/es/form/Form'; import BigNumber from 'bignumber.js'; import { useAccount } from 'wagmi'; @@ -31,9 +31,11 @@ interface IProps { deploymentId?: string; actionBtn?: React.ReactNode; onSuccess?: () => void; + initialStatus?: 'Add' | 'Remove'; + disabled?: boolean; } -const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess }) => { +const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess, initialStatus, disabled }) => { const { address: account } = useAccount(); const project = useProjectFromQuery(projectId ?? ''); const { data: deploymentMetadata } = useDeploymentMetadata(deploymentId); @@ -45,6 +47,7 @@ const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess const { contracts } = useWeb3Store(); const [open, setOpen] = useState(false); const [currentRewardsPerToken, setCurrentRewardsPerToken] = useState(BigNumber(0)); + const [addOrRemove, setAddOrRemove] = useState<'Add' | 'Remove'>(initialStatus || 'Add'); const runnerAllocation = useAsyncMemo(async () => { if (!account) @@ -62,6 +65,8 @@ const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess }; }, [account]); + console.warn(runnerAllocation.data?.total.toString(), runnerAllocation.data?.used.toString()); + const allocationRewardsRate = useAsyncMemo(async () => { const rewards = await contracts?.rewardsBooster.boosterQueryRewardRate( project.data?.type === ProjectType.RPC ? 1 : 0, @@ -102,40 +107,55 @@ const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess const avaibleStakeAmount = useMemo(() => { const leftAllocation = runnerAllocation.data?.left ? BigNumber(runnerAllocation.data?.left) : BigNumber(0); - const haveAllocated = formatSQT( - BigNumber(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0').toString(), - ); - return leftAllocation.plus(haveAllocated).toString(); + return leftAllocation.toString(); }, [allocatedStake, runnerAllocation.data?.left]); + const currentAllocatedTokensOfThisDeployment = useMemo(() => { + return formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount || '0'); + }, [allocatedStake.data]); + const estimatedRewardsPerTokenOneEra = useMemo(() => { // 2s one block // 7 days one era return currentRewardsPerToken.multipliedBy(NETWORK_NAME === SQNetworks.TESTNET ? 1800 : 1800 * 24 * 7); }, [currentRewardsPerToken]); + const estimatedRewardsAfterInput = useMemo(() => { + // lack div all tokens + // to know all tokens that already allocated is not very easy. + if (estimatedRewardsPerTokenOneEra.eq(0) || !formAllocateVal) { + return 'Unkonwn'; + } + + return formatNumber( + formatSQT( + estimatedRewardsPerTokenOneEra + .multipliedBy( + BigNumber(formAllocateVal) + .minus(formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0')) + .abs() || 0, + ) + .toString(), + ), + ); + }, [estimatedRewardsPerTokenOneEra, formAllocateVal, addOrRemove]); + const updateAllocate = async () => { if (!deploymentId || !account) return; await form.validateFields(); - const addOrRemoveFunc = BigNumber(form.getFieldValue('allocateVal')).gt( - formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0'), - ) - ? contracts?.stakingAllocation.addAllocation - : contracts?.stakingAllocation.removeAllocation; + const addOrRemoveFunc = + addOrRemove === 'Add' + ? contracts?.stakingAllocation.addAllocation + : contracts?.stakingAllocation.removeAllocation; try { const res = await addOrRemoveFunc?.( cidToBytes32(deploymentId), account, - parseEther( - BigNumber(form.getFieldValue('allocateVal')) - .minus(formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0')) - .abs() - .toFixed(), - ), + parseEther(BigNumber(form.getFieldValue('allocateVal')).toFixed()), ); await res?.wait(); @@ -155,7 +175,8 @@ const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess }; useEffect(() => { - if (open && account && deploymentId) { + if (open && account && deploymentId && !disabled) { + setAddOrRemove(initialStatus || 'Add'); getAllocatedStake({ variables: { id: `${deploymentId}:${account}`, @@ -165,14 +186,16 @@ const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess getAllocateRewardsPerBlock(); runnerAllocation.refetch(); } - }, [open, account, deploymentId]); + }, [open, account, deploymentId, disabled]); return (
{actionBtn ? (
{ - setOpen(true); + if (!disabled) { + setOpen(true); + } }} > {actionBtn} @@ -227,34 +250,31 @@ const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess
-
- -
-
- - Current allocated stake to {project.data?.metadata.name} - - - - {formatNumber(formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount || '0'))} {TOKEN} - -
- -
- - Available stake to allocate {TOKEN} - - - - {avaibleStakeAmount} {TOKEN} - -
+
+ { + setAddOrRemove(val.target.value); + }} + style={{ display: 'flex', flexDirection: 'column', gap: 16, margin: '24px 0 0 0' }} + > + Add Allocation + + Remove Allocation + +
+
+
- New allocation amount + New allocation amount = ({ projectId, deploymentId, actionBtn, onSuccess > { form.setFieldsValue({ allocateVal: value }); @@ -283,15 +303,29 @@ const DoAllocate: FC = ({ projectId, deploymentId, actionBtn, onSuccess >
+
+
+ + Current allocated stake to {project.data?.metadata.name} + + + + {formatNumber(currentAllocatedTokensOfThisDeployment)} {TOKEN} + +
+
+ + Available stake to allocate {TOKEN} + + + + {avaibleStakeAmount} {TOKEN} + +
+
- Estimated allocation rewards after update:{' '} - {estimatedRewardsPerTokenOneEra.eq(0) - ? 'Unknown' - : formatNumber( - formatSQT(estimatedRewardsPerTokenOneEra.multipliedBy(formAllocateVal || 0).toString()), - )}{' '} - {TOKEN} Per era + Estimated allocation rewards after update: {estimatedRewardsAfterInput} {TOKEN} Per era
diff --git a/src/components/ProjectCard/ProjectCard.tsx b/src/components/ProjectCard/ProjectCard.tsx index e21aff9bd..1682722b8 100644 --- a/src/components/ProjectCard/ProjectCard.tsx +++ b/src/components/ProjectCard/ProjectCard.tsx @@ -87,7 +87,7 @@ const ProjectCard: React.FC = ({ project, onClick }) => { Last updated - {dayjs(project.updatedTimestamp).fromNow()} + {dayjs(project.updatedTimestamp).utc().fromNow()}
)} diff --git a/src/components/ProjectHeader/ProjectHeader.tsx b/src/components/ProjectHeader/ProjectHeader.tsx index ca81e8390..53bef99c6 100644 --- a/src/components/ProjectHeader/ProjectHeader.tsx +++ b/src/components/ProjectHeader/ProjectHeader.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import DoAllocate from '@components/DoAllocate/DoAllocate'; import UnsafeWarn from '@components/UnsafeWarn'; import { Manifest } from '@hooks/useGetDeploymentManifest'; import { ProjectDetailsQuery } from '@hooks/useProjectFromQuery'; @@ -84,7 +83,6 @@ const ProjectHeader: React.FC = ({ {/* */} -
diff --git a/src/components/Status/Status.tsx b/src/components/Status/Status.tsx index a2efd069b..3013334a5 100644 --- a/src/components/Status/Status.tsx +++ b/src/components/Status/Status.tsx @@ -16,7 +16,7 @@ export const deploymentStatus: { [key: string]: StatusColor } = { INDEXING: StatusColor.blue, STARTED: StatusColor.blue, READY: StatusColor.green, - NOTINDEXING: StatusColor.red, + NOTINDEXING: StatusColor.gray, TERMINATED: StatusColor.red, }; diff --git a/src/components/TransactionModal/TransactionModal.tsx b/src/components/TransactionModal/TransactionModal.tsx index a9460a2e4..68d8d5b98 100644 --- a/src/components/TransactionModal/TransactionModal.tsx +++ b/src/components/TransactionModal/TransactionModal.tsx @@ -77,6 +77,10 @@ export type TransactionModalProps = { currentConfirmButtonLoading?: boolean; }; +export interface TransactionModalRef { + showModal: (key: string) => void; +} + // TODO: arrange this compoent // No questions aspect: Feature good. // Need some arrange: 1. onClick, actions, renderContent are coupling, those attributes are all having part logic to do same thing. @@ -85,160 +89,169 @@ export type TransactionModalProps = { // 2. actions and variant have the same problems. // 3. text is not a searchable name. // I am not sure the optimize target. just put those problems now. -const TransactionModal = ({ - renderContent, // renderModalContent - text, - currentStep, - actions, - onClick, - onClose, - onSuccess, - inputParams, - variant = 'button', - initialCheck, - loading, - rethrowWhenSubmit = false, - width = '45%', - className = '', - buttonClassName = '', - currentConfirmButtonLoading = false, -}: TransactionModalProps): React.ReactElement | null => { - const { t } = useTranslation(); - const [showModal, setShowModal] = React.useState(); - const [isLoading, setIsLoading] = React.useState(initialCheck?.loading || false); - const [successModalText, setSuccessModalText] = React.useState(); - const [failureModalText, setFailureModalText] = React.useState(); - - React.useEffect(() => { - if (initialCheck) { - const { error } = initialCheck; - error && setFailureModalText(parseError(error)); - } - }, [failureModalText, initialCheck, initialCheck?.loading, showModal]); - - React.useEffect(() => { - if (successModalText) { - const timeoutId = setTimeout(() => { - setSuccessModalText(undefined); - }, 2500); - - return () => clearTimeout(timeoutId); - } - }, [successModalText]); - - const resetModal = () => { - setShowModal(undefined); - setFailureModalText(undefined); - onClose && onClose(); - }; +const TransactionModal = React.forwardRef>( + ( + { + renderContent, // renderModalContent + text, + currentStep, + actions, + onClick, + onClose, + onSuccess, + inputParams, + variant = 'button', + initialCheck, + loading, + rethrowWhenSubmit = false, + width = '45%', + className = '', + buttonClassName = '', + currentConfirmButtonLoading = false, + }, + ref, + ) => { + const { t } = useTranslation(); + const [showModal, setShowModal] = React.useState(); + const [isLoading, setIsLoading] = React.useState(initialCheck?.loading || false); + const [successModalText, setSuccessModalText] = React.useState(); + const [failureModalText, setFailureModalText] = React.useState(); - const handleBtnClick = (key: T) => { - setShowModal(key); - }; + React.useEffect(() => { + if (initialCheck) { + const { error } = initialCheck; + error && setFailureModalText(parseError(error)); + } + }, [failureModalText, initialCheck, initialCheck?.loading, showModal]); + + React.useEffect(() => { + if (successModalText) { + const timeoutId = setTimeout(() => { + setSuccessModalText(undefined); + }, 2500); + + return () => clearTimeout(timeoutId); + } + }, [successModalText]); + + const resetModal = () => { + setShowModal(undefined); + setFailureModalText(undefined); + onClose && onClose(); + }; - const wrapTxAction = (action: typeof onClick, rethrow?: boolean) => async (params: P) => { - try { - if (!showModal || !action) return; + const handleBtnClick = (key: string) => { + setShowModal(key); + }; - const tx = await action(params, showModal); - setIsLoading(true); - resetModal(); - openNotification({ title: t('transaction.submmited') }); - const result = await tx.wait(); + React.useImperativeHandle(ref, () => ({ + showModal: handleBtnClick, + })); - if (result.status) { - onSuccess && onSuccess(); - setSuccessModalText(text.successText || 'Success'); + const wrapTxAction = (action: typeof onClick, rethrow?: boolean) => async (params: string) => { + try { + if (!showModal || !action) return; + + const tx = await action(params, showModal); + setIsLoading(true); + resetModal(); + openNotification({ title: t('transaction.submmited') }); + const result = await tx.wait(); + + if (result.status) { + onSuccess && onSuccess(); + setSuccessModalText(text.successText || 'Success'); + openNotification({ + type: NotificationType.SUCCESS, + title: 'Success', + description: text.successText ?? t('status.changeValidIn15s'), + }); + } else { + throw new Error(text.failureText); + } + } catch (error) { openNotification({ - type: NotificationType.SUCCESS, - title: 'Success', - description: text.successText ?? t('status.changeValidIn15s'), + type: NotificationType.ERROR, + title: 'Failure', + description: `${text.failureText ?? 'Error'}: ${parseError(error)}`, }); - } else { - throw new Error(text.failureText); + setFailureModalText(parseError(error)); + if (rethrow) { + throw error; + } + } finally { + setIsLoading(false); } - } catch (error) { - openNotification({ - type: NotificationType.ERROR, - title: 'Failure', - description: `${text.failureText ?? 'Error'}: ${parseError(error)}`, - }); - setFailureModalText(parseError(error)); - if (rethrow) { - throw error; - } - } finally { - setIsLoading(false); - } - }; + }; - const modalVisible = !!showModal; - - return ( -
- {modalVisible && ( - { - resetModal(); - }} - loading={loading} - currentStep={currentStep} - steps={text.steps} - content={ - renderContent?.(wrapTxAction(onClick, rethrowWhenSubmit), resetModal, isLoading, failureModalText) || ( - //NOTE: slowly deprecate it and use NumberInput only - ) - } - width={width} - className={className} - /> - )} - - {actions.map(({ label, key, onClick, disabled, tooltip, defaultOpenTooltips, rightItem, ...rest }) => { - const isTextButton = variant.match(/text|Text/); - const sortedStyle = disabled ? (isTextButton ? 'disabledTextBtn' : 'disabledButton') : variant; - - return ( -
- -
- ); - })} -
- ); -}; + const modalVisible = !!showModal; + + return ( +
+ {modalVisible && ( + { + resetModal(); + }} + loading={loading} + currentStep={currentStep} + steps={text.steps} + content={ + renderContent?.(wrapTxAction(onClick, rethrowWhenSubmit), resetModal, isLoading, failureModalText) || ( + //NOTE: slowly deprecate it and use NumberInput only + ) + } + width={width} + className={className} + /> + )} + + {actions.map(({ label, key, onClick, disabled, tooltip, defaultOpenTooltips, rightItem, ...rest }) => { + const isTextButton = variant.match(/text|Text/); + const sortedStyle = disabled ? (isTextButton ? 'disabledTextBtn' : 'disabledButton') : variant; + + return ( +
+ +
+ ); + })} +
+ ); + }, +); export default TransactionModal; diff --git a/src/hooks/useSortedIndexerDeployments.tsx b/src/hooks/useSortedIndexerDeployments.tsx index c4e018110..42e352fac 100644 --- a/src/hooks/useSortedIndexerDeployments.tsx +++ b/src/hooks/useSortedIndexerDeployments.tsx @@ -1,51 +1,53 @@ // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 +import { indexingProgress } from '@subql/network-clients'; import { IndexerDeploymentNodeFieldsFragment as DeploymentIndexer, ServiceStatus } from '@subql/network-query'; -import { useGetDeploymentIndexersByIndexerQuery } from '@subql/react-hooks'; +import { + useGetAllocationRewardsByDeploymentIdAndIndexerIdQuery, + useGetDeploymentIndexersByIndexerQuery, + useGetIndexerAllocationProjectsQuery, +} from '@subql/react-hooks'; import { useProjectMetadata } from '../containers'; import { ProjectMetadata } from '../models'; -import { AsyncData, getDeploymentProgress } from '../utils'; +import { AsyncData, getDeploymentMetadata } from '../utils'; import { useAsyncMemo } from './useAsyncMemo'; import { useIndexerMetadata } from './useIndexerMetadata'; -const fetchDeploymentProgress = async ( - indexer: string, - proxyEndpoint: string | undefined, - deploymentId: string | undefined, -) => { - const indexingProgressErr = 'Failed to fetch deployment progress. Please check the proxyEndpoint.'; - if (proxyEndpoint && deploymentId) { - try { - const indexingProgress = await getDeploymentProgress({ - proxyEndpoint, - deploymentId, - indexer, - }); - return { indexingProgress }; - } catch (error) { - return { indexingProgressErr }; - } - } - - return { indexingProgressErr }; -}; - export interface UseSortedIndexerDeploymentsReturn extends Partial { - indexingProgress?: number | undefined; - indexingProgressErr?: string; + indexingErr?: string; deploymentId?: string; projectId?: string; projectName?: string; projectMeta: ProjectMetadata; isOffline?: boolean | undefined; + lastHeight: number; + indexingProgress: number; + allocatedAmount?: string; + allocatedTotalRewards?: string; } // TODO: apply with query hook export function useSortedIndexerDeployments(indexer: string): AsyncData> { const { getMetadataFromCid } = useProjectMetadata(); - const indexerDeployments = useGetDeploymentIndexersByIndexerQuery({ variables: { indexerAddress: indexer } }); + const indexerDeployments = useGetDeploymentIndexersByIndexerQuery({ + variables: { indexerAddress: indexer }, + fetchPolicy: 'network-only', + }); + const allocatedProjects = useGetIndexerAllocationProjectsQuery({ + variables: { + id: indexer || '', + }, + fetchPolicy: 'network-only', + }); + const allocatedRewards = useGetAllocationRewardsByDeploymentIdAndIndexerIdQuery({ + variables: { + indexerId: indexer || '', + }, + fetchPolicy: 'network-only', + }); + const { indexerMetadata } = useIndexerMetadata(indexer); const proxyEndpoint = indexerMetadata?.url; @@ -55,8 +57,17 @@ export function useSortedIndexerDeployments(indexer: string): AsyncData deployment?.status !== ServiceStatus.TERMINATED, ); + + // merge have allocation but not indexing project + const mergedDeployments = [ + ...filteredDeployments, + ...(allocatedProjects.data?.indexerAllocationSummaries?.nodes.filter( + (i) => !filteredDeployments.find((j) => j?.deploymentId === i?.deploymentId), + ) || []), + ]; + return await Promise.all( - filteredDeployments.map(async (indexerDeployment) => { + mergedDeployments.map(async (indexerDeployment) => { const metadata: ProjectMetadata = indexerDeployment?.deployment?.project ? await getMetadataFromCid(indexerDeployment.deployment.project.metadata) : { @@ -69,30 +80,71 @@ export function useSortedIndexerDeployments(indexer: string): AsyncData i?.deploymentId === deploymentId) + ?.totalAmount.toString(); + const allocatedTotalRewards = allocatedRewards.data?.indexerAllocationRewards?.groupedAggregates + ?.find((i) => { + return i?.keys?.[0] === deploymentId; + }) + ?.sum?.reward.toString(); + + const projectId = + indexerDeployment?.__typename === 'IndexerDeployment' + ? indexerDeployment.deployment?.project?.id + : indexerDeployment?.proejctId; return { - ...indexerDeployment, - indexingProgress, - indexingProgressErr, + indexingErr, + indexingProgress: sortedIndexingProcess, + lastHeight, isOffline, deploymentId, - projectId: indexerDeployment?.deployment?.project?.id, - projectName: metadata.name ?? indexerDeployment?.deployment?.project?.id, + projectId, + projectName: metadata.name ?? projectId, projectMeta: { ...metadata, }, + allocatedAmount, + allocatedTotalRewards, }; }), ); - }, [indexerDeployments.loading, proxyEndpoint]); + }, [indexerDeployments.loading, proxyEndpoint, allocatedProjects.data, allocatedRewards.data]); - return sortedIndexerDeployments; + return { + ...sortedIndexerDeployments, + refetch: async () => { + await indexerDeployments.refetch(); + await allocatedProjects.refetch(); + await allocatedRewards.refetch(); + }, + }; } diff --git a/src/i18n/en/indexer.ts b/src/i18n/en/indexer.ts index 5534d8f49..b7070d253 100644 --- a/src/i18n/en/indexer.ts +++ b/src/i18n/en/indexer.ts @@ -82,7 +82,8 @@ const translation = { nonProjects: `You haven’t indexed any projects yet`, nonProjectDesc: `Follow our documentation to help get you set up as an Indexer then head over to the Explorer to find the first project you would like to index.`, learnMore: 'Learn how to index a project <1>here', - description: 'All SubQuery projects you are currently indexing', + description: + 'All SubQuery projects you are currently indexing, you can also update staking amounts and allocate your stake to different projects here', }, indexerPlans: { title: 'You haven’t created any plans yet', diff --git a/src/index.less b/src/index.less index 5da2d45c5..458e92aa1 100644 --- a/src/index.less +++ b/src/index.less @@ -267,6 +267,12 @@ label, margin: 1rem 0; } +.overflowEllipsis { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + /* For antD UI - for future lib usage*/ /* For antD table header */ // TODO: Those should be define at components. @@ -304,3 +310,13 @@ label, border-color: var(--sq-blue600); color: var(--sq-blue600); } + +.ant-radio-wrapper .ant-radio-checked .ant-radio-inner { + border-color: var(--sq-blue600); + background: transparent; + + &::after { + background: var(--sq-blue600); + transform: scale(0.5); + } +} \ No newline at end of file diff --git a/src/pages/indexer/MyAllocation/MyAllocation.tsx b/src/pages/indexer/MyAllocation/MyAllocation.tsx deleted file mode 100644 index f66382b7f..000000000 --- a/src/pages/indexer/MyAllocation/MyAllocation.tsx +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import React, { FC, useMemo } from 'react'; -import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'; -import { CurEra, DeploymentMeta, Description, WalletRoute } from '@components'; -import DoAllocate from '@components/DoAllocate/DoAllocate'; -import { SubqlCard, TableText, TableTitle, Typography } from '@subql/components'; -import { - formatSQT, - useAsyncMemo, - useGetAllocationRewardsByDeploymentIdAndIndexerIdQuery, - useGetIndexerAllocationProjectsQuery, -} from '@subql/react-hooks'; -import { formatNumber, notEmpty, TOKEN } from '@utils'; -import { retry } from '@utils/retry'; -import { Table, Tooltip } from 'antd'; -import BigNumber from 'bignumber.js'; -import { useAccount } from 'wagmi'; - -import { useWeb3Store } from 'src/stores'; - -import styles from './index.module.less'; - -const MyAllocation: FC = (props) => { - const { address: account } = useAccount(); - const { contracts } = useWeb3Store(); - - const runnerAllocation = useAsyncMemo(async () => { - if (!account) - return { - used: '0', - total: '0', - }; - const res = await contracts?.stakingAllocation.runnerAllocation(account); - - return { - used: formatSQT(res?.used.toString() || '0'), - total: formatSQT(res?.total.toString() || '0'), - }; - }, [account]); - - const isOverAllocate = useMemo(() => { - if (!runnerAllocation.data?.used || !runnerAllocation.data?.total) return false; - return +runnerAllocation.data?.used > +runnerAllocation.data?.total; - }, [runnerAllocation.data?.used, runnerAllocation.data?.total]); - - const allocatedProjects = useGetIndexerAllocationProjectsQuery({ - variables: { - id: account || '', - }, - }); - - const allocatedRewards = useGetAllocationRewardsByDeploymentIdAndIndexerIdQuery({ - variables: { - indexerId: account || '', - }, - }); - - return ( - -
-
- - My Allocations - - -
- - -
- -
- - - {isOverAllocate ? 'Over Allocations' : 'Total Allocations'} - {isOverAllocate && ( - - - - )} - - - {`${BigNumber( - runnerAllocation.data?.used || '0', - ).toFormat()} ${TOKEN}`} -
- } - style={{ boxShadow: 'none', minWidth: 260, padding: 4 }} - /> - - - Total Allocation REWARDS - - - - - - {`${formatNumber( - formatSQT( - allocatedRewards.data?.indexerAllocationRewards?.groupedAggregates - ?.reduce((cur, add) => cur.plus(add.sum?.reward.toString() || '0'), BigNumber(0)) - .toString() || '0', - ), - )} ${TOKEN}`} - - } - style={{ boxShadow: 'none', minWidth: 260, padding: 4 }} - /> - - - record.deploymentId} - columns={[ - { - title: #, - render(value, record, index) { - return {index + 1}; - }, - }, - { - title: Project, - dataIndex: 'deploymentId', - render(value, record) { - return ( - - ); - }, - }, - { - title: Allocated Amount, - dataIndex: 'totalAmount', - render(value) { - return ( - - {formatNumber(formatSQT(value))} {TOKEN} - - ); - }, - }, - { - title: Total Rewards, - dataIndex: 'deploymentId', - render(value) { - const rewards = allocatedRewards.data?.indexerAllocationRewards?.groupedAggregates?.find( - (i) => i.keys?.[0] === value, - ); - const totalRewards = rewards?.sum?.reward; - return ( - - {formatNumber(formatSQT(totalRewards?.toString() || '0'))} {TOKEN} - - ); - }, - }, - { - title: Action, - dataIndex: 'deploymentId', - render(value, record) { - return ( - - Update - - } - onSuccess={() => { - runnerAllocation.refetch(); - retry(allocatedProjects.refetch); - }} - > - ); - }, - }, - ]} - >
- - } - >
- ); -}; -export default MyAllocation; diff --git a/src/pages/indexer/MyAllocation/index.module.less b/src/pages/indexer/MyAllocation/index.module.less deleted file mode 100644 index a8d75768a..000000000 --- a/src/pages/indexer/MyAllocation/index.module.less +++ /dev/null @@ -1,5 +0,0 @@ -.myAllocation { - padding: 40px 16px; - display: flex; - flex-direction: column; -} \ No newline at end of file diff --git a/src/pages/indexer/MyDelegators/OwnDelegator.tsx b/src/pages/indexer/MyDelegators/OwnDelegator.tsx index f04cb6c8e..1487ff5b5 100644 --- a/src/pages/indexer/MyDelegators/OwnDelegator.tsx +++ b/src/pages/indexer/MyDelegators/OwnDelegator.tsx @@ -4,17 +4,20 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; +import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'; import { ConnectedIndexer } from '@components/IndexerDetails/IndexerName'; -import { useEra } from '@hooks'; +import { useEra, useSortedIndexer } from '@hooks'; import { mapEraValue, parseRawEraValue } from '@hooks/useEraValue'; -import { TableTitle } from '@subql/components'; +import { SubqlCard, TableTitle } from '@subql/components'; import { Spinner, Typography } from '@subql/components'; import { renderAsyncArray, useGetIndexerDelegatorsQuery } from '@subql/react-hooks'; import { convertStringToNumber, mapAsync, mergeAsync, TOKEN } from '@utils'; import { formatNumber } from '@utils/numberFormatters'; -import { Table } from 'antd'; +import { retry } from '@utils/retry'; +import { Table, Tooltip } from 'antd'; import { formatEther } from 'ethers/lib/utils'; +import { SetCommissionRate } from '../MyStaking/SetCommissionRate'; import styles from './OwnDelegator.module.css'; interface Props { @@ -27,6 +30,7 @@ export const OwnDelegator: React.FC = ({ indexer, showHeader = false }) = const indexerDelegations = useGetIndexerDelegatorsQuery({ variables: { id: indexer ?? '', offset: 0 } }); const { currentEra } = useEra(); const navigate = useNavigate(); + const sortedIndexer = useSortedIndexer(indexer || ''); const columns = [ { @@ -61,6 +65,57 @@ export const OwnDelegator: React.FC = ({ indexer, showHeader = false }) = return (
+
+ +
+ Current Commission Rate + + { + retry(() => { + sortedIndexer.refresh?.(); + }); + }} + > +
+
+ } + titleExtra={ +
+ + {sortedIndexer.data?.commission.current} % + + + + {sortedIndexer.data?.commission.after} % + +
+ } + style={{ boxShadow: 'none', marginBottom: 24, width: 360 }} + > +
+
+ + Capcity + + + {formatNumber(sortedIndexer.data?.capacity.current || '0')} {TOKEN} + +
+ +
+ + {' '} + + + {formatNumber(sortedIndexer.data?.capacity.after || '0')} {TOKEN} + +
+
+ +
{renderAsyncArray( mapAsync( ([sortedIndexer, era]) => diff --git a/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx b/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx index 010e126a8..8ea5c8ccf 100644 --- a/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx +++ b/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx @@ -4,16 +4,38 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { Spinner, SubqlProgress } from '@subql/components'; +import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'; +import WarningOutlined from '@ant-design/icons/WarningOutlined'; +import DoAllocate from '@components/DoAllocate/DoAllocate'; +import { BalanceLayout } from '@pages/dashboard'; +import { DoStake } from '@pages/indexer/MyStaking/DoStake'; +import { Spinner, SubqlCard, Typography } from '@subql/components'; import { TableTitle } from '@subql/components'; +import { ServiceStatus } from '@subql/network-query'; +import { + formatSQT, + mergeAsync, + useAsyncMemo, + useGetAllocationRewardsByDeploymentIdAndIndexerIdQuery, +} from '@subql/react-hooks'; import { getDeploymentStatus } from '@utils/getIndexerStatus'; -import { Table, TableProps, Typography } from 'antd'; +import { retry } from '@utils/retry'; +import { Table, TableProps, Tooltip } from 'antd'; +import BigNumberJs from 'bignumber.js'; +import { BigNumber } from 'ethers'; + +import { useWeb3Store } from 'src/stores'; import { DeploymentInfo, Status } from '../../../../components'; import { Description } from '../../../../components/Description/Description'; import { deploymentStatus } from '../../../../components/Status/Status'; -import { useSortedIndexerDeployments, UseSortedIndexerDeploymentsReturn } from '../../../../hooks'; -import { renderAsync, truncateToDecimalPlace } from '../../../../utils'; +import { + useIsIndexer, + useSortedIndexer, + useSortedIndexerDeployments, + UseSortedIndexerDeploymentsReturn, +} from '../../../../hooks'; +import { formatNumber, renderAsync, TOKEN, truncateToDecimalPlace } from '../../../../utils'; import { ROUTES } from '../../../../utils'; import styles from './OwnDeployments.module.css'; @@ -29,40 +51,147 @@ export const OwnDeployments: React.FC = ({ indexer, emptyList, desc }) => const { t } = useTranslation(); const navigate = useNavigate(); const indexerDeployments = useSortedIndexerDeployments(indexer); + const isIndexer = useIsIndexer(indexer); + const sortedIndexer = useSortedIndexer(indexer || ''); + const { contracts } = useWeb3Store(); + + const allocatedRewards = useGetAllocationRewardsByDeploymentIdAndIndexerIdQuery({ + variables: { + indexerId: indexer || '', + }, + }); + + const runnerAllocation = useAsyncMemo(async () => { + if (!indexer) + return { + used: '0', + total: '0', + left: '0', + }; + const res = await contracts?.stakingAllocation.runnerAllocation(indexer); + + return { + used: formatSQT(res?.used.toString() || '0'), + total: formatSQT(res?.total.toString() || '0'), + left: formatSQT(res?.total.sub(res.used).toString() || '0'), + }; + }, [indexer]); + + const isOverAllocate = React.useMemo(() => { + if (!runnerAllocation.data?.used || !runnerAllocation.data?.total) return false; + return +runnerAllocation.data?.used > +runnerAllocation.data?.total; + }, [runnerAllocation.data?.used, runnerAllocation.data?.total]); const columns: TableProps['columns'] = [ { - title: '', + title: 'Project', dataIndex: 'deploymentId', render: (deploymentId: string, deployment) => ( ), + onCell: (record) => { + return { + onClick: (_) => { + if (record.projectId) { + navigate(`${PROJECT_NAV}/${record.projectId}/overview`); + } + }, + }; + }, }, { - width: '100%', - title: , + title: , dataIndex: 'indexingProgress', render: (indexingProgress: number, deployment) => { - const { indexingProgressErr } = deployment; - if (indexingProgressErr) + // TODO: will use metric service replace it. hardcode for now. + const sortedStatus = deployment.status ? getDeploymentStatus(deployment.status, false) : 'NOTINDEXING'; + + const { indexingErr } = deployment; + if (indexingErr) return (
- Error: {' '} - {indexingProgressErr} + Error: {indexingErr}
); return ( - +
+
+ + {truncateToDecimalPlace(indexingProgress, 2) * 100} % + + +
+ {deployment.lastHeight ? ( + + Current blocks: #{deployment.lastHeight} + + ) : ( + '' + )} +
); }, }, { - title: , + title: , + dataIndex: 'allocatedAmount', + render: (allocatedAmount: string) => { + return ( + + {formatNumber(formatSQT(allocatedAmount || '0'))} {TOKEN} + + ); + }, + }, + { + title: , + dataIndex: 'allocatedTotalRewards', + render: (allocatedTotalRewards, deployment) => { + return ( + + {formatNumber(formatSQT(allocatedTotalRewards || '0'))} {TOKEN} + + ); + }, + }, + { + title: , dataIndex: 'status', render: (status, deployment) => { - // TODO: will use metric service replace it. hardcode for now. - const sortedStatus = getDeploymentStatus(status, false); - return ; + return ( +
+ Add Allocation} + onSuccess={() => { + retry(() => { + indexerDeployments.refetch?.(); + }); + }} + initialStatus="Add" + > + + + Remove Allocation + + } + onSuccess={() => { + retry(() => { + indexerDeployments.refetch?.(); + }); + }} + initialStatus="Remove" + > +
+ ); }, }, ]; @@ -72,36 +201,198 @@ export const OwnDeployments: React.FC = ({ indexer, emptyList, desc }) => return (
{renderAsync( - indexerDeployments, + mergeAsync(indexerDeployments, isIndexer, sortedIndexer, runnerAllocation), { - error: (error) => ( - {`Failed to get projects: ${error.message}`} - ), + error: (error) => {`Failed to get projects: ${error.message}`}, loading: () => , data: (data) => { - if (!data || data.length === 0) - return <>{emptyList ?? {t('projects.nonDeployments')} }; + const [indexerDepolymentsData, isIndexerData, sortedIndexerData, runnerAllocationData] = data; + + if ( + !isIndexerData || + (!sortedIndexerData && (!indexerDepolymentsData || indexerDepolymentsData.length === 0)) + ) { + return <>{emptyList ?? {t('projects.nonDeployments')} }; + } - const sortedData = data.sort((deployment) => (deployment.isOffline ? 1 : -1)); + const sortedData = indexerDepolymentsData?.sort((deployment) => (deployment.isOffline ? 1 : -1)); + + const total = BigNumberJs(sortedIndexerData?.ownStake.current || 0) + .plus(BigNumberJs(sortedIndexerData?.totalDelegations.current || 0)) + .plus(BigNumberJs(runnerAllocationData?.left || 0)); + const renderLineData = { + ownStake: BigNumberJs(sortedIndexerData?.ownStake.current || 0) + .div(total) + .multipliedBy(100) + .toFixed(2), + delegation: BigNumberJs(sortedIndexerData?.totalDelegations.current || 0) + .div(total) + .multipliedBy(100) + .toFixed(2), + unAllocation: BigNumberJs(runnerAllocationData?.left || 0) + .div(total) + .multipliedBy(100) + .toFixed(2), + }; return ( <> {sortedDesc &&
{sortedDesc}
} - { - return { - onClick: (_) => { - if (record.projectId) { - navigate(`${PROJECT_NAV}/${record.projectId}/overview`); - } - }, - }; - }} - /> +
+ +
+ Current Total Stake + + + + + + { + retry(() => { + sortedIndexer?.refresh?.(); + }); + }} + > +
+ +
+ {BalanceLayout({ + mainBalance: BigNumber.from(sortedIndexerData?.totalStake.current).toString(), + secondaryBalance: BigNumber.from(sortedIndexerData?.totalStake.after).toString(), + })} +
+ +
+
+
+
+
+ +
+ {[ + { + name: 'Own Stake', + color: 'var(--sq-blue600)', + currentBalance: formatNumber( + BigNumber.from(sortedIndexerData?.ownStake.current).toString(), + ), + afterBalance: formatNumber(BigNumber.from(sortedIndexerData?.ownStake.after).toString()), + }, + { + name: 'Total Delegation', + color: 'var(--sq-success)', + currentBalance: formatNumber( + BigNumber.from(sortedIndexerData?.totalDelegations.current).toString(), + ), + afterBalance: formatNumber( + BigNumber.from(sortedIndexerData?.totalDelegations.after).toString(), + ), + }, + { + name: isOverAllocate ? 'Over Allocated' : 'Unallocated Stake', + color: 'var(--sq-warning)', + currentBalance: formatNumber(runnerAllocationData?.left || '0'), + afterBalance: formatNumber(runnerAllocationData?.left || '0'), + isOverAllocate: isOverAllocate, + }, + ].map((item) => { + return ( +
+
+ + {item.name} + +
+ + {item.currentBalance} {TOKEN} + {item.isOverAllocate && ( + + + + )} + + {!item.isOverAllocate && ( + + {item.afterBalance} {TOKEN} + + )} +
+
+ ); + })} +
+
+ } + style={{ boxShadow: 'none', marginBottom: 24, flex: 1 }} + > + + + Current Total Allocation + + } + titleExtra={BalanceLayout({ + mainBalance: formatNumber(runnerAllocationData?.used || '0'), + })} + style={{ boxShadow: 'none', marginBottom: 24, flex: 1 }} + > +
+ Total Allocation Rewards + + + {formatNumber( + formatSQT( + allocatedRewards.data?.indexerAllocationRewards?.groupedAggregates + ?.reduce((cur, add) => cur.plus(add.sum?.reward.toString() || '0'), BigNumberJs(0)) + .toString() || '0', + ), + )}{' '} + {TOKEN} + +
+
+ +
); }, diff --git a/src/pages/indexer/MyStaking/DoStake/DoStake.module.less b/src/pages/indexer/MyStaking/DoStake/DoStake.module.less new file mode 100644 index 000000000..f580baa5b --- /dev/null +++ b/src/pages/indexer/MyStaking/DoStake/DoStake.module.less @@ -0,0 +1,15 @@ +.stakeAction { + transform: rotate(90deg); + cursor: pointer; + width: 32px; + height: 32px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; +} + +.stakeAction:hover { + background: #4388DD0D; + color: var(--sq-blue600); +} \ No newline at end of file diff --git a/src/pages/indexer/MyStaking/DoStake/DoStake.tsx b/src/pages/indexer/MyStaking/DoStake/DoStake.tsx index be1f77692..aa8749f04 100644 --- a/src/pages/indexer/MyStaking/DoStake/DoStake.tsx +++ b/src/pages/indexer/MyStaking/DoStake/DoStake.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; +import { AiOutlineEllipsis } from 'react-icons/ai'; import { claimIndexerRewardsModalText, ModalApproveToken, @@ -10,7 +11,7 @@ import { tokenApprovalModalText, } from '@components'; import TransactionModal from '@components/TransactionModal'; -import { TransactionModalAction } from '@components/TransactionModal/TransactionModal'; +import { TransactionModalAction, TransactionModalRef } from '@components/TransactionModal/TransactionModal'; import { useSQToken, useWeb3 } from '@containers'; import { parseEther } from '@ethersproject/units'; import { useLockPeriod } from '@hooks'; @@ -18,12 +19,15 @@ import { useMaxUnstakeAmount } from '@hooks/useMaxUnstakeAmount'; import { useRewardCollectStatus } from '@hooks/useRewardCollectStatus'; import { Spinner, Typography } from '@subql/components'; import { formatEther, isUndefined, mergeAsync, renderAsyncArray } from '@utils'; +import { Button, Dropdown, Tooltip } from 'antd'; import assert from 'assert'; import dayjs from 'dayjs'; import { TFunction } from 'i18next'; import { useWeb3Store } from 'src/stores'; +import styles from './DoStake.module.less'; + enum StakeAction { Stake = 'stake', UnStake = 'unstake', @@ -71,6 +75,7 @@ const getContentText = ( export const DoStake: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) => { const [stakeAction, setStakeAction] = React.useState(StakeAction.Stake); + const modalRef = React.useRef(null); const { contracts } = useWeb3Store(); const { t } = useTranslation(); @@ -127,6 +132,7 @@ export const DoStake: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) => { label: t('indexer.stake'), key: StakeAction.Stake, onClick: () => setStakeAction(StakeAction.Stake), + style: { display: 'none' }, }; const unstakeButton: TransactionModalAction = { @@ -135,36 +141,85 @@ export const DoStake: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) => { onClick: () => setStakeAction(StakeAction.UnStake), disabled: isMaxUnstakeZero, tooltip: isMaxUnstakeZero ? t('indexer.unStakeTooltip') : undefined, + style: { display: 'none' }, }; const actions = [stakeButton, unstakeButton]; return ( - { - stakeAction === StakeAction.Stake ? balance.refetch() : maxUnstake.refetch(true); - onSuccess(); - }} - onClick={handleClick} - renderContent={(onSubmit, _, loading) => { - if (requireClaimIndexerRewards) { - return ( - rewardClaimStatus.refetch(true)} indexer={account ?? ''} /> - ); - } - - if (requireTokenApproval && !requireClaimIndexerRewards) { - return stakingAllowance.refetch()} isLoading={loading} />; - } - }} - /> + <> + + Stake more + + ), + key: 1, + onClick: () => { + if (modalRef.current) { + modalRef.current.showModal(stakeButton.key); + } + stakeButton.onClick?.(); + }, + }, + { + label: ( + + + + ), + key: 2, + }, + ], + }} + > +
+ +
+
+ { + stakeAction === StakeAction.Stake ? balance.refetch() : maxUnstake.refetch(true); + onSuccess(); + }} + onClick={handleClick} + renderContent={(onSubmit, _, loading) => { + if (requireClaimIndexerRewards) { + return ( + rewardClaimStatus.refetch(true)} indexer={account ?? ''} /> + ); + } + + if (requireTokenApproval && !requireClaimIndexerRewards) { + return stakingAllowance.refetch()} isLoading={loading} />; + } + }} + /> + ); }, }); diff --git a/src/pages/indexer/MyStaking/SetCommissionRate/SetCommissionRate.tsx b/src/pages/indexer/MyStaking/SetCommissionRate/SetCommissionRate.tsx index b597f8f55..af267225f 100644 --- a/src/pages/indexer/MyStaking/SetCommissionRate/SetCommissionRate.tsx +++ b/src/pages/indexer/MyStaking/SetCommissionRate/SetCommissionRate.tsx @@ -3,7 +3,10 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; +import { AiOutlineEllipsis } from 'react-icons/ai'; +import { TransactionModalRef } from '@components/TransactionModal/TransactionModal'; import { Spinner, Typography } from '@subql/components'; +import { Button, Dropdown } from 'antd'; import assert from 'assert'; import { TFunction } from 'i18next'; @@ -15,6 +18,8 @@ import { useWeb3 } from '../../../../containers'; import { COMMISSION_DIV_UNIT, useCommissionRate } from '../../../../hooks/useCommissionRate'; import { useRewardCollectStatus } from '../../../../hooks/useRewardCollectStatus'; import { mergeAsync, renderAsyncArray } from '../../../../utils'; +// TODO: make this kind of style into components library +import styles from '../DoStake/DoStake.module.less'; const getModalText = (requireClaimIndexerRewards = false, commissionRate: string | undefined, t: TFunction) => { if (requireClaimIndexerRewards) return claimIndexerRewardsModalText; @@ -34,6 +39,7 @@ export const SetCommissionRate: React.FC<{ onSuccess: () => void }> = ({ onSucce const { contracts } = useWeb3Store(); const { t } = useTranslation(); const { account } = useWeb3(); + const modalRef = React.useRef(null); const rewardClaimStatus = useRewardCollectStatus(account || ''); const commissionRate = useCommissionRate(account); @@ -60,31 +66,61 @@ export const SetCommissionRate: React.FC<{ onSuccess: () => void }> = ({ onSucce const modalText = getModalText(requireClaimIndexerRewards, sortedCommissionRate?.toString(), t); return ( - { - commissionRate.refetch(true); - onSuccess(); - }} - renderContent={(onSubmit, _, loading) => { - if (requireClaimIndexerRewards) { - return rewardClaimStatus.refetch()} indexer={account ?? ''} />; - } - }} - /> + <> + + Update Commission Rate + + ), + key: 1, + onClick: () => { + console.warn(123123); + if (modalRef.current) { + modalRef.current.showModal('commission'); + } + }, + }, + ], + }} + > +
+ +
+
+ { + commissionRate.refetch(true); + onSuccess(); + }} + renderContent={(onSubmit, _, loading) => { + if (requireClaimIndexerRewards) { + return ( + rewardClaimStatus.refetch()} indexer={account ?? ''} /> + ); + } + }} + /> + ); }, }); diff --git a/src/pages/indexer/index.tsx b/src/pages/indexer/index.tsx index 8e78e3bba..f1f2a0dd3 100644 --- a/src/pages/indexer/index.tsx +++ b/src/pages/indexer/index.tsx @@ -8,16 +8,12 @@ import { Outlet } from 'react-router'; import { AppSidebar } from '../../components'; import { ROUTES } from '../../utils'; -const { MY_PROJECTS, MY_PLANS, MY_STAKING, SERVICE_AGREEMENTS, OFFER_MARKETPLACE, MY_DELEGATORS } = ROUTES; +const { MY_PROJECTS, MY_PLANS, SERVICE_AGREEMENTS, OFFER_MARKETPLACE, MY_DELEGATORS } = ROUTES; const Indexer: React.FC = () => { const { t } = useTranslation(); const sidebarList = [ - { - label: t('indexer.myStaking'), - link: MY_STAKING, - }, { label: t('myProjects.title'), link: MY_PROJECTS, @@ -34,10 +30,6 @@ const Indexer: React.FC = () => { label: t('indexer.myPlans'), link: MY_PLANS, }, - { - label: 'My Allocations', - link: 'my-allocations', - }, { label: t('plans.category.offerMarketplace'), link: OFFER_MARKETPLACE, diff --git a/src/router/routes.ts b/src/router/routes.ts index fc13c6661..6fd7d6ba0 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -172,16 +172,12 @@ export const routers: BasicRouteType[] = [ { path: '/indexer', component: React.lazy(() => import('../pages/indexer/index')), - redirect: '/indexer/my-staking', + redirect: '/indexer/my-projects', children: [ { path: 'my-staking/*', component: React.lazy(() => import('../pages/indexer/MyStaking/MyStaking')), }, - { - path: 'my-allocations/*', - component: React.lazy(() => import('../pages/indexer/MyAllocation/MyAllocation')), - }, { path: 'my-delegators/*', component: React.lazy(() => import('../pages/indexer/MyDelegators/MyDelegators')), diff --git a/src/utils/index.tsx b/src/utils/index.tsx index 1bd0e8597..c3148a6e9 100644 --- a/src/utils/index.tsx +++ b/src/utils/index.tsx @@ -92,7 +92,7 @@ export const filterSuccessPromoiseSettledResult = ( result: PromiseSettledResult, ): result is PromiseFulfilledResult => result.status === 'fulfilled'; -export type AsyncData = Readonly<{ data?: T; loading: boolean; error?: Error }>; +export type AsyncData = Readonly<{ data?: T; loading: boolean; error?: Error; refetch?: () => void }>; type Data = T | undefined; type MergedData = [Data, Data, Data, Data, Data, Data]; // NOTE: update mergeAsync returnType when migrate to sdk