Skip to content

Commit

Permalink
Merge pull request #5316 from LiskHQ/5241-make-fees-editable
Browse files Browse the repository at this point in the history
Make transaction fees editable
  • Loading branch information
ManuGowda authored Sep 21, 2023
2 parents 6d10ab8 + f85633f commit 70bbfff
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,22 +158,33 @@ describe('ChangeCommissionForm', () => {
fireEvent.change(input, { target: { value } });
fireEvent.click(button);

const feeToken = { availableBalance: 40000000, chainID: '04000000', symbol: 'LSK' };

await waitFor(() => {
expect(nextStep).toHaveBeenCalledWith(
expect.objectContaining({
fees: [
{
components: [
{
feeToken: { availableBalance: 40000000, chainID: '04000000', symbol: 'LSK' },
feeToken,
type: 'bytesFee',
value: 96000n,
},
],
title: 'Transaction',
token: feeToken,
label: 'transactionFee',
value: '-',
},
{
components: [],
isHidden: true,
title: 'Message',
value: '-',
token: feeToken,
label: 'messageFee',
},
{ components: [], isHidden: true, title: 'Message', value: '-' },
],
formProps: {
composedFees: [
Expand All @@ -187,8 +198,17 @@ describe('ChangeCommissionForm', () => {
],
title: 'Transaction',
value: '-',
token: feeToken,
label: 'transactionFee',
},
{
components: [],
isHidden: true,
title: 'Message',
value: '-',
token: feeToken,
label: 'messageFee',
},
{ components: [], isHidden: true, title: 'Message', value: '-' },
],
fields: {
newCommission: '30.00',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,30 +218,35 @@ describe('Unlock LSK modal', () => {
wrapper.update();
});
await flushPromises();
const feeToken = {
availableBalance: '1000000000',
chainName: 'Lisk',
lockedBalances: [{ amount: '10000000000', moduleID: '5' }],
symbol: 'LSK',
tokenID: '0000000100000000',
};
expect(props.nextStep).toBeCalledWith({
transactionJSON,
formProps: {
composedFees: [
{
title: 'Transaction',
label: 'transactionFee',
token: feeToken,
value: '-',
components: [
{
type: 'bytesFee',
value: 96000n,
feeToken: {
availableBalance: '1000000000',
chainName: 'Lisk',
lockedBalances: [{ amount: '10000000000', moduleID: '5' }],
symbol: 'LSK',
tokenID: '0000000100000000',
},
feeToken,
},
],
},
{
title: 'Message',
value: '-',
label: 'messageFee',
token: feeToken,
isHidden: true,
components: [],
},
Expand All @@ -258,6 +263,8 @@ describe('Unlock LSK modal', () => {
{
title: 'Transaction',
value: '-',
label: 'transactionFee',
token: feeToken,
components: [
{
type: 'bytesFee',
Expand All @@ -275,6 +282,8 @@ describe('Unlock LSK modal', () => {
{
title: 'Message',
value: '-',
label: 'messageFee',
token: feeToken,
isHidden: true,
components: [],
},
Expand All @@ -287,28 +296,38 @@ describe('Unlock LSK modal', () => {
wrapper.find('.confirm-btn').at(0).simulate('click');

await flushPromises();
const feeToken = {
availableBalance: '1000000000',
chainName: 'Lisk',
lockedBalances: [{ amount: '10000000000', moduleID: '5' }],
symbol: 'LSK',
tokenID: '0000000100000000',
};

expect(props.nextStep).toBeCalledWith(
expect.objectContaining({
fees: [
{
components: [
{
feeToken: {
availableBalance: '1000000000',
chainName: 'Lisk',
lockedBalances: [{ amount: '10000000000', moduleID: '5' }],
symbol: 'LSK',
tokenID: '0000000100000000',
},
feeToken,
type: 'bytesFee',
value: 96000n,
},
],
title: 'Transaction',
token: feeToken,
label: 'transactionFee',
value: '-',
},
{ components: [], isHidden: true, title: 'Message', value: '-' },
{
components: [],
isHidden: true,
title: 'Message',
value: '-',
label: 'messageFee',
token: feeToken,
},
],
formProps: {
composedFees: [
Expand All @@ -327,9 +346,18 @@ describe('Unlock LSK modal', () => {
},
],
title: 'Transaction',
token: feeToken,
label: 'transactionFee',
value: '-',
},
{
components: [],
isHidden: true,
title: 'Message',
value: '-',
label: 'messageFee',
token: feeToken,
},
{ components: [], isHidden: true, title: 'Message', value: '-' },
],
enableMinimumBalanceFeedback: true,
fields: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
padding-left: 0px !important;
margin-top: 14px;
margin-bottom: -4px;
width: fit-content;

& > img {
width: 16px;
Expand Down
138 changes: 92 additions & 46 deletions src/modules/transaction/components/TransactionPriority/FeeViewer.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useState } from 'react';
/* eslint-disable complexity */
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { convertFromBaseDenom } from '@token/fungible/utils/helpers';
import Input from 'src/theme/Input/Input';
import Icon from 'src/theme/Icon';
import Spinner from 'src/theme/Spinner';
import Tooltip from 'src/theme/Tooltip';
import { keyCodes } from 'src/utils/keyCodes';
import { validateAmount } from 'src/utils/validators';
import styles from './TransactionPriority.css';

Expand Down Expand Up @@ -45,47 +47,89 @@ const FeesViewer = ({
isCustom,
onInputFee,
feeValue,
minFee,
fees,
setCustomFee,
customFee,
token,
minRequiredBalance,
computedMinimumFees,
}) => {
const { t } = useTranslation();
const [showEditIcon, setShowEditIcon] = useState(false);
const [showEditInput, setShowEditInput] = useState({});
const composedFeeList = fees.filter(({ isHidden }) => !isHidden);

const onInputFocus = (e) => {
e.preventDefault();
if (!feeValue) onInputFee(minFee);
useEffect(() => {
if (showEditInput) {
composedFeeList.forEach(({ label, token }) => {
if (!customFee?.value?.[label]) {
onInputFee((state) => ({
...state,
[label]: convertFromBaseDenom(computedMinimumFees[label], token),
}));
}
});
}
}, [showEditInput]);

const onInputKeyUp = (e, label) => {
if (e.keyCode !== keyCodes.enter || customFee?.error?.[label]) return;
setShowEditInput((state) => ({ ...state, [label]: false }));
};

const onInputBlur = (e) => {
const onInputBlur = (e, label) => {
e.preventDefault();
setShowEditIcon(true);
if (customFee?.error?.[label]) {
const { token } = composedFeeList.find(
(composedFeeValue) => composedFeeValue.label === label
);
const minFeeFromBaseDenom = convertFromBaseDenom(computedMinimumFees[label], token);

onInputFee((minFees) => ({
...minFees,
[label]: minFeeFromBaseDenom,
}));

setCustomFee((state) => ({
value: {
...state.value,
[label]: minFeeFromBaseDenom,
},
feedback: {},
error: {},
}));
}
setShowEditInput({});
};

const onInputChange = (e) => {
const onInputChange = (e, label) => {
e.preventDefault();
const customFeeInput = e.target.value;
onInputFee(customFeeInput);
const { token } = composedFeeList.find((composedFeeValue) => composedFeeValue.label === label);

const customFeeStatus = getCustomFeeStatus({
customFeeInput,
minFee,
minRequiredBalance,
token,
minFee: computedMinimumFees[label],
});
setCustomFee({
value: !customFeeStatus ? customFeeInput || minFee : minFee,
feedback: customFeeStatus,
error: !!customFeeStatus,
});

onInputFee((minFees) => ({
...minFees,
[label]: customFeeInput,
}));

setCustomFee((state) => ({
value: {
...state.value,
[label]: customFeeInput,
},
feedback: { ...state.feedback, [label]: customFeeStatus },
error: { ...state.error, [label]: !!customFeeStatus },
}));
};

const onClickCustomEdit = (e) => {
const onClickCustomEdit = (e, label) => {
e.preventDefault();
setShowEditIcon(false);
setShowEditInput(() => ({ [label]: true }));
};

if (isLoading) {
Expand All @@ -97,37 +141,39 @@ const FeesViewer = ({
);
}

if (isCustom && !showEditIcon) {
return (
<Input
className="custom-fee-input"
autoFocus
type="text"
size="m"
value={feeValue}
onChange={onInputChange}
onBlur={onInputBlur}
onFocus={onInputFocus}
status={
getCustomFeeStatus({ customFeeInput: feeValue, minFee, minRequiredBalance, token })
? 'error'
: 'ok'
}
feedback={customFee?.feedback}
/>
);
}

return (
<div className={styles.feesListWrapper}>
{composedFeeList.map(({ title, value, components }) => (
{composedFeeList.map(({ title, value, components, label }) => (
<div className={styles.feeRow} key={title}>
<span>{title}</span>
<span className={`${styles.value} fee-value-${title}`} onClick={onClickCustomEdit}>
{typeof value === 'object' ? value?.value : value}
{isCustom && showEditIcon && title === 'Transaction' && <Icon name="edit" />}
{title === 'Transaction' && components.length !== 0 && (
<Tooltip position="top left">
<span className={`${styles.value} fee-value-${title}`}>
{isCustom && showEditInput[label] ? (
<div className={styles.feeInput}>
<Input
autoFocus
className="custom-fee-input"
type="text"
size="xs"
value={feeValue[label]}
onChange={(e) => onInputChange(e, label)}
onKeyUp={(e) => onInputKeyUp(e, label)}
onBlur={(e) => onInputBlur(e, label)}
status={customFee?.error?.[label] ? 'error' : 'ok'}
feedback={customFee?.feedback?.[label]}
/>
</div>
) : (
<span>{typeof value === 'object' ? value?.value : value}</span>
)}
{isCustom && !showEditInput[label] && (
<Icon
data-testid="edit-icon"
onClick={(e) => onClickCustomEdit(e, label)}
name="edit"
/>
)}
{title === 'Transaction' && components.length !== 0 && !isCustom && (
<Tooltip className={styles.tooltip} position="top left">
<div className={styles.feesBreakdownRow}>
<p>{t('Fee breakdown')}</p>
{components.map(({ type, value: feeValueInfo, feeToken }, index) => (
Expand Down
Loading

0 comments on commit 70bbfff

Please sign in to comment.