Skip to content

Commit

Permalink
Merge pull request #558 from Concordium/ui-update/update-delegation
Browse files Browse the repository at this point in the history
UI update/update delegation
  • Loading branch information
soerenbf authored Oct 30, 2024
2 parents c43caef + 05304ed commit 7d0971d
Show file tree
Hide file tree
Showing 19 changed files with 352 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function AccountCooldowns({ cooldowns }: Props) {
{cooldowns.map((c) => {
const cdDays = timestampToRelativeDays(c.timestamp);
return (
<Card className="account-cooldowns__item">
<Card className="account-cooldowns__item" key={c.timestamp.toString()}>
<Card.Row className="flex-column">
<Text.Capture className="account-cooldowns__em">{t('inactiveStake.label')}</Text.Capture>
<Text.Capture>{t('inactiveStake.description')}</Text.Capture>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,7 @@
.register-delegator {
&__token-card {
margin-top: rem(16px);
background: $gradient-card-bg;
border-radius: rem(16px);
padding: rem(20px) rem(16px);
margin-bottom: rem(8px);

.token {
margin-bottom: rem(12px);

.token-available {
display: flex;
margin-top: rem(12px);
justify-content: space-between;
}
}

.amount {
padding: rem(24px) 0 rem(8px) 0;
border-top: 1px solid rgba($color-black, 0.1);
border-bottom: 1px solid rgba($color-black, 0.1);

.amount-selected {
display: flex;
margin-top: rem(8px);
justify-content: space-between;
align-items: center;

.capture__additional_small {
border-radius: rem(4px);
padding: rem(4px);
background-color: $secondary-button-bg;
}
}
}

.estimated-fee {
display: flex;
flex-direction: column;
margin-top: rem(4px);
}

.text__main_regular,
.text__main_small,
.capture__additional_small {
color: $color-black;
}

.heading_big {
color: $color-grey-1;
}

.capture__main_small {
color: $color-grey-3;
}
}

&__pool-info {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { useMemo } from 'react';
/* eslint-disable react/destructuring-assignment */
import React, { useEffect, useMemo, useState } from 'react';
import { UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useAtomValue } from 'jotai';
import { useAsyncMemo } from 'wallet-common-helpers';
import { AccountTransactionType, DelegationTargetType } from '@concordium/web-sdk';
import {
AccountTransactionType,
CcdAmount,
ConfigureDelegationPayload,
DelegationTargetType,
} from '@concordium/web-sdk';

import Button from '@popup/popupX/shared/Button';
import FormToggleCheckbox from '@popup/popupX/shared/Form/ToggleCheckbox';
Expand All @@ -12,13 +18,15 @@ import Form, { useForm } from '@popup/popupX/shared/Form';
import TokenAmount, { AmountForm } from '@popup/popupX/shared/Form/TokenAmount';
import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext/AccountInfoListenerContext';
import { displayNameAndSplitAddress, useSelectedCredential } from '@popup/shared/utils/account-helpers';
import { formatTokenAmount } from '@popup/popupX/shared/utils/helpers';
import { formatCcdAmount, formatTokenAmount, parseCcdAmount } from '@popup/popupX/shared/utils/helpers';
import { CCD_METADATA } from '@shared/constants/token-metadata';
import { grpcClientAtom } from '@popup/store/settings';
import Text from '@popup/popupX/shared/Text';
import { useGetTransactionFee } from '@popup/shared/utils/transaction-helpers';
import FullscreenNotice, { FullscreenNoticeProps } from '@popup/popupX/shared/FullscreenNotice';

import { DelegationTypeForm, DelegatorStakeForm, configureDelegatorPayloadFromForm } from '../util';
import { DelegationTypeForm, DelegatorForm, DelegatorStakeForm, configureDelegatorPayloadFromForm } from '../util';
import { STAKE_WARNING_THRESHOLD, isAboveStakeWarningThreshold } from '../../util';

type PoolInfoProps = {
/** The validator pool ID to show information for */
Expand Down Expand Up @@ -60,67 +68,145 @@ function PoolInfo({ validatorId }: PoolInfoProps) {
);
}

type HighStakeNoticeProps = FullscreenNoticeProps & {
onContinue(): void;
};

function HighStakeWarning({ onContinue, ...props }: HighStakeNoticeProps) {
const { t } = useTranslation('x', { keyPrefix: 'earn.delegator.stake.overStakeThresholdWarning' });
return (
<FullscreenNotice {...props}>
<Page>
<Page.Top heading={t('title')} />
{t('description', { threshold: STAKE_WARNING_THRESHOLD.toString() })}
<Page.Footer>
<Button.Main label={t('buttonContinue')} onClick={onContinue} />
<Button.Main label={t('buttonBack')} onClick={props.onClose} />
</Page.Footer>
</Page>
</FullscreenNotice>
);
}

type Props = {
/** The title for the configuriation step */
title: string;
/** The initial values of the step, if any */
initialValues?: DelegatorStakeForm;
/** The delegation target of the transaction */
target: DelegationTypeForm;
/** The existing delegation values registered on the account */
existingValues: DelegatorForm | undefined;
/** The submit handler triggered when submitting the form in the step */
onSubmit(values: DelegatorStakeForm): void;
};

export default function DelegatorStake({ title, target, initialValues, onSubmit }: Props) {
export default function DelegatorStake({ title, target, initialValues, existingValues, onSubmit }: Props) {
const { t } = useTranslation('x', { keyPrefix: 'earn.delegator.stake' });
const form = useForm<DelegatorStakeForm>({
defaultValues: initialValues ?? { amount: '0.00', redelegate: true },
});
const submit = form.handleSubmit(onSubmit);
const selectedCred = useSelectedCredential();
const selectedAccountInfo = useAccountInfo(selectedCred);
const [highStakeWarning, setHighStakeWarning] = useState(false);

const values = form.watch();
const getCost = useGetTransactionFee(AccountTransactionType.ConfigureDelegation);
const fee = useMemo(
() => getCost(configureDelegatorPayloadFromForm({ target, stake: { amount: '0', redelegate: true } })), // Use dummy values, as it does not matter when calculating transaction cost
[target, getCost]
);
const fee = useMemo(() => {
let payload: ConfigureDelegationPayload;
try {
// We try here, as parsing invalid CCD amounts from the input can fail.
payload = configureDelegatorPayloadFromForm({ target, stake: values }, existingValues);
} catch {
// Fall back to a payload from a form with any parsable amount
payload = configureDelegatorPayloadFromForm(
{
target: {
type: target?.type ?? DelegationTargetType.PassiveDelegation,
bakerId: target?.bakerId,
},
stake: { amount: '0', redelegate: values.redelegate ?? false },
},
existingValues
);
}
return getCost(payload);
}, [target, values, getCost]);

useEffect(() => {
if (selectedAccountInfo === undefined || fee === undefined) {
return;
}

try {
const parsed = parseCcdAmount(values.amount);
const newMax = CcdAmount.fromMicroCcd(
selectedAccountInfo.accountAmount.microCcdAmount - fee.microCcdAmount
);
if (parsed.microCcdAmount > newMax.microCcdAmount) {
form.setValue('amount', formatCcdAmount(newMax), { shouldValidate: true });
}
} catch {
// Do nothing..
}
}, [selectedAccountInfo?.accountAmount, fee]);

if (selectedAccountInfo === undefined || selectedCred === undefined || fee === undefined) {
return null;
}

const handleSubmit = () => {
if (!form.formState.errors.amount === undefined) {
submit(); // To set the form to submitted.
return;
}

const amount = parseCcdAmount(form.getValues().amount);
if (isAboveStakeWarningThreshold(amount.microCcdAmount, selectedAccountInfo)) {
setHighStakeWarning(true);
} else {
submit();
}
};

return (
<Page className="register-delegator-container">
<Page.Top heading={title} />
<Text.Capture className="m-l-5 m-t-neg-5">
{t('selectedAccount', { account: displayNameAndSplitAddress(selectedCred) })}
</Text.Capture>
<Form formMethods={form} onSubmit={onSubmit}>
{(f) => (
<>
<TokenAmount
accountInfo={selectedAccountInfo}
fee={fee}
tokenType="ccd"
buttonMaxLabel={t('inputAmount.buttonMax')}
form={f as unknown as UseFormReturn<AmountForm>}
ccdBalance="total"
/>
{target.type === DelegationTargetType.Baker && (
<PoolInfo validatorId={BigInt(target.bakerId!)} />
)}
<div className="register-delegator__reward">
<div className="register-delegator__reward_auto-add">
<Text.Main>{t('redelegate.label')}</Text.Main>
<FormToggleCheckbox register={f.register} name="redelegate" />
<>
<HighStakeWarning open={highStakeWarning} onClose={() => setHighStakeWarning(false)} onContinue={submit} />
<Page className="register-delegator-container">
<Page.Top heading={title} />
<Text.Capture className="m-l-5 m-t-neg-5">
{t('selectedAccount', { account: displayNameAndSplitAddress(selectedCred) })}
</Text.Capture>
<Form formMethods={form} onSubmit={handleSubmit}>
{(f) => (
<>
<TokenAmount
className="register-delegator__token-card"
accountInfo={selectedAccountInfo}
fee={fee}
tokenType="ccd"
buttonMaxLabel={t('inputAmount.buttonMax')}
form={f as unknown as UseFormReturn<AmountForm>}
ccdBalance="total"
/>
{target.type === DelegationTargetType.Baker && (
<PoolInfo validatorId={BigInt(target.bakerId!)} />
)}
<div className="register-delegator__reward">
<div className="register-delegator__reward_auto-add">
<Text.Main>{t('redelegate.label')}</Text.Main>
<FormToggleCheckbox register={f.register} name="redelegate" />
</div>
<Text.Capture>{t('redelegate.description')}</Text.Capture>
</div>
<Text.Capture>{t('redelegate.description')}</Text.Capture>
</div>
</>
)}
</Form>
<Page.Footer>
<Button.Main className="m-t-20" label={t('buttonContinue')} onClick={submit} />
</Page.Footer>
</Page>
</>
)}
</Form>
<Page.Footer>
<Button.Main className="m-t-20" label={t('buttonContinue')} onClick={handleSubmit} />
</Page.Footer>
</Page>
</>
);
}
Loading

0 comments on commit 7d0971d

Please sign in to comment.