From 55634fae0ca52cce5dd3e80c2e906ef8056f4003 Mon Sep 17 00:00:00 2001 From: amalcaraz Date: Tue, 26 Mar 2024 13:09:02 +0100 Subject: [PATCH] fix: unlink buttons for CRN owner in detail pages --- .../common/ComputeResourceNodesTable/cmp.tsx | 8 +- src/components/common/LinkCRNButton/cmp.tsx | 50 ++++---- src/components/common/StakeButton/cmp.tsx | 2 +- .../ComputeResourceNodeDetailPage/cmp.tsx | 41 +++--- .../earn/CoreChannelNodeDetailPage/cmp.tsx | 11 +- src/domain/node.ts | 37 ++++-- src/hooks/common/useFilterUserLinkedNodes.ts | 2 +- src/hooks/common/useLinking.ts | 119 +++++++++++++----- src/hooks/common/useNodeDetail.ts | 7 +- src/hooks/common/useStaking.ts | 5 +- .../earn/useComputeResourceNodeDetailPage.tsx | 64 ++++++---- .../earn/useComputeResourceNodesPage.tsx | 33 ++--- .../earn/useCoreChannelNodeDetailPage.tsx | 9 +- src/hooks/pages/earn/useStakingPage.tsx | 4 +- 14 files changed, 250 insertions(+), 142 deletions(-) diff --git a/src/components/common/ComputeResourceNodesTable/cmp.tsx b/src/components/common/ComputeResourceNodesTable/cmp.tsx index 242bc5b..776885b 100644 --- a/src/components/common/ComputeResourceNodesTable/cmp.tsx +++ b/src/components/common/ComputeResourceNodesTable/cmp.tsx @@ -17,8 +17,12 @@ import ButtonLink from '../ButtonLink' import Image from 'next/image' import { apiServer } from '@/helpers/constants' import { UseSortedListReturn } from '@/hooks/common/useSortedList' +import { UseLinkingReturn } from '@/hooks/common/useLinking' -export type ComputeResourceNodesTableProps = { +export type ComputeResourceNodesTableProps = Pick< + UseLinkingReturn, + 'handleLink' | 'handleUnlink' +> & { filteredNodes?: CRN[] userNode?: CCN account?: Account @@ -27,8 +31,6 @@ export type ComputeResourceNodesTableProps = { loadItemsDisabled?: boolean handleLoadItems?: () => Promise handleSortItems?: UseSortedListReturn['handleSortItems'] - handleLink: (nodeHash: string) => void - handleUnlink: (nodeHash: string) => void } export const ComputeResourceNodesTable = ({ diff --git a/src/components/common/LinkCRNButton/cmp.tsx b/src/components/common/LinkCRNButton/cmp.tsx index f07d92e..623b6a4 100644 --- a/src/components/common/LinkCRNButton/cmp.tsx +++ b/src/components/common/LinkCRNButton/cmp.tsx @@ -1,55 +1,63 @@ import { memo, useCallback, useMemo } from 'react' import { Button } from '@aleph-front/core' -import { Account } from 'aleph-sdk-ts/dist/accounts/account' -import { CCN, CRN, NodeManager } from '@/domain/node' +import { CCN, CRN } from '@/domain/node' +import { UseLinkingReturn, useLinking } from '@/hooks/common/useLinking' export type LinkCRNButtonProps = { node: CRN userNode?: CCN - account?: Account accountBalance?: number - onLink: (nodeHash: string) => void - onUnlink: (nodeHash: string) => void + onLink?: UseLinkingReturn['handleLink'] + onUnlink?: UseLinkingReturn['handleUnlink'] } // https://github.com/aleph-im/aleph-account/blob/main/src/components/NodesTable.vue#L298 export const LinkCRNButton = ({ node, userNode, - account, onLink, onUnlink, }: LinkCRNButtonProps) => { - // @todo: Refactor this (use singleton) - const nodeManager = useMemo(() => new NodeManager(account), [account]) + const { + isLinkableByUser: isLinkableByUserCheck, + isUnlinkableByUser: isUnlinkableByUserCheck, + handleLink: defaultHandleLink, + handleUnlink: defaultHandleUnlink, + } = useLinking() - const isLinkedNode = useMemo(() => { - return nodeManager.isUserLinked(node, userNode) - }, [node, nodeManager, userNode]) + const handleLink = onLink || defaultHandleLink + const handleUnlink = onUnlink || defaultHandleUnlink - const isDisabled = useMemo(() => { - const [canLink] = nodeManager.isLinkable(node, userNode) - return !canLink - }, [nodeManager, node, userNode]) + const isLinkableByUser = useMemo( + () => node && userNode && isLinkableByUserCheck(node, userNode), + [isLinkableByUserCheck, node, userNode], + ) + + const isUnlinkableByUser = useMemo( + () => node && isUnlinkableByUserCheck(node), + [isUnlinkableByUserCheck, node], + ) const handleOnClick = useCallback(() => { - if (isLinkedNode) { - onUnlink(node.hash) + if (!userNode) return + + if (isUnlinkableByUser) { + handleUnlink(node.hash) } else { - onLink(node.hash) + handleLink(node, userNode) } - }, [isLinkedNode, onUnlink, node.hash, onLink]) + }, [handleLink, handleUnlink, isUnlinkableByUser, node, userNode]) return ( <> - {!isLinkedNode ? ( + {!isUnlinkableByUser ? ( diff --git a/src/components/common/StakeButton/cmp.tsx b/src/components/common/StakeButton/cmp.tsx index 9cbc0a1..05319b4 100644 --- a/src/components/common/StakeButton/cmp.tsx +++ b/src/components/common/StakeButton/cmp.tsx @@ -27,7 +27,7 @@ export const StakeButton = ({ }, [node, nodeManager]) const isDisabled = useMemo(() => { - const [canStake] = nodeManager.isStakeable(node, accountBalance) + const [canStake] = nodeManager.isStakeableBy(node, accountBalance) return !canStake }, [nodeManager, node, accountBalance]) diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx index 2ae2c9e..fc26e9c 100644 --- a/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx @@ -31,8 +31,6 @@ export const ComputeResourceNodeDetailPage = () => { baseLatency, lastMetricsCheck, calculatedRewards, - isUserLinked, - isLinkable, creationDate, nameCtrl, descriptionCtrl, @@ -47,6 +45,9 @@ export const ComputeResourceNodeDetailPage = () => { nodeSpecs, nodeIssue, createInstanceUrl, + isLinked, + isLinkableByUser, + isUnlinkableByUser, // nodeBenchmark, handleRemove, handleSubmit, @@ -355,11 +356,11 @@ export const ComputeResourceNodeDetailPage = () => {
- {!node?.parentData ? ( + {!isLinked ? (
- {!isUserLinked && isLinkable ? ( + {isLinkableByUser ? (
) : (
- - - - {isUserLinked ? ( + {node?.parentData && ( + + + + )} + {isUnlinkableByUser && ( - ) : ( - <> )}
)} diff --git a/src/components/pages/earn/CoreChannelNodeDetailPage/cmp.tsx b/src/components/pages/earn/CoreChannelNodeDetailPage/cmp.tsx index 9ec3ce6..d5cca7b 100644 --- a/src/components/pages/earn/CoreChannelNodeDetailPage/cmp.tsx +++ b/src/components/pages/earn/CoreChannelNodeDetailPage/cmp.tsx @@ -45,9 +45,11 @@ export const CoreChannelNodeDetailPage = () => { lockedCtrl, registrationUrlCtrl, isDirty, + account, + isUnlinkableByUser, + handleUnlink, handleRemove, handleSubmit, - handleUnlink, } = useCoreChannelNodeDetailPage() return ( @@ -258,6 +260,7 @@ export const CoreChannelNodeDetailPage = () => { }, (_, i) => { const crn = node?.crnsData[i] + const isCRNOwner = crn?.owner === account?.address return !crn ? (
@@ -292,12 +295,10 @@ export const CoreChannelNodeDetailPage = () => { ImageCmp={Image} /> - {isOwner ? ( - - ) : ( - <> )}
) diff --git a/src/domain/node.ts b/src/domain/node.ts index 9f4f8d5..a3e7048 100644 --- a/src/domain/node.ts +++ b/src/domain/node.ts @@ -606,7 +606,11 @@ export class NodeManager { return !!node.stakers[this.account.address] } - isUserLinked(node: CRN, userNode?: CCN): boolean { + isLinked(node: CRN): boolean { + return !!node.parentData + } + + isUnlinkableBy(node: CRN, userNode?: CCN): boolean { if (!userNode) return false return ( @@ -615,17 +619,22 @@ export class NodeManager { ) } - isStakeable(node: CCN, balance: number): [boolean, string] { - if (!this.account) return [false, 'Please login'] - - if (balance < 10_000) - return [false, 'You need at least 10000 ALEPH to stake'] - + isStakeable(node: CCN): [boolean, string] { if (node.total_staked >= NodeManager.maxStakedPerNode) return [false, 'Too many ALEPH staked on that node'] if (this.isLocked(node)) return [false, 'This node is locked'] + return [true, `${node.hash} is stakeable`] + } + + isStakeableBy(node: CCN, balance: number | undefined): [boolean, string] { + const isStakeable = this.isStakeable(node) + if (!isStakeable[0]) return isStakeable + + if (!balance || balance < 10_000) + return [false, 'You need at least 10000 ALEPH to stake'] + if (this.isUserNode(node)) return [false, "You can't stake while you operate a node"] @@ -634,8 +643,18 @@ export class NodeManager { return [true, `Stake ${balance.toFixed(2)} ALEPH in this node`] } - isLinkable(node: CRN, userNode?: CCN): [boolean, string] { - if (!this.account) return [false, 'Please login'] + isLinkable(node: CRN): [boolean, string] { + if (node.locked) return [false, 'This node is locked'] + + if (!!node.parent) + return [false, `The node is already linked to ${node.parent} ccn`] + + return [true, `${node.hash} is linkable`] + } + + isLinkableBy(node: CRN, userNode: CCN | undefined): [boolean, string] { + const isLinkable = this.isLinkable(node) + if (!isLinkable[0]) return isLinkable if (!userNode || !this.isUserNode(userNode)) return [false, "The user doesn't own a core channel node"] diff --git a/src/hooks/common/useFilterUserLinkedNodes.ts b/src/hooks/common/useFilterUserLinkedNodes.ts index 031c855..97f62bb 100644 --- a/src/hooks/common/useFilterUserLinkedNodes.ts +++ b/src/hooks/common/useFilterUserLinkedNodes.ts @@ -25,7 +25,7 @@ export function useFilterUserLinkedNodes({ const filterUserNodes = useCallback( (nodes?: CRN[]) => { if (!nodes) return - return nodes.filter((node) => nodeManager.isUserLinked(node, userNode)) + return nodes.filter((node) => nodeManager.isUnlinkableBy(node, userNode)) }, [nodeManager, userNode], ) diff --git a/src/hooks/common/useLinking.ts b/src/hooks/common/useLinking.ts index 25ed6ba..9ab7779 100644 --- a/src/hooks/common/useLinking.ts +++ b/src/hooks/common/useLinking.ts @@ -3,11 +3,19 @@ import { CCN, CRN, NodeManager } from '@/domain/node' import { EntityAddAction } from '@/store/entity' import { useNotification } from '@aleph-front/core' import { useCallback, useMemo } from 'react' -import { useUserCoreChannelNode } from './useUserCoreChannelNode' export type UseLinkingReturn = { - handleLink: (nodeHash: string) => Promise - handleUnlink: (nodeHash: string) => Promise + isLinked: (crnHashOrNode: string | CRN) => boolean + isLinkableByUser: ( + crnHashOrNode: string | CRN, + ccnHashOrNode: string | CCN, + ) => boolean + isUnlinkableByUser: (crnHashOrNode: string | CRN) => boolean + handleLink: ( + crnHashOrNode: string | CRN, + ccnHashOrNode: string | CCN, + ) => Promise + handleUnlink: (crnHashOrNode: string | CRN) => Promise } function calculateVirtualNodesLink(userNode: CCN, linkNode: CRN): [CCN, CRN] { @@ -53,39 +61,89 @@ function calculateVirtualNodesUnlink( export function useLinking(): UseLinkingReturn { const [state, dispatch] = useAppState() const { account } = state.account - const { entities: nodes } = state.crns + const { entities: crns } = state.crns + const { entities: ccns } = state.ccns const nodeManager = useMemo(() => new NodeManager(account), [account]) const noti = useNotification() - const { userNode } = useUserCoreChannelNode({}) + const getCRNNode = useCallback( + (crnHashOrNode: string | CRN) => { + return typeof crnHashOrNode === 'string' + ? crns?.find((node) => node.hash === crnHashOrNode) + : crnHashOrNode + }, + [crns], + ) + + const getCCNNode = useCallback( + (ccnHashOrNode: string | CCN) => { + return typeof ccnHashOrNode === 'string' + ? ccns?.find((node) => node.hash === ccnHashOrNode) + : ccnHashOrNode + }, + [ccns], + ) + + const isLinked = useCallback( + (crnHashOrNode: string | CRN) => { + const node = getCRNNode(crnHashOrNode) + if (!node) return false + + return nodeManager.isLinked(node) + }, + [getCRNNode, nodeManager], + ) + + const isLinkableByUser = useCallback( + (crnHashOrNode: string | CRN, ccnHashOrNode: string | CCN) => { + const node = getCRNNode(crnHashOrNode) + const userNode = getCCNNode(ccnHashOrNode) + if (!node || !userNode) return false + + return nodeManager.isLinkableBy(node, userNode)[0] + }, + [getCCNNode, getCRNNode, nodeManager], + ) + + const isUnlinkableByUser = useCallback( + (crnHashOrNode: string | CRN) => { + const node = getCRNNode(crnHashOrNode) + if (!node) return false + + const userNode = getCCNNode(node.parentData || node.parent || '') + if (!userNode) return false + + return nodeManager.isUnlinkableBy(node, userNode) + }, + + [getCCNNode, getCRNNode, nodeManager], + ) const handleLink = useCallback( - async (nodeHash: string) => { + async (crnHashOrNode: string | CRN, ccnHashOrNode: string | CCN) => { try { if (!noti) throw new Error('Notification not ready') - if (!account) throw new Error('Invalid account') - if (!userNode) throw new Error('Invalid user node') - const targetNode = nodes?.find((node) => node.hash === nodeHash) - if (!targetNode) throw new Error('Invalid staking node') + const crnNode = getCRNNode(crnHashOrNode) + if (!crnNode) throw new Error('Invalid CRN node') - if ( - !nodeManager.isLinkable(targetNode, userNode) || - nodeManager.isUserLinked(targetNode, userNode) - ) + const ccnNode = getCCNNode(ccnHashOrNode) + if (!ccnNode) throw new Error('Invalid CCN node') + + if (!isLinkableByUser(crnNode, ccnNode)) throw new Error('Not linkable node') - await nodeManager.linkComputeResourceNode(nodeHash) + await nodeManager.linkComputeResourceNode(crnNode.hash) noti.add({ variant: 'success', title: 'Success', - text: `Linked resource node "${nodeHash}" successfully.`, + text: `Linked resource node "${crnNode.hash}" successfully.`, }) - const [ccn, crn] = calculateVirtualNodesLink(userNode, targetNode) + const [ccn, crn] = calculateVirtualNodesLink(ccnNode, crnNode) dispatch( new EntityAddAction({ @@ -112,31 +170,31 @@ export function useLinking(): UseLinkingReturn { return false }, - [account, dispatch, nodeManager, nodes, noti, userNode], + [dispatch, getCCNNode, getCRNNode, isLinkableByUser, nodeManager, noti], ) const handleUnlink = useCallback( - async (nodeHash: string) => { + async (crnHashOrNode: string | CRN) => { try { if (!noti) throw new Error('Notification not ready') - if (!account) throw new Error('Invalid account') - if (!userNode) throw new Error('Invalid user node') - const targetNode = nodes?.find((node) => node.hash === nodeHash) - if (!targetNode) throw new Error('Invalid staking node') + const crnNode = getCRNNode(crnHashOrNode) + if (!crnNode) throw new Error('Invalid CRN node') - if (!nodeManager.isUserLinked(targetNode, userNode)) - throw new Error('Not linkable node') + const ccnNode = getCCNNode(crnNode.parentData || crnNode.parent || '') + if (!ccnNode) throw new Error('Invalid CCN node') + + if (!isUnlinkableByUser(crnNode)) throw new Error('Not unlinkable node') - await nodeManager.unlinkComputeResourceNode(nodeHash) + await nodeManager.unlinkComputeResourceNode(crnNode.hash) noti.add({ variant: 'success', title: 'Success', - text: `Unlinked resource node "${nodeHash}" successfully.`, + text: `Unlinked resource node "${crnNode.hash}" successfully.`, }) - const [ccn, crn] = calculateVirtualNodesUnlink(userNode, targetNode) + const [ccn, crn] = calculateVirtualNodesUnlink(ccnNode, crnNode) dispatch( new EntityAddAction({ @@ -163,10 +221,13 @@ export function useLinking(): UseLinkingReturn { return false }, - [account, dispatch, nodeManager, nodes, noti, userNode], + [dispatch, getCCNNode, getCRNNode, isUnlinkableByUser, nodeManager, noti], ) return { + isLinked, + isLinkableByUser, + isUnlinkableByUser, handleLink, handleUnlink, } diff --git a/src/hooks/common/useNodeDetail.ts b/src/hooks/common/useNodeDetail.ts index 064a8d3..872fe65 100644 --- a/src/hooks/common/useNodeDetail.ts +++ b/src/hooks/common/useNodeDetail.ts @@ -4,6 +4,7 @@ import { useNotification } from '@aleph-front/core' import { AlephNode, NodeManager } from '@/domain/node' import { useAppState } from '@/contexts/appState' import { EntityDelAction } from '@/store/entity' +import { Account } from 'aleph-sdk-ts/dist/accounts/account' export type UseNodeDetailProps = { node?: N @@ -11,6 +12,7 @@ export type UseNodeDetailProps = { } export type UseNodeDetailReturn = { + account?: Account node?: N nodes?: N[] nodesOnSameASN?: number @@ -100,13 +102,14 @@ export function useNodeDetail({ }, [node]) const isOwner = useMemo( - () => node?.owner === account?.address, - [account, node], + () => node && nodeManager.isUserNode(node), + [nodeManager, node], ) // ----------------------------- return { + account, node, nodesOnSameASN, baseLatency, diff --git a/src/hooks/common/useStaking.ts b/src/hooks/common/useStaking.ts index 1d4a6c4..462b3a7 100644 --- a/src/hooks/common/useStaking.ts +++ b/src/hooks/common/useStaking.ts @@ -87,10 +87,7 @@ export function useStaking(): UseStakingReturn { const targetNode = nodes?.find((node) => node.hash === nodeHash) if (!targetNode) throw new Error('Invalid staking node') - if ( - !nodeManager.isStakeable(targetNode, balance) || - nodeManager.isUserStake(targetNode) - ) + if (!nodeManager.isStakeableBy(targetNode, balance)) throw new Error('Not stakeable node') await stakeManager.stake(nodeHash) diff --git a/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx b/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx index 3e4225b..c2cca73 100644 --- a/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx +++ b/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx @@ -41,16 +41,17 @@ export type UseComputeResourceNodeDetailPageReturn = UseNodeDetailReturn & node?: CRN userNode?: CCN calculatedRewards?: number - isUserLinked?: boolean - isLinkable?: boolean asnTier?: UseHostingProviderTopItem nodeSpecs?: CRNSpecs nodeIssue?: StreamNotSupportedIssue createInstanceUrl?: string nodeBenchmark?: CRNBenchmark + isLinked?: boolean + isLinkableByUser?: boolean + isUnlinkableByUser?: boolean handleRemove: () => void - handleLink: () => void - handleUnlink: () => void + handleLink: () => Promise + handleUnlink: () => Promise } export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetailPageReturn { @@ -67,7 +68,7 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail // @todo: Refactor this (use singleton) const nodeManager = useMemo(() => new NodeManager(account), [account]) - nodeManager.isUserLinked + // ----------------------------- const { calculatedRewards } = useAccountRewards({ @@ -80,37 +81,51 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail // ----------------------------- - const isUserLinked = useMemo(() => { - if (!node) return - return nodeManager.isUserLinked(node, userNode) - }, [node, nodeManager, userNode]) + const { + isLinked: isLinkedCheck, + isLinkableByUser: isLinkableByUserCheck, + isUnlinkableByUser: isUnlinkableByUserCheck, + handleLink: handleLinkBase, + handleUnlink: handleUnlinkBase, + } = useLinking() + + const isLinked = useMemo( + () => node && isLinkedCheck(node), + [isLinkedCheck, node], + ) - const isLinkable = useMemo(() => { - if (!node) return - return nodeManager.isLinkable(node, userNode)[0] - }, [node, nodeManager, userNode]) + const isLinkableByUser = useMemo( + () => node && userNode && isLinkableByUserCheck(node, userNode), + [isLinkableByUserCheck, node, userNode], + ) - const { handleLink: handleLinkBase, handleUnlink: handleUnlinkBase } = - useLinking() + const isUnlinkableByUser = useMemo( + () => node && isUnlinkableByUserCheck(node), + [isUnlinkableByUserCheck, node], + ) const handleLink = useCallback(async () => { - if (!node) return + if (!node || !userNode) return false - const success = await handleLinkBase(node.hash) - if (!success) return + const success = await handleLinkBase(node.hash, userNode.hash) + if (!success) return success - if (!userNode) return + if (!userNode) return success router.replace(`/earn/ccn/${userNode.hash}`) + + return success }, [handleLinkBase, router, node, userNode]) const handleUnlink = useCallback(async () => { - if (!node) return + if (!node) return false const success = await handleUnlinkBase(node.hash) - if (!success) return + if (!success) return success - if (!userNode) return + if (!userNode) return success router.replace(`/earn/ccn/${userNode?.hash}`) + + return success }, [handleUnlinkBase, router, node, userNode]) // ----------------------------- @@ -233,13 +248,14 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail node, userNode, calculatedRewards, - isUserLinked, - isLinkable, asnTier, nodeSpecs, nodeIssue, createInstanceUrl, // nodeBenchmark, + isLinked, + isLinkableByUser, + isUnlinkableByUser, handleLink, handleUnlink, ...formProps, diff --git a/src/hooks/pages/earn/useComputeResourceNodesPage.tsx b/src/hooks/pages/earn/useComputeResourceNodesPage.tsx index a774819..ee60a36 100644 --- a/src/hooks/pages/earn/useComputeResourceNodesPage.tsx +++ b/src/hooks/pages/earn/useComputeResourceNodesPage.tsx @@ -13,7 +13,7 @@ import { useFilterNodeIssues } from '@/hooks/common/useFilterNodeIssues' import { useFilterUserNodes } from '@/hooks/common/useFilterUserNodes' import { useSortByIssuesNodes } from '@/hooks/common/useSortByIssuesNodes' import { useUserCoreChannelNode } from '@/hooks/common/useUserCoreChannelNode' -import { useLinking } from '@/hooks/common/useLinking' +import { UseLinkingReturn, useLinking } from '@/hooks/common/useLinking' import { useRouter } from 'next/router' import { useRequestCRNSpecs } from '@/hooks/common/useRequestEntity/useRequestCRNSpecs' @@ -21,8 +21,8 @@ export type UseComputeResourceNodesPageProps = { nodes?: CRN[] } -export type UseComputeResourceNodesPageReturn = - UseComputeResourceNodesReturn & { +export type UseComputeResourceNodesPageReturn = UseComputeResourceNodesReturn & + Pick & { userNodes?: CRN[] filteredUserNodes?: CRN[] userNodesIssues: Record @@ -41,8 +41,6 @@ export type UseComputeResourceNodesPageReturn = paginatedSortedFilteredNodes?: CRN[] loadItemsDisabled: boolean handleLoadItems: () => Promise - handleLink: (nodeHash: string) => void - handleUnlink: (nodeHash: string) => void handleTabChange: (tab: string) => void handleLinkableOnlyChange: (e: ChangeEvent) => void } @@ -129,9 +127,7 @@ export function useComputeResourceNodesPage( const linkableNodes = useMemo(() => { if (!baseFilteredNodes) return return baseFilteredNodes.filter( - (node) => - nodeManager.isLinkable(node, userNode)[0] && - !nodeManager.isUserLinked(node, userNode), + (node) => nodeManager.isLinkableBy(node, userNode)[0], ) }, [baseFilteredNodes, nodeManager, userNode]) @@ -167,23 +163,28 @@ export function useComputeResourceNodesPage( useLinking() const handleLink = useCallback( - async (nodeHash: string) => { - const success = await handleLinkBase(nodeHash) - if (!success) return - if (!userNode?.hash) return + async (crnHashOrNode: string | CRN) => { + if (!userNode?.hash) return false + + const success = await handleLinkBase(crnHashOrNode, userNode.hash) + if (!success) return success router.replace(`/earn/ccn/${userNode.hash}`) + + return success }, [handleLinkBase, router, userNode], ) const handleUnlink = useCallback( - async (nodeHash: string) => { - const success = await handleUnlinkBase(nodeHash) - if (!success) return - if (!userNode?.hash) return + async (crnHashOrNode: string | CRN) => { + const success = await handleUnlinkBase(crnHashOrNode) + if (!success) return success + if (!userNode?.hash) return success router.replace(`/earn/ccn/${userNode.hash}`) + + return success }, [handleUnlinkBase, router, userNode], ) diff --git a/src/hooks/pages/earn/useCoreChannelNodeDetailPage.tsx b/src/hooks/pages/earn/useCoreChannelNodeDetailPage.tsx index c4aad29..7997bdc 100644 --- a/src/hooks/pages/earn/useCoreChannelNodeDetailPage.tsx +++ b/src/hooks/pages/earn/useCoreChannelNodeDetailPage.tsx @@ -12,14 +12,15 @@ import { UseEditCoreChannelNodeFormReturn, useEditCoreChannelNodeForm, } from '@/hooks/form/useEditCoreChannelNodeForm' -import { useLinking } from '@/hooks/common/useLinking' +import { UseLinkingReturn, useLinking } from '@/hooks/common/useLinking' export type UseCoreChannelNodeDetailPageProps = { nodes?: CCN[] } export type UseCoreChannelNodeDetailPageReturn = UseNodeDetailReturn & - UseEditCoreChannelNodeFormReturn & { + UseEditCoreChannelNodeFormReturn & + Pick & { nodes?: CCN[] node?: CCN aggregateLatency?: string @@ -28,7 +29,6 @@ export type UseCoreChannelNodeDetailPageReturn = UseNodeDetailReturn & relativeETHHeightPercent?: number calculatedRewards?: number locked?: boolean - handleUnlink: (nodeHash: string) => void } export function useCoreChannelNodeDetailPage(): UseCoreChannelNodeDetailPageReturn { @@ -97,7 +97,7 @@ export function useCoreChannelNodeDetailPage(): UseCoreChannelNodeDetailPageRetu // ----------------------------- - const { handleUnlink } = useLinking() + const { isUnlinkableByUser, handleUnlink } = useLinking() // ----------------------------- @@ -125,6 +125,7 @@ export function useCoreChannelNodeDetailPage(): UseCoreChannelNodeDetailPageRetu metricsLatency, relativeETHHeightPercent, calculatedRewards, + isUnlinkableByUser, handleUnlink, ...formProps, ...details, diff --git a/src/hooks/pages/earn/useStakingPage.tsx b/src/hooks/pages/earn/useStakingPage.tsx index 0391ae6..f7d10dc 100644 --- a/src/hooks/pages/earn/useStakingPage.tsx +++ b/src/hooks/pages/earn/useStakingPage.tsx @@ -106,9 +106,7 @@ export function useStakingPage( const stakeableNodes = useMemo(() => { if (!baseFilteredNodes) return return baseFilteredNodes.filter( - (node) => - nodeManager.isStakeable(node, accountBalance)[0] && - !nodeManager.isUserStake(node), + (node) => nodeManager.isStakeableBy(node, accountBalance)[0], ) }, [accountBalance, baseFilteredNodes, nodeManager])