Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(wallet-dashboard): improve staking dialog #4476

Merged
merged 44 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
fc05cae
feat(dashboard): add unstake timelocked objects view
VmMad Nov 28, 2024
3ed6d5e
refactor: improve function
VmMad Nov 28, 2024
0fc7a62
feat: add more unstaking information and rename constant
VmMad Dec 4, 2024
93ade20
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
VmMad Dec 4, 2024
068a798
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
VmMad Dec 4, 2024
c70f4f1
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
VmMad Dec 5, 2024
d0234e7
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
VmMad Dec 5, 2024
da6de48
refactor: imports
VmMad Dec 5, 2024
d4ff8b4
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
VmMad Dec 5, 2024
ec530eb
fix: imports
VmMad Dec 5, 2024
7a31083
fix: remove duplication collapsible
VmMad Dec 9, 2024
3e3f85a
refactor: include both single stake and grouped in a unstake hook
VmMad Dec 9, 2024
f4ce8e4
refactor: unify usntake panel
VmMad Dec 10, 2024
c3392cc
fix: go back correctly to previous screen in staking
VmMad Dec 10, 2024
377e1cc
refactor: cleanup
VmMad Dec 10, 2024
0de0218
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
VmMad Dec 10, 2024
597a179
refactor: remove popups
VmMad Dec 10, 2024
34b042c
refactor: remove popups
VmMad Dec 10, 2024
0229429
revert "refactor: remove popups"
VmMad Dec 10, 2024
b68af14
refactor: remove only unnecessary popups
VmMad Dec 10, 2024
0a12460
refactor: divide hooks and move deeper inside components
VmMad Dec 10, 2024
0cf1080
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
VmMad Dec 12, 2024
3a7586e
fix: resolve re rendering issue
VmMad Dec 12, 2024
eb8cd1e
feat: remove redundant code
VmMad Dec 12, 2024
8e567c8
fix: add enter amount dialog component
brancoder Dec 12, 2024
5b8382b
fix: resolve conflicts
brancoder Dec 12, 2024
798a8ff
fix: update conditionally showing the stake dialog
VmMad Dec 12, 2024
21104d3
fix(tooling-dashboard): improve stake wizard (#4454)
brancoder Dec 13, 2024
8e3a4a7
Merge branch 'tooling-dashboard/add-timelocked-unstake-flow' into too…
brancoder Dec 13, 2024
c8c2047
refactor: imports / exports
VmMad Dec 13, 2024
338400a
feat: add wait for transaction and refresh
VmMad Dec 13, 2024
c19da61
feat: improve dialogs
VmMad Dec 16, 2024
db718f5
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
cpl121 Dec 16, 2024
8dba2e0
Merge branch 'develop' into tooling-dashboard/add-timelocked-unstake-…
cpl121 Dec 17, 2024
c9f4bb4
Merge branch 'tooling-dashboard/add-timelocked-unstake-flow' into too…
brancoder Dec 17, 2024
c230547
feat(dashboard): minor fixes
cpl121 Dec 17, 2024
2f1d5de
fix: query key for timelocked staking
brancoder Dec 17, 2024
cffe6e5
fix: revert constants changes
brancoder Dec 17, 2024
d2cc8d1
fix: resolve conflicts
brancoder Dec 17, 2024
411d0c9
fix: disable stake button if no available amount for staking
brancoder Dec 17, 2024
f9d9590
Merge branch 'tooling-dashboard/add-timelocked-unstake-flow' into too…
brancoder Dec 17, 2024
a54fefb
fix: resolve conflicts
brancoder Dec 18, 2024
4cf33b6
fix: add infoMessage var
brancoder Dec 18, 2024
ce76f23
fix: revert pnpm lock changes
brancoder Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { useFormatCoin, useStakeTxnInfo } from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import {
Button,
ButtonType,
KeyValueInfo,
Panel,
Divider,
Input,
InputType,
Header,
InfoBoxType,
InfoBoxStyle,
InfoBox,
} from '@iota/apps-ui-kit';
import { Field, type FieldProps, useFormikContext } from 'formik';
import { Exclamation, Loader } from '@iota/ui-icons';
import { useIotaClientQuery } from '@iota/dapp-kit';

import { Validator } from './Validator';
import { StakedInfo } from './StakedInfo';
import { DialogLayout, DialogLayoutBody, DialogLayoutFooter } from '../../layout';

export interface FormValues {
amount: string;
}

interface EnterAmountDialogLayoutProps {
selectedValidator: string;
senderAddress: string;
caption: string;
showInfo: boolean;
infoMessage: string;
isLoading: boolean;
onBack: () => void;
handleClose: () => void;
handleStake: () => void;
isStakeDisabled?: boolean;
gasBudget?: string | number | null;
}

function EnterAmountDialogLayout({
selectedValidator,
gasBudget,
senderAddress,
caption,
showInfo,
infoMessage,
isLoading,
isStakeDisabled,
onBack,
handleClose,
handleStake,
}: EnterAmountDialogLayoutProps): JSX.Element {
const { data: system } = useIotaClientQuery('getLatestIotaSystemState');
const { values, errors } = useFormikContext<FormValues>();
const amount = values.amount;

const [gas, symbol] = useFormatCoin(gasBudget ?? 0, IOTA_TYPE_ARG);

const { stakedRewardsStartEpoch, timeBeforeStakeRewardsRedeemableAgoDisplay } = useStakeTxnInfo(
system?.epoch,
);

return (
<DialogLayout>
<Header title="Enter amount" onClose={handleClose} onBack={onBack} titleCentered />
<DialogLayoutBody>
<div className="flex w-full flex-col justify-between">
<div>
<div className="mb-md">
<Validator address={selectedValidator} isSelected showAction={false} />
</div>
<StakedInfo
validatorAddress={selectedValidator}
accountAddress={senderAddress}
/>
<div className="my-md w-full">
<Field name="amount">
{({
field: { onChange, ...field },
form: { setFieldValue },
meta,
}: FieldProps<FormValues>) => {
return (
<Input
{...field}
onValueChange={({ value }) => {
setFieldValue('amount', value, true);
}}
type={InputType.NumericFormat}
label="Amount"
value={amount}
suffix={` ${symbol}`}
placeholder="Enter amount to stake"
errorMessage={
values.amount && meta.error ? meta.error : undefined
}
caption={caption}
/>
);
}}
</Field>
{showInfo ? (
<div className="mt-md">
<InfoBox
type={InfoBoxType.Error}
supportingText={infoMessage}
style={InfoBoxStyle.Elevated}
icon={<Exclamation />}
/>
</div>
) : null}
</div>

<Panel hasBorder>
<div className="flex flex-col gap-y-sm p-md">
<KeyValueInfo
keyText="Staking Rewards Start"
value={stakedRewardsStartEpoch}
fullwidth
/>
<KeyValueInfo
keyText="Redeem Rewards"
value={timeBeforeStakeRewardsRedeemableAgoDisplay}
fullwidth
/>
<Divider />
<KeyValueInfo
keyText="Gas fee"
value={gas || '--'}
supportingLabel={symbol}
fullwidth
/>
</div>
</Panel>
</div>
</div>
</DialogLayoutBody>
<DialogLayoutFooter>
<div className="flex w-full justify-between gap-sm">
<Button fullWidth type={ButtonType.Secondary} onClick={onBack} text="Back" />
<Button
fullWidth
type={ButtonType.Primary}
disabled={!amount || !!errors?.amount || isLoading || isStakeDisabled}
onClick={handleStake}
text="Stake"
icon={
isLoading ? (
<Loader className="animate-spin" data-testid="loading-indicator" />
) : null
}
iconAfterText
/>
</div>
</DialogLayoutFooter>
</DialogLayout>
);
}

export default EnterAmountDialogLayout;
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import {
useFormatCoin,
useBalance,
CoinFormat,
parseAmount,
useCoinMetadata,
useStakeTxnInfo,
} from '@iota/core';
import { useFormatCoin, useBalance, CoinFormat, parseAmount, useCoinMetadata } from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import {
Button,
ButtonType,
KeyValueInfo,
Panel,
Divider,
Input,
InputType,
Header,
InfoBoxType,
InfoBoxStyle,
InfoBox,
} from '@iota/apps-ui-kit';
import { Field, type FieldProps, useFormikContext } from 'formik';
import { Exclamation } from '@iota/ui-icons';
import { useIotaClientQuery, useSignAndExecuteTransaction } from '@iota/dapp-kit';

import { Validator } from './Validator';
import { StakedInfo } from './StakedInfo';
import { DialogLayout, DialogLayoutBody, DialogLayoutFooter } from '../../layout';
import { useFormikContext } from 'formik';
import { useSignAndExecuteTransaction } from '@iota/dapp-kit';
import { useNewStakeTransaction, useNotifications } from '@/hooks';
import { NotificationType } from '@/stores/notificationStore';
import EnterAmountDialogLayout from './EnterAmountDialogLayout';

export interface FormValues {
amount: string;
Expand All @@ -49,51 +25,41 @@ interface EnterAmountViewProps {
}

function EnterAmountView({
selectedValidator: selectedValidatorAddress,
selectedValidator,
onBack,
handleClose,
amountWithoutDecimals,
senderAddress,
onSuccess,
}: EnterAmountViewProps): JSX.Element {
const { addNotification } = useNotifications();
const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction();
const { values, resetForm } = useFormikContext<FormValues>();

const coinType = IOTA_TYPE_ARG;
const { data: metadata } = useCoinMetadata(coinType);
const decimals = metadata?.decimals ?? 0;

const { addNotification } = useNotifications();

const { values, errors, resetForm } = useFormikContext<FormValues>();
const amount = values.amount;
const { data: iotaBalance } = useBalance(senderAddress);
const coinBalance = BigInt(iotaBalance?.totalBalance || 0);

const { data: newStakeData, isLoading: isTransactionLoading } = useNewStakeTransaction(
selectedValidatorAddress,
selectedValidator,
amountWithoutDecimals,
senderAddress,
);

const { data: system } = useIotaClientQuery('getLatestIotaSystemState');
const { data: iotaBalance } = useBalance(senderAddress!);
const coinBalance = BigInt(iotaBalance?.totalBalance || 0);
const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction();

const gasBudgetBigInt = BigInt(newStakeData?.gasBudget ?? 0);
const [gas, symbol] = useFormatCoin(newStakeData?.gasBudget, IOTA_TYPE_ARG);

const maxTokenBalance = coinBalance - gasBudgetBigInt;
const [maxTokenFormatted, maxTokenFormattedSymbol] = useFormatCoin(
maxTokenBalance,
IOTA_TYPE_ARG,
CoinFormat.FULL,
);

const caption = isTransactionLoading
? '--'
: `${maxTokenFormatted} ${maxTokenFormattedSymbol} Available`;

const { stakedRewardsStartEpoch, timeBeforeStakeRewardsRedeemableAgoDisplay } = useStakeTxnInfo(
system?.epoch,
);

const caption = `${maxTokenFormatted} ${maxTokenFormattedSymbol} Available`;
const infoMessage =
'You have selected an amount that will leave you with insufficient funds to pay for gas fees for unstaking or any other transactions.';
const hasEnoughRemaingBalance =
maxTokenBalance > parseAmount(values.amount, decimals) + BigInt(2) * gasBudgetBigInt;

Expand All @@ -120,97 +86,18 @@ function EnterAmountView({
}

return (
<DialogLayout>
<Header title="Enter amount" onClose={handleClose} onBack={onBack} titleCentered />
<DialogLayoutBody>
<div className="flex w-full flex-col justify-between">
<div>
<div className="mb-md">
<Validator
address={selectedValidatorAddress}
isSelected
showAction={false}
/>
</div>
<StakedInfo
validatorAddress={selectedValidatorAddress}
accountAddress={senderAddress!}
/>
<div className="my-md w-full">
<Field name="amount">
{({
field: { onChange, ...field },
form: { setFieldValue },
meta,
}: FieldProps<FormValues>) => {
return (
<Input
{...field}
onValueChange={({ value }) => {
setFieldValue('amount', value, true);
}}
type={InputType.NumericFormat}
label="Amount"
value={amount}
suffix={` ${symbol}`}
placeholder="Enter amount to stake"
errorMessage={
values.amount && meta.error ? meta.error : undefined
}
caption={coinBalance ? caption : ''}
/>
);
}}
</Field>
{!hasEnoughRemaingBalance ? (
<div className="mt-md">
<InfoBox
type={InfoBoxType.Error}
supportingText="You have selected an amount that will leave you with insufficient funds to pay for gas fees for unstaking or any other transactions."
style={InfoBoxStyle.Elevated}
icon={<Exclamation />}
/>
</div>
) : null}
</div>

<Panel hasBorder>
<div className="flex flex-col gap-y-sm p-md">
<KeyValueInfo
keyText="Staking Rewards Start"
value={stakedRewardsStartEpoch}
fullwidth
/>
<KeyValueInfo
keyText="Redeem Rewards"
value={timeBeforeStakeRewardsRedeemableAgoDisplay}
fullwidth
/>
<Divider />
<KeyValueInfo
keyText="Gas fee"
value={gas || '--'}
supportingLabel={symbol}
fullwidth
/>
</div>
</Panel>
</div>
</div>
</DialogLayoutBody>
<DialogLayoutFooter>
<div className="flex w-full justify-between gap-sm">
<Button fullWidth type={ButtonType.Secondary} onClick={onBack} text="Back" />
<Button
fullWidth
type={ButtonType.Primary}
onClick={handleStake}
disabled={!amount || !!errors?.amount}
text="Stake"
/>
</div>
</DialogLayoutFooter>
</DialogLayout>
<EnterAmountDialogLayout
selectedValidator={selectedValidator}
gasBudget={newStakeData?.gasBudget}
senderAddress={senderAddress}
caption={caption}
showInfo={!hasEnoughRemaingBalance}
infoMessage={infoMessage}
isLoading={isTransactionLoading}
onBack={onBack}
handleClose={handleClose}
handleStake={handleStake}
/>
);
}

Expand Down
Loading
Loading