Skip to content

Commit

Permalink
feat(wallet-dashboard): integrate Formik
Browse files Browse the repository at this point in the history
panteleymonchuk committed Nov 14, 2024
1 parent 5932ed5 commit 9bdb887
Showing 4 changed files with 166 additions and 68 deletions.
114 changes: 79 additions & 35 deletions apps/wallet-dashboard/components/Dialogs/Staking/StakeDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React, { useState } from 'react';
import React, { useState, useMemo } from 'react';
import { EnterAmountView, SelectValidatorView } from './views';
import {
useNotifications,
@@ -16,21 +16,31 @@ import {
useCoinMetadata,
useGetAllOwnedObjects,
useGetValidatorsApy,
useBalance,
createValidationSchema,
} from '@iota/core';
import { Formik } from 'formik';
import type { FormikHelpers } from 'formik';
import { useCurrentAccount, useSignAndExecuteTransaction } from '@iota/dapp-kit';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { NotificationType } from '@/stores/notificationStore';
import { prepareObjectsForTimelockedStakingTransaction } from '@/lib/utils';
import { Dialog } from '@iota/apps-ui-kit';
import { DetailsView, UnstakeView } from './views';

export const MIN_NUMBER_IOTA_TO_STAKE = 1;

export enum StakeDialogView {
Details,
SelectValidator,
EnterAmount,
Unstake,
}

const INITIAL_VALUES = {
amount: '',
};

interface StakeDialogProps {
isTimelockedStaking?: boolean;
onSuccess?: (digest: string) => void;
@@ -83,9 +93,26 @@ function StakeDialog({
const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction();
const { addNotification } = useNotifications();
const { data: rollingAverageApys } = useGetValidatorsApy();
const { data: iotaBalance } = useBalance(senderAddress!);
const coinBalance = BigInt(iotaBalance?.totalBalance || 0);
const minimumStake = parseAmount(MIN_NUMBER_IOTA_TO_STAKE.toString(), coinDecimals);
const coinSymbol = metadata?.symbol ?? '';
console.log('symbol', coinSymbol);

const validators = Object.keys(rollingAverageApys ?? {}) ?? [];

const validationSchema = useMemo(
() =>
createValidationSchema(
coinBalance,
coinSymbol,
coinDecimals,
view === StakeDialogView.Unstake,
minimumStake,
),
[coinBalance, coinSymbol, coinDecimals, view, minimumStake],
);

function handleBack(): void {
setView(StakeDialogView.SelectValidator);
}
@@ -138,42 +165,59 @@ function StakeDialog({
});
}

function onSubmit(
values: Record<string, string>,
{ resetForm }: FormikHelpers<Record<string, string>>,
) {
setAmount(values.amount);
handleStake();
resetForm();
}

return (
<Dialog open={isOpen} onOpenChange={() => handleClose()}>
{view === StakeDialogView.Details && stakedDetails && (
<DetailsView
handleStake={detailsHandleStake}
handleUnstake={detailsHandleUnstake}
stakedDetails={stakedDetails}
handleClose={handleClose}
/>
)}
{view === StakeDialogView.SelectValidator && (
<SelectValidatorView
selectedValidator={selectedValidator}
handleClose={handleClose}
validators={validators}
onSelect={handleValidatorSelect}
onNext={selectValidatorHandleNext}
/>
)}
{view === StakeDialogView.EnterAmount && (
<EnterAmountView
selectedValidator={selectedValidator}
amount={amount}
handleClose={handleClose}
onChange={(e) => setAmount(e.target.value)}
onBack={handleBack}
onStake={handleStake}
/>
)}
{view === StakeDialogView.Unstake && stakedDetails && (
<UnstakeView
extendedStake={stakedDetails}
handleClose={handleClose}
showActiveStatus
/>
)}
<Formik
initialValues={INITIAL_VALUES}
validationSchema={validationSchema}
onSubmit={onSubmit}
validateOnMount
>
<>
{view === StakeDialogView.Details && stakedDetails && (
<DetailsView
handleStake={detailsHandleStake}
handleUnstake={detailsHandleUnstake}
stakedDetails={stakedDetails}
handleClose={handleClose}
/>
)}
{view === StakeDialogView.SelectValidator && (
<SelectValidatorView
selectedValidator={selectedValidator}
handleClose={handleClose}
validators={validators}
onSelect={handleValidatorSelect}
onNext={selectValidatorHandleNext}
/>
)}
{view === StakeDialogView.EnterAmount && (
<EnterAmountView
selectedValidator={selectedValidator}
handleClose={handleClose}
onBack={handleBack}
onStake={handleStake}
gasBudget={newStakeData?.gasBudget}
/>
)}
{view === StakeDialogView.Unstake && stakedDetails && (
<UnstakeView
extendedStake={stakedDetails}
handleClose={handleClose}
showActiveStatus
/>
)}
</>
</Formik>
</Dialog>
);
}
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { useFormatCoin, useBalance, CoinFormat } from '@iota/core';
import { useFormatCoin, useBalance, CoinFormat, parseAmount, useCoinMetadata } from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import {
Button,
@@ -13,17 +13,25 @@ import {
Input,
InputType,
Header,
InfoBoxType,
InfoBoxStyle,
InfoBox,
} from '@iota/apps-ui-kit';
import { useStakeTxnInfo } from '../hooks';
import { Field, type FieldProps, useFormikContext } from 'formik';
import { Exclamation } from '@iota/ui-icons';
import { useCurrentAccount, useIotaClientQuery } from '@iota/dapp-kit';

import { useStakeTxnInfo } from '../hooks';
import { Validator } from './Validator';
import { StakedInfo } from './StakedInfo';
import { Layout, LayoutBody, LayoutFooter } from './Layout';

interface FormValues {
amount: string;
}

interface EnterAmountViewProps {
selectedValidator: string;
amount: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onBack: () => void;
onStake: () => void;
showActiveStatus?: boolean;
@@ -33,31 +41,48 @@ interface EnterAmountViewProps {

function EnterAmountView({
selectedValidator: selectedValidatorAddress,
amount,
onChange,
onBack,
onStake,
gasBudget = 0,
handleClose,
}: EnterAmountViewProps): JSX.Element {
const coinType = IOTA_TYPE_ARG;
const { data: metadata } = useCoinMetadata(coinType);
const decimals = metadata?.decimals ?? 0;

const account = useCurrentAccount();
const accountAddress = account?.address;

const { values } = useFormikContext<FormValues>();
const amount = values.amount;

console.log('amount', amount);

const { data: system } = useIotaClientQuery('getLatestIotaSystemState');
const { data: iotaBalance } = useBalance(accountAddress!);

const coinBalance = BigInt(iotaBalance?.totalBalance || 0);

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

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

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

const hasEnoughRemaingBalance =
maxTokenBalance > parseAmount(values.amount, decimals) + BigInt(2) * _gasBudget;
const shouldShowInsufficientRemainingFundsWarning =
maxTokenFormatted >= values.amount && !hasEnoughRemaingBalance;

console.log(gasBudget);

return (
<Layout>
<Header title="Enter amount" onClose={handleClose} onBack={handleClose} titleCentered />
@@ -76,14 +101,39 @@ function EnterAmountView({
accountAddress={accountAddress!}
/>
<div className="my-md w-full">
<Input
type={InputType.NumericFormat}
label="Amount"
value={amount}
onChange={onChange}
placeholder="Enter amount to stake"
caption={`${maxTokenFormatted} ${maxTokenFormattedSymbol} Available`}
/>
<Field name="amount">
{({
field: { onChange, ...field },
form: { setFieldValue },
meta,
}: FieldProps<FormValues>) => {
return (
<Input
{...field}
onValueChange={(values) =>
setFieldValue('amount', values.value, true)
}
type={InputType.NumericFormat}
label="Amount"
value={amount}
onChange={onChange}
placeholder="Enter amount to stake"
errorMessage={
values.amount && meta.error ? meta.error : undefined
}
caption={`${maxTokenFormatted} ${maxTokenFormattedSymbol} Available`}
/>
);
}}
</Field>
{shouldShowInsufficientRemainingFundsWarning ? (
<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 />}
/>
) : null}
</div>

<Panel hasBorder>
1 change: 1 addition & 0 deletions apps/wallet-dashboard/package.json
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@
"@tanstack/react-query": "^5.50.1",
"@tanstack/react-virtual": "^3.5.0",
"clsx": "^2.1.1",
"formik": "^2.4.2",
"next": "14.2.10",
"react": "^18.3.1",
"react-hot-toast": "^2.4.1",
39 changes: 21 additions & 18 deletions pnpm-lock.yaml

0 comments on commit 9bdb887

Please sign in to comment.