mirror of
https://github.com/lux-tel/websites.git
synced 2026-10-30 20:31:36 +00:00
265 lines
8.7 KiB
TypeScript
265 lines
8.7 KiB
TypeScript
import { ButtonDataTestId } from '@/testing/data-test-ids';
|
|
import { useTranslations } from 'next-intl';
|
|
import { CollapsableButton } from '@/components/StakedNodeCard';
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogFooter,
|
|
AlertDialogTrigger,
|
|
} from '@session/ui/ui/alert-dialog';
|
|
import { Button } from '@session/ui/ui/button';
|
|
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
|
import { formatLocalizedTimeFromSeconds } from '@/lib/locale-client';
|
|
import { SESSION_NODE_TIME, SOCIALS, URL } from '@/lib/constants';
|
|
import { externalLink } from '@/lib/locale-defaults';
|
|
import { useChain } from '@session/contracts/hooks/useChain';
|
|
import { formatBigIntTokenValue } from '@session/util-crypto/maths';
|
|
import { ETH_DECIMALS } from '@session/wallet/lib/eth';
|
|
import { useRemoteFeatureFlagQuery } from '@/lib/feature-flags-client';
|
|
import { REMOTE_FEATURE_FLAG } from '@/lib/feature-flags';
|
|
import { Loading } from '@session/ui/components/loading';
|
|
import Link from 'next/link';
|
|
import { Social } from '@session/ui/components/SocialLinkList';
|
|
import { ChevronsDownIcon } from '@session/ui/icons/ChevronsDownIcon';
|
|
import { Progress, PROGRESS_STATUS } from '@session/ui/motion/progress';
|
|
import useRequestNodeExit from '@/hooks/useRequestNodeExit';
|
|
import NodeActionModuleInfo from '@/components/StakedNode/NodeActionModuleInfo';
|
|
import { SENT_SYMBOL } from '@session/contracts';
|
|
import { Stake } from '@session/sent-staking-js/client';
|
|
import { formatSENTNumber } from '@session/contracts/hooks/SENT';
|
|
|
|
enum EXIT_REQUEST_STATE {
|
|
ALERT,
|
|
PENDING,
|
|
}
|
|
|
|
export function NodeRequestExitButton({ node }: { node: Stake }) {
|
|
const [exitRequestState, setExitRequestState] = useState<EXIT_REQUEST_STATE>(
|
|
EXIT_REQUEST_STATE.ALERT
|
|
);
|
|
const dictionary = useTranslations('nodeCard.staked.requestExit');
|
|
const { enabled: isNodeExitRequestDisabled, isLoading: isRemoteFlagLoading } =
|
|
useRemoteFeatureFlagQuery(REMOTE_FEATURE_FLAG.DISABLE_REQUEST_NODE_EXIT);
|
|
|
|
return (
|
|
<AlertDialog>
|
|
<AlertDialogTrigger asChild>
|
|
<CollapsableButton
|
|
ariaLabel={dictionary('buttonAria')}
|
|
dataTestId={ButtonDataTestId.Staked_Node_Request_Exit}
|
|
>
|
|
{dictionary('buttonText')}
|
|
</CollapsableButton>
|
|
</AlertDialogTrigger>
|
|
<AlertDialogContent
|
|
dialogTitle={
|
|
<>
|
|
{exitRequestState !== EXIT_REQUEST_STATE.ALERT ? (
|
|
<ChevronsDownIcon
|
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute left-8 mt-1.5 rotate-90 cursor-pointer rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"
|
|
onClick={() => setExitRequestState(EXIT_REQUEST_STATE.ALERT)}
|
|
/>
|
|
) : null}
|
|
{dictionary('dialog.title')}
|
|
</>
|
|
}
|
|
className="text-center"
|
|
>
|
|
{isRemoteFlagLoading ? (
|
|
<Loading />
|
|
) : isNodeExitRequestDisabled ? (
|
|
<RequestNodeExitDisabled />
|
|
) : exitRequestState === EXIT_REQUEST_STATE.PENDING ? (
|
|
<RequestNodeExitContractWriteDialog node={node} />
|
|
) : (
|
|
<RequestNodeExitDialog
|
|
node={node}
|
|
onSubmit={() => setExitRequestState(EXIT_REQUEST_STATE.PENDING)}
|
|
/>
|
|
)}
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
);
|
|
}
|
|
|
|
function RequestNodeExitDisabled() {
|
|
const dictionary = useTranslations('nodeCard.staked.requestExit');
|
|
return (
|
|
<p>
|
|
{dictionary.rich('disabledInfo', {
|
|
link: (children: ReactNode) => (
|
|
<Link
|
|
className="text-session-green font-medium underline"
|
|
href={SOCIALS[Social.Discord].link}
|
|
referrerPolicy="no-referrer"
|
|
target="_blank"
|
|
>
|
|
{children}
|
|
</Link>
|
|
),
|
|
})}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
function RequestNodeExitDialog({ node, onSubmit }: { node: Stake; onSubmit: () => void }) {
|
|
const chain = useChain();
|
|
const dictionary = useTranslations('nodeCard.staked.requestExit.dialog');
|
|
|
|
return (
|
|
<>
|
|
<div className="text-lg font-medium">{dictionary('description1')}</div>
|
|
<p>
|
|
{dictionary('description2', {
|
|
request_time: formatLocalizedTimeFromSeconds(
|
|
SESSION_NODE_TIME(chain).EXIT_REQUEST_TIME_SECONDS,
|
|
{
|
|
addSuffix: true,
|
|
}
|
|
),
|
|
})}
|
|
<br />
|
|
<br />
|
|
{dictionary.rich('description3', {
|
|
request_time: formatLocalizedTimeFromSeconds(
|
|
SESSION_NODE_TIME(chain).EXIT_REQUEST_TIME_SECONDS
|
|
),
|
|
exit_time: formatLocalizedTimeFromSeconds(
|
|
SESSION_NODE_TIME(chain).EXIT_GRACE_TIME_SECONDS
|
|
),
|
|
link: externalLink(URL.NODE_LIQUIDATION_LEARN_MORE),
|
|
})}
|
|
<br />
|
|
<br />
|
|
{dictionary('description4')}
|
|
</p>
|
|
<AlertDialogFooter className="mt-4 flex w-full flex-col font-medium sm:flex-row">
|
|
<Button
|
|
variant="destructive-ghost"
|
|
rounded="md"
|
|
size="lg"
|
|
aria-label={dictionary('buttons.submitAria', {
|
|
pubKey: node.service_node_pubkey,
|
|
})}
|
|
className="w-full"
|
|
data-testid={ButtonDataTestId.Staked_Node_Request_Exit_Dialog_Submit}
|
|
onClick={onSubmit}
|
|
type="submit"
|
|
>
|
|
{dictionary('buttons.submit')}
|
|
</Button>
|
|
<AlertDialogCancel asChild>
|
|
<Button
|
|
variant="ghost"
|
|
rounded="md"
|
|
size="lg"
|
|
className="w-full"
|
|
aria-label={dictionary('buttons.cancelAria')}
|
|
data-testid={ButtonDataTestId.Staked_Node_Request_Exit_Dialog_Cancel}
|
|
>
|
|
{dictionary('buttons.cancel')}
|
|
</Button>
|
|
</AlertDialogCancel>
|
|
</AlertDialogFooter>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function RequestNodeExitContractWriteDialog({ node }: { node: Stake }) {
|
|
const stageDictKey = 'nodeCard.staked.requestExit.dialog.stage' as const;
|
|
const dictionary = useTranslations('nodeCard.staked.requestExit.dialog.write');
|
|
const dictionaryStage = useTranslations(stageDictKey);
|
|
|
|
const {
|
|
initiateRemoveBLSPublicKey,
|
|
fee,
|
|
estimateContractWriteFee,
|
|
simulateEnabled,
|
|
resetContract,
|
|
status,
|
|
errorMessage,
|
|
} = useRequestNodeExit({
|
|
contractId: node.contract_id,
|
|
});
|
|
|
|
const feeEstimate = useMemo(
|
|
() => (fee !== null ? formatBigIntTokenValue(fee ?? BigInt(0), ETH_DECIMALS, 18) : null),
|
|
[fee]
|
|
);
|
|
|
|
const stakedAmount = useMemo(
|
|
() => (node.staked_balance ? formatSENTNumber(node.staked_balance) : `0 ${SENT_SYMBOL}`),
|
|
[node.staked_balance]
|
|
);
|
|
|
|
const handleClick = () => {
|
|
if (simulateEnabled) {
|
|
resetContract();
|
|
}
|
|
initiateRemoveBLSPublicKey();
|
|
};
|
|
|
|
const isDisabled = !node.contract_id;
|
|
|
|
useEffect(() => {
|
|
if (!isDisabled) {
|
|
estimateContractWriteFee();
|
|
}
|
|
}, [node.contract_id]);
|
|
|
|
return (
|
|
<>
|
|
<NodeActionModuleInfo
|
|
node={node}
|
|
feeEstimate={feeEstimate}
|
|
feeEstimateText={dictionary('requestFee')}
|
|
/>
|
|
<AlertDialogFooter className="mt-4 flex flex-col gap-8 sm:flex-col">
|
|
<Button
|
|
variant="destructive"
|
|
rounded="md"
|
|
size="lg"
|
|
aria-label={dictionary('buttons.submitAria', {
|
|
tokenAmount: stakedAmount,
|
|
gasAmount: feeEstimate ?? 0,
|
|
})}
|
|
className="w-full"
|
|
data-testid={ButtonDataTestId.Staked_Node_Request_Exit_Write_Dialog_Submit}
|
|
disabled={isDisabled || (simulateEnabled && status !== PROGRESS_STATUS.ERROR)}
|
|
onClick={handleClick}
|
|
>
|
|
{dictionary('buttons.submit')}
|
|
</Button>
|
|
{simulateEnabled ? (
|
|
<Progress
|
|
steps={[
|
|
{
|
|
text: {
|
|
[PROGRESS_STATUS.IDLE]: dictionaryStage('arbitrum.idle'),
|
|
[PROGRESS_STATUS.PENDING]: dictionaryStage('arbitrum.pending'),
|
|
[PROGRESS_STATUS.SUCCESS]: dictionaryStage('arbitrum.success'),
|
|
[PROGRESS_STATUS.ERROR]: errorMessage,
|
|
},
|
|
status,
|
|
},
|
|
{
|
|
text: {
|
|
[PROGRESS_STATUS.IDLE]: dictionaryStage('network.idle'),
|
|
[PROGRESS_STATUS.PENDING]: dictionaryStage('network.pending'),
|
|
[PROGRESS_STATUS.SUCCESS]: dictionaryStage('network.success'),
|
|
[PROGRESS_STATUS.ERROR]: errorMessage,
|
|
},
|
|
status:
|
|
status === PROGRESS_STATUS.SUCCESS
|
|
? PROGRESS_STATUS.SUCCESS
|
|
: PROGRESS_STATUS.IDLE,
|
|
},
|
|
]}
|
|
/>
|
|
) : null}
|
|
</AlertDialogFooter>
|
|
</>
|
|
);
|
|
}
|