Skip to content

Commit

Permalink
Merge pull request #49 from oxen-io/new_smart_contract_status_progress
Browse files Browse the repository at this point in the history
New smart contract status progress UI
  • Loading branch information
Aerilym authored Sep 17, 2024
2 parents 7b2225e + 0972b36 commit b7ba65d
Show file tree
Hide file tree
Showing 38 changed files with 1,513 additions and 1,265 deletions.
23 changes: 10 additions & 13 deletions apps/staking/app/mystakes/modules/BalanceModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ import {
getVariableFontSizeForLargeModule,
ModuleDynamicQueryText,
} from '@/components/ModuleDynamic';
import { getTotalStakedAmountForAddress } from '@/components/NodeCard';
import { getTotalStakedAmountForAddressBigInt } from '@/components/NodeCard';
import type { ServiceNode } from '@session/sent-staking-js/client';
import { Module, ModuleTitle } from '@session/ui/components/Module';
import { useWallet } from '@session/wallet/hooks/wallet-hooks';
import { useTranslations } from 'next-intl';
import { useMemo } from 'react';
import { Address } from 'viem';
import type { Address } from 'viem';
import { useStakingBackendQueryWithParams } from '@/lib/sent-staking-backend-client';
import { getStakedNodes } from '@/lib/queries/getStakedNodes';
import { generateMockNodeData } from '@session/sent-staking-js/test';
import type { QUERY_STATUS } from '@/lib/query';
import { formatSENTNumber } from '@session/contracts/hooks/SENT';
import { DYNAMIC_MODULE } from '@/lib/constants';
import { formatSENTBigInt } from '@session/contracts/hooks/SENT';
import { FEATURE_FLAG } from '@/lib/feature-flags';
import { useFeatureFlag } from '@/lib/feature-flags-client';

Expand All @@ -27,9 +26,11 @@ const getTotalStakedAmount = ({
nodes: Array<ServiceNode>;
address: Address;
}) => {
return nodes.reduce(
(acc, node) => acc + getTotalStakedAmountForAddress(node.contributors, address),
0
return formatSENTBigInt(
nodes.reduce(
(acc, node) => acc + getTotalStakedAmountForAddressBigInt(node.contributors, address),
BigInt(0)
)
);
};

Expand Down Expand Up @@ -75,10 +76,6 @@ export default function BalanceModule() {
const titleFormat = useTranslations('modules.title');
const title = dictionary('title');

const formattedTotalStakedAmount = useMemo(() => {
return `${formatSENTNumber(totalStakedAmount ?? 0, DYNAMIC_MODULE.SENT_ROUNDED_DECIMALS)}`;
}, [totalStakedAmount]);

return (
<Module size="lg" variant="hero">
<ModuleTitle>{titleFormat('format', { title })}</ModuleTitle>
Expand All @@ -94,10 +91,10 @@ export default function BalanceModule() {
refetch,
}}
style={{
fontSize: getVariableFontSizeForLargeModule(formattedTotalStakedAmount.length),
fontSize: getVariableFontSizeForLargeModule(totalStakedAmount?.length ?? 6),
}}
>
{formattedTotalStakedAmount}
{totalStakedAmount}
</ModuleDynamicQueryText>
</Module>
);
Expand Down
202 changes: 52 additions & 150 deletions apps/staking/app/mystakes/modules/ClaimTokensModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,20 @@ import { formatBigIntTokenValue } from '@session/util/maths';
import { ETH_DECIMALS } from '@session/wallet/lib/eth';
import { LoadingText } from '@session/ui/components/loading-text';
import { QUERY, TICKER, URL } from '@/lib/constants';
import useClaimRewards, { CLAIM_REWARDS_STATE } from '@/hooks/useClaimRewards';
import { type ReactNode, useEffect, useMemo } from 'react';
import useClaimRewards from '@/hooks/useClaimRewards';
import { useEffect, useMemo } from 'react';
import { useWallet } from '@session/wallet/hooks/wallet-hooks';
import { externalLink } from '@/lib/locale-defaults';
import { TriangleAlertIcon } from '@session/ui/icons/TriangleAlertIcon';
import { Tooltip } from '@session/ui/ui/tooltip';
import { AlertTooltip } from '@session/ui/ui/tooltip';
import { useStakingBackendQueryWithParams } from '@/lib/sent-staking-backend-client';
import { getRewardsClaimSignature } from '@/lib/queries/getRewardsClaimSignature';
import type { WriteContractStatus } from '@session/contracts/hooks/useContractWriteQuery';
import type { VariantProps } from 'class-variance-authority';
import { StatusIndicator, statusVariants } from '@session/ui/components/StatusIndicator';
import type { Address } from 'viem';
import { Loading } from '@session/ui/components/loading';
import { useRemoteFeatureFlagQuery } from '@/lib/feature-flags-client';
import { REMOTE_FEATURE_FLAG } from '@/lib/feature-flags';
import { toast } from '@session/ui/lib/toast';
import { ClaimRewardsDisabledInfo } from '@/components/ClaimRewardsDisabledInfo';
import { Progress, PROGRESS_STATUS } from '@session/ui/components/motion/progress';

export default function ClaimTokensModule() {
const { address } = useWallet();
Expand Down Expand Up @@ -87,10 +84,10 @@ export default function ClaimTokensModule() {
disabled={isDisabled}
onClick={handleClick}
>
<ModuleContent className="flex h-full select-none flex-row items-center gap-2 p-0 align-middle font-bold">
<ModuleContent className="flex h-full select-none flex-row items-center gap-2 p-0 py-3 align-middle font-bold">
<PresentIcon
className={cn(
'mb-2 h-8 w-8 transition-all duration-300',
'mb-1 h-8 w-8 transition-all duration-300',
isDisabled
? 'fill-session-text opacity-50'
: 'fill-session-green group-hover:fill-session-black'
Expand Down Expand Up @@ -124,138 +121,6 @@ export default function ClaimTokensModule() {
);
}

function getStatusFromSubStage(
subStage: WriteContractStatus
): VariantProps<typeof statusVariants>['status'] {
switch (subStage) {
case 'error':
return 'red';
case 'success':
return 'green';
case 'pending':
return 'pending';
default:
case 'idle':
return 'grey';
}
}

const dictionaryKey: Record<CLAIM_REWARDS_STATE, string> = {
[CLAIM_REWARDS_STATE.SIMULATE_UPDATE_BALANCE]: 'updateBalance.simulate',
[CLAIM_REWARDS_STATE.WRITE_UPDATE_BALANCE]: 'updateBalance.write',
[CLAIM_REWARDS_STATE.TRANSACTION_UPDATE_BALANCE]: 'updateBalance.transaction',
[CLAIM_REWARDS_STATE.SIMULATE_CLAIM]: 'claimRewards.simulate',
[CLAIM_REWARDS_STATE.WRITE_CLAIM]: 'claimRewards.write',
[CLAIM_REWARDS_STATE.TRANSACTION_CLAIM]: 'claimRewards.transaction',
} as const;

function getDictionaryKeyFromStageAndSubStage<
Stage extends CLAIM_REWARDS_STATE,
SubStage extends WriteContractStatus,
>({
currentStage,
stage,
subStage,
}: {
currentStage: CLAIM_REWARDS_STATE;
stage: Stage;
subStage: SubStage;
}) {
return `${dictionaryKey[stage]}.${stage > currentStage || subStage === 'idle' ? 'pending' : subStage}`;
}

function StageRow({
currentStage,
stage,
subStage,
children,
}: {
currentStage: CLAIM_REWARDS_STATE;
stage: CLAIM_REWARDS_STATE;
subStage: WriteContractStatus;
children?: ReactNode;
}) {
const dictionary = useTranslations('modules.claim.stage');
return (
<span className="inline-flex items-center gap-4 align-middle">
<StatusIndicator
className="h-4 w-4"
status={
stage === currentStage
? getStatusFromSubStage(subStage)
: stage > currentStage
? 'grey'
: stage < currentStage
? 'green'
: undefined
}
/>
<span className="mt-0.5">
{dictionary(
/** @ts-expect-error - TODO: Properly type this dictionary key construction function */
children ?? getDictionaryKeyFromStageAndSubStage({ currentStage, stage, subStage })
)}
</span>
</span>
);
}

function QueryStatusInformation({
stage,
subStage,
}: {
stage: CLAIM_REWARDS_STATE;
subStage: WriteContractStatus;
}) {
return (
<div className="flex w-full flex-col gap-8">
<StageRow
stage={CLAIM_REWARDS_STATE.SIMULATE_UPDATE_BALANCE}
currentStage={stage}
subStage={subStage}
/>
<StageRow
stage={CLAIM_REWARDS_STATE.WRITE_UPDATE_BALANCE}
currentStage={stage}
subStage={subStage}
/>
<StageRow
stage={CLAIM_REWARDS_STATE.TRANSACTION_UPDATE_BALANCE}
currentStage={stage}
subStage={subStage}
/>
<StageRow
stage={CLAIM_REWARDS_STATE.SIMULATE_CLAIM}
currentStage={stage}
subStage={subStage}
/>
<StageRow stage={CLAIM_REWARDS_STATE.WRITE_CLAIM} currentStage={stage} subStage={subStage} />
<StageRow
stage={CLAIM_REWARDS_STATE.TRANSACTION_CLAIM}
currentStage={stage}
subStage={subStage}
/>
<StageRow
stage={CLAIM_REWARDS_STATE.TRANSACTION_CLAIM}
currentStage={stage}
subStage={subStage}
>
{stage === CLAIM_REWARDS_STATE.TRANSACTION_CLAIM && subStage === 'success'
? 'done.success'
: 'done.pending'}
</StageRow>
</div>
);
}

const AlertTooltip = ({ tooltipContent }: { tooltipContent: ReactNode }) => {
return (
<Tooltip tooltipContent={tooltipContent}>
<TriangleAlertIcon className="stroke-warning mb-0.5 h-4 w-4" />
</Tooltip>
);
};

function ClaimTokensDialog({
formattedUnclaimedRewardsAmount,
address,
Expand All @@ -270,6 +135,7 @@ function ClaimTokensDialog({
excludedSigners: Array<bigint>;
}) {
const dictionary = useTranslations('modules.claim.dialog');
const dictionaryStage = useTranslations('modules.claim.stage');

const claimRewardsArgs = useMemo(
() => ({
Expand All @@ -286,10 +152,12 @@ function ClaimTokensDialog({
claimFee,
updateBalanceFee,
estimateFee,
stage,
subStage,
updateRewardsBalanceStatus,
claimRewardsStatus,
enabled,
skipUpdateBalance,
updateRewardsBalanceErrorMessage,
claimRewardsErrorMessage,
} = useClaimRewards(claimRewardsArgs);

const feeEstimate = useMemo(
Expand All @@ -312,17 +180,22 @@ function ClaimTokensDialog({

const isButtonDisabled =
isDisabled ||
(!skipUpdateBalance &&
stage !== CLAIM_REWARDS_STATE.SIMULATE_UPDATE_BALANCE &&
subStage !== 'idle') ||
(skipUpdateBalance && stage !== CLAIM_REWARDS_STATE.SIMULATE_CLAIM && subStage !== 'idle');
(skipUpdateBalance
? claimRewardsStatus !== PROGRESS_STATUS.IDLE
: updateRewardsBalanceStatus !== PROGRESS_STATUS.IDLE);

useEffect(() => {
if (!isDisabled) {
estimateFee();
}
}, [address, rewards, blsSignature]);

useEffect(() => {
if (claimRewardsStatus === PROGRESS_STATUS.SUCCESS) {
toast.success(dictionary('successToast', { tokenAmount: formattedUnclaimedRewardsAmount }));
}
}, [claimRewardsStatus]);

return (
<>
<div className="flex flex-col gap-4">
Expand Down Expand Up @@ -353,7 +226,7 @@ function ClaimTokensDialog({
{formattedUnclaimedRewardsAmount}
</ActionModuleRow>
</div>
<AlertDialogFooter className="mt-4 flex flex-col gap-8 sm:flex-col">
<AlertDialogFooter className="mt-4 flex flex-col gap-6 sm:flex-col">
<Button
variant="outline"
rounded="md"
Expand All @@ -367,9 +240,38 @@ function ClaimTokensDialog({
disabled={isButtonDisabled}
onClick={handleClick}
>
{dictionary('buttons.submit')}
{dictionary('buttons.submit', { tokenAmount: formattedUnclaimedRewardsAmount })}
</Button>
{enabled ? <QueryStatusInformation stage={stage} subStage={subStage} /> : null}
{enabled ? (
<Progress
steps={[
{
text: {
[PROGRESS_STATUS.IDLE]: dictionaryStage('balance.idle'),
[PROGRESS_STATUS.PENDING]: dictionaryStage('balance.pending'),
[PROGRESS_STATUS.SUCCESS]: dictionaryStage('balance.success'),
[PROGRESS_STATUS.ERROR]: updateRewardsBalanceErrorMessage,
},
status: updateRewardsBalanceStatus,
},
{
text: {
[PROGRESS_STATUS.IDLE]: dictionaryStage('claim.idle', {
tokenAmount: formattedUnclaimedRewardsAmount,
}),
[PROGRESS_STATUS.PENDING]: dictionaryStage('claim.pending', {
tokenAmount: formattedUnclaimedRewardsAmount,
}),
[PROGRESS_STATUS.SUCCESS]: dictionaryStage('claim.success', {
tokenAmount: formattedUnclaimedRewardsAmount,
}),
[PROGRESS_STATUS.ERROR]: claimRewardsErrorMessage,
},
status: claimRewardsStatus,
},
]}
/>
) : null}
</AlertDialogFooter>
</>
);
Expand Down
Loading

0 comments on commit b7ba65d

Please sign in to comment.