mirror of
https://github.com/lux-tel/websites.git
synced 2026-10-30 20:31:36 +00:00
206 lines
7.4 KiB
TypeScript
206 lines
7.4 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
getContributedContributor,
|
|
getContributionRangeForWallet,
|
|
getReservedContributorNonContributed,
|
|
} from '@/app/stake/[address]/StakeInfo';
|
|
import {
|
|
InfoNodeCard,
|
|
NodeItem,
|
|
NodeItemLabel,
|
|
NodeItemSeparator,
|
|
NodeItemValue,
|
|
} from '@/components/InfoNodeCard';
|
|
import { STAKE_CONTRACT_STATE, parseStakeContractState } from '@/components/StakedNode/state';
|
|
import { NodeOperatorIndicator } from '@/components/StakedNodeCard';
|
|
import { useCurrentActor } from '@/hooks/useCurrentActor';
|
|
import { SESSION_NODE_FULL_STAKE_AMOUNT } from '@/lib/constants';
|
|
import { formatPercentage } from '@/lib/locale-client';
|
|
import { getTotalStaked } from '@/lib/maths';
|
|
import { ButtonDataTestId } from '@/testing/data-test-ids';
|
|
import { formatSENTBigInt } from '@session/contracts/hooks/Token';
|
|
import type { ContributionContract } from '@session/staking-api-js/schema';
|
|
import { ContactIcon } from '@session/ui/icons/ContactIcon';
|
|
import { SessionTokenIcon } from '@session/ui/icons/SessionTokenIcon';
|
|
import { cn } from '@session/ui/lib/utils';
|
|
import { AlertTooltip, Tooltip } from '@session/ui/ui/tooltip';
|
|
import { areHexesEqual } from '@session/util-crypto/string';
|
|
import { useTranslations } from 'next-intl';
|
|
import { usePathname } from 'next/navigation';
|
|
import { type HTMLAttributes, forwardRef } from 'react';
|
|
|
|
export const StakedToIndicator = forwardRef<
|
|
HTMLDivElement,
|
|
HTMLAttributes<HTMLDivElement> & {
|
|
tokenAmount: bigint;
|
|
hideTextOnMobile?: boolean;
|
|
}
|
|
>(({ className, hideTextOnMobile, tokenAmount, ...props }, ref) => {
|
|
const dictionary = useTranslations('nodeCard.open');
|
|
|
|
return (
|
|
<>
|
|
<Tooltip
|
|
tooltipContent={dictionary('stakedToTooltip', {
|
|
tokenAmount: formatSENTBigInt(tokenAmount),
|
|
})}
|
|
>
|
|
<div
|
|
ref={ref}
|
|
className={cn(
|
|
'flex flex-row items-center gap-1 align-middle font-normal text-session-green text-sm md:text-base',
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
<SessionTokenIcon className="h-3.5 w-3.5 fill-session-green" />
|
|
{hideTextOnMobile ? (
|
|
<span className="hidden md:block">{dictionary('stakedTo')}</span>
|
|
) : (
|
|
dictionary('stakedTo')
|
|
)}
|
|
</div>
|
|
</Tooltip>
|
|
</>
|
|
);
|
|
});
|
|
|
|
const OpenNodeCard = forwardRef<
|
|
HTMLDivElement,
|
|
HTMLAttributes<HTMLDivElement> & {
|
|
contract: ContributionContract;
|
|
forceSmall?: boolean;
|
|
showAlreadyRunningWarning?: boolean;
|
|
}
|
|
>(({ className, forceSmall, showAlreadyRunningWarning, contract, ...props }, ref) => {
|
|
const dictionary = useTranslations('nodeCard.open');
|
|
const generalNodeDictionary = useTranslations('sessionNodes.general');
|
|
const dictGeneral = useTranslations('general');
|
|
const titleFormat = useTranslations('modules.title');
|
|
const pathname = usePathname();
|
|
const address = useCurrentActor();
|
|
|
|
const isOperator = areHexesEqual(contract.operator_address, address);
|
|
const contributor = getContributedContributor(contract, address);
|
|
const reservedContributor = getReservedContributorNonContributed(contract, address);
|
|
|
|
const { minStake, maxStake } = getContributionRangeForWallet(contract, address);
|
|
const totalStaked = getTotalStaked(contract.contributors);
|
|
|
|
const connectedWalletNonContributedReservedStakeAmount =
|
|
(!contributor && reservedContributor?.reserved) || 0;
|
|
|
|
const state = parseStakeContractState(contract);
|
|
|
|
const isActive = pathname === `/stake/${contract.address}`;
|
|
|
|
return (
|
|
<InfoNodeCard
|
|
ref={ref}
|
|
className={className}
|
|
pubKey={contract.service_node_pubkey}
|
|
statusIndicatorColour={
|
|
state === STAKE_CONTRACT_STATE.AWAITING_OPERATOR_CONTRIBUTION || showAlreadyRunningWarning
|
|
? 'yellow'
|
|
: connectedWalletNonContributedReservedStakeAmount
|
|
? 'green'
|
|
: 'blue'
|
|
}
|
|
isActive={pathname === `/stake/${contract.address}`}
|
|
forceSmall={forceSmall}
|
|
button={
|
|
!isActive
|
|
? {
|
|
ariaLabel: dictionary('viewButton.ariaLabel'),
|
|
text: dictionary('viewButton.text'),
|
|
dataTestId: ButtonDataTestId.Node_Card_View,
|
|
link: `/stake/${contract.address}`,
|
|
}
|
|
: undefined
|
|
}
|
|
warnings={
|
|
<>
|
|
{showAlreadyRunningWarning ? (
|
|
<AlertTooltip
|
|
iconClassName="h-4 w-4 md:h-10 md:w-10"
|
|
tooltipContent={dictionary('errorStakedButAlreadyRunning')}
|
|
/>
|
|
) : null}
|
|
{state === STAKE_CONTRACT_STATE.AWAITING_OPERATOR_CONTRIBUTION ? (
|
|
<AlertTooltip
|
|
iconClassName="h-10 w-10"
|
|
tooltipContent={dictionary('youOperatorNotStaked')}
|
|
/>
|
|
) : connectedWalletNonContributedReservedStakeAmount ? (
|
|
<Tooltip
|
|
tooltipContent={dictionary('youReservedNotContributed', {
|
|
tokenAmount: formatSENTBigInt(connectedWalletNonContributedReservedStakeAmount),
|
|
})}
|
|
>
|
|
<ContactIcon className="h-10 w-10 stroke-session-green" />
|
|
</Tooltip>
|
|
) : null}
|
|
</>
|
|
}
|
|
{...props}
|
|
>
|
|
{isOperator ? (
|
|
<NodeItem className="-ms-0.5 mb-0.5 flex flex-row items-center gap-1.5 align-middle">
|
|
<NodeOperatorIndicator isConnectedWallet />
|
|
</NodeItem>
|
|
) : null}
|
|
{contributor ? (
|
|
<NodeItem className="-ms-0.5 mb-0.5 flex flex-row items-center align-middle">
|
|
<StakedToIndicator className="me-1.5" hideTextOnMobile tokenAmount={contributor.amount} />
|
|
<NodeItemValue>{formatSENTBigInt(contributor.amount, 1)}</NodeItemValue>
|
|
</NodeItem>
|
|
) : null}
|
|
{!contributor ? (
|
|
<NodeItem className="sm:flex-nowrap sm:text-nowrap">
|
|
<NodeItemLabel className="inline-flex sm:flex-nowrap sm:text-nowrap lg:hidden">
|
|
{titleFormat('format', { title: dictionary('min') })}
|
|
</NodeItemLabel>
|
|
<NodeItemLabel className="hidden sm:flex-nowrap sm:text-nowrap lg:inline-flex">
|
|
{titleFormat('format', { title: dictionary('minContribution') })}
|
|
</NodeItemLabel>
|
|
<NodeItemValue>
|
|
{formatSENTBigInt(
|
|
reservedContributor?.reserved ? reservedContributor.reserved : minStake,
|
|
2
|
|
)}
|
|
</NodeItemValue>
|
|
</NodeItem>
|
|
) : null}
|
|
<NodeItemSeparator className="hidden md:block" />
|
|
{contributor ? (
|
|
<NodeItem className="hidden md:block">
|
|
<NodeItemLabel>
|
|
{titleFormat('format', { title: dictionary('remainingStake') })}
|
|
</NodeItemLabel>
|
|
<NodeItemValue>
|
|
{formatSENTBigInt(SESSION_NODE_FULL_STAKE_AMOUNT - totalStaked, 2)}
|
|
</NodeItemValue>
|
|
</NodeItem>
|
|
) : (
|
|
<NodeItem className="hidden md:block">
|
|
<NodeItemLabel>{titleFormat('format', { title: dictionary('max') })}</NodeItemLabel>
|
|
<NodeItemValue>{formatSENTBigInt(maxStake, 2)}</NodeItemValue>
|
|
</NodeItem>
|
|
)}
|
|
<NodeItemSeparator />
|
|
<NodeItem>
|
|
<NodeItemLabel>
|
|
{titleFormat('format', { title: generalNodeDictionary('operatorFeeShort') })}
|
|
</NodeItemLabel>
|
|
<NodeItemValue>
|
|
{contract.fee !== null ? formatPercentage(contract.fee / 10000) : dictGeneral('notFound')}
|
|
</NodeItemValue>
|
|
</NodeItem>
|
|
</InfoNodeCard>
|
|
);
|
|
});
|
|
OpenNodeCard.displayName = 'OpenNodeCard';
|
|
|
|
export { OpenNodeCard };
|