Skip to content

Commit

Permalink
Rui/loans modals lose close animation due to conditional render (#1460)
Browse files Browse the repository at this point in the history
* wip

* feat: continue

* fix: code review

* fix:merge

---------

Co-authored-by: Thomas Jeatt <[email protected]>
  • Loading branch information
danielsimao and tomjeatt authored Jul 20, 2023
1 parent 56f45b0 commit c26b788
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 98 deletions.
2 changes: 2 additions & 0 deletions src/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,8 @@
"action_liquidation_risk": "{{action}} this amount will increase your LTV, thus also increasing the risk of liquidation.",
"no_loan_positions": "No {{loanType}} positions",
"your_loan_positions_will_show_here": "Your {{loanType}} positions will show here",
"use_ticker_as_collateral": "Use {{ticker}} as Collateral",
"disable_ticker": "Disable {{ticker}}",
"owed": "Owed",
"lend_apy_ticker": "Lend APY {{ticker}}",
"borrow_apy_ticker": "Borrow APY {{ticker}}",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { LoanAsset } from '@interlay/interbtc-api';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';

import { Flex } from '@/component-library';
import { AuthCTA, TransactionFeeDetails } from '@/components';
import {
LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD,
toggleCollateralLoanSchema,
ToggleCollateralLoansFormData,
useForm
} from '@/lib/form';
import { useGetAccountLendingStatistics } from '@/utils/hooks/api/loans/use-get-account-lending-statistics';
import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { isTransactionFormDisabled } from '@/utils/hooks/transaction/utils/form';

import { CollateralModalVariant } from './CollateralModal';

type CollateralFormProps = {
asset: LoanAsset;
variant: Extract<CollateralModalVariant, 'enable' | 'disable'>;
isOpen?: boolean;
onSigning: () => void;
};

const CollateralForm = ({ asset, variant, isOpen, onSigning }: CollateralFormProps): JSX.Element => {
const { t } = useTranslation();

const { refetch } = useGetAccountLendingStatistics();

const overlappingModalRef = useRef<HTMLDivElement>(null);

const transaction = useTransaction({
onSigning,
onSuccess: refetch
});

const handleSubmit = () => {
if (variant === 'enable') {
return transaction.execute(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
} else {
return transaction.execute(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
}
};

const form = useForm<ToggleCollateralLoansFormData>({
initialValues: {
[LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD]: ''
},
validationSchema: toggleCollateralLoanSchema(),
onSubmit: handleSubmit,
onComplete: async () => {
if (variant === 'enable') {
return transaction.fee.estimate(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
} else {
return transaction.fee.estimate(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
}
}
});

// Doing this call on mount so that the form becomes dirty
// TODO: find better approach
useEffect(() => {
if (!isOpen) return;

form.setFieldValue(LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD, transaction.fee.defaultCurrency.ticker, true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);

const isBtnDisabled = isTransactionFormDisabled(form, transaction.fee);

return (
<form onSubmit={form.handleSubmit}>
<Flex direction='column' gap='spacing4'>
<TransactionFeeDetails
fee={transaction.fee}
selectProps={{
...form.getSelectFieldProps(LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD),
modalRef: overlappingModalRef
}}
/>
<AuthCTA type='submit' size='large' disabled={isBtnDisabled} loading={transaction.isLoading}>
{variant === 'enable'
? t('use_ticker_as_collateral', { ticker: asset.currency.ticker })
: t('disable_ticker', { ticker: asset.currency.ticker })}
</AuthCTA>
</Flex>
</form>
);
};

export { CollateralForm };
export type { CollateralFormProps };
Original file line number Diff line number Diff line change
@@ -1,52 +1,38 @@
import { CollateralPosition, CurrencyExt, LoanAsset } from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
import { useEffect, useRef } from 'react';
import { useRef } from 'react';
import { TFunction, useTranslation } from 'react-i18next';

import { CTA, Flex, Modal, ModalBody, ModalFooter, ModalHeader, ModalProps, Status } from '@/component-library';
import { AuthCTA, TransactionFeeDetails } from '@/components';
import {
LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD,
toggleCollateralLoanSchema,
ToggleCollateralLoansFormData,
useForm
} from '@/lib/form';
import { useGetAccountLendingStatistics } from '@/utils/hooks/api/loans/use-get-account-lending-statistics';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { isTransactionFormDisabled } from '@/utils/hooks/transaction/utils/form';

import { useGetLTV } from '../../hooks/use-get-ltv';
import { BorrowLimit } from '../BorrowLimit';
import { CollateralForm } from './CollateralForm';
import { StyledDescription } from './CollateralModal.style';

type CollateralModalVariant = 'enable' | 'disable' | 'disable-error' | 'disable-vault-collateral';

const getContentMap = (t: TFunction, variant: CollateralModalVariant, asset: LoanAsset) =>
const getContentMap = (t: TFunction, variant: CollateralModalVariant) =>
({
enable: {
title: 'Enable as Collateral',
description:
'Each asset used as collateral increases your borrowing limit. Be aware that this can subject the asset to being seized in liquidation.',
buttonLabel: `Use ${asset.currency.ticker} as Collateral`
'Each asset used as collateral increases your borrowing limit. Be aware that this can subject the asset to being seized in liquidation.'
},
disable: {
title: 'Disable Collateral',
description:
"This asset will no longer be used towards your borrowing limit, and can't be seized in liquidation.",
buttonLabel: `Disable ${asset.currency.ticker}`
description: "This asset will no longer be used towards your borrowing limit, and can't be seized in liquidation."
},
'disable-error': {
title: 'Collateral Required',
description:
'This asset is required to support your borrowed assets. Either repay borrowed assets, or supply another asset as collateral.',
buttonLabel: `Dismiss`
'This asset is required to support your borrowed assets. Either repay borrowed assets, or supply another asset as collateral.'
},
'disable-vault-collateral': {
title: 'Already used as vault collateral',
description:
'This asset is already used as vault collateral and therefore can not be used as collateral for lending.',
buttonLabel: `Dismiss`
'This asset is already used as vault collateral and therefore can not be used as collateral for lending.'
}
}[variant]);

Expand All @@ -64,8 +50,8 @@ const getModalVariant = (
};

type Props = {
asset: LoanAsset;
position: CollateralPosition;
asset?: LoanAsset;
position?: CollateralPosition;
};

type InheritAttrs = Omit<ModalProps, keyof Props | 'children'>;
Expand All @@ -74,58 +60,23 @@ type CollateralModalProps = Props & InheritAttrs;

const CollateralModal = ({ asset, position, onClose, isOpen, ...props }: CollateralModalProps): JSX.Element | null => {
const { t } = useTranslation();
const { refetch } = useGetAccountLendingStatistics();

const { getLTV } = useGetLTV();
const prices = useGetPrices();

const overlappingModalRef = useRef<HTMLDivElement>(null);

const transaction = useTransaction({
onSigning: onClose,
onSuccess: refetch
});
if (!asset || !position) {
return null;
}

const { isCollateral: isCollateralActive, amount: lendPositionAmount, vaultCollateralAmount } = position;

const loanAction = isCollateralActive ? 'withdraw' : 'lend';
const currentLTV = getLTV({ type: loanAction, amount: lendPositionAmount });
const variant = getModalVariant(isCollateralActive, currentLTV?.status, vaultCollateralAmount);

const handleSubmit = () => {
if (variant === 'enable') {
return transaction.execute(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
} else {
return transaction.execute(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
}
};

const form = useForm<ToggleCollateralLoansFormData>({
initialValues: {
[LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD]: ''
},
validationSchema: toggleCollateralLoanSchema(),
onSubmit: handleSubmit,
onComplete: async () => {
if (variant === 'enable') {
return transaction.fee.estimate(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
} else {
return transaction.fee.estimate(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
}
}
});

// Doing this call on mount so that the form becomes dirty
// TODO: find better approach
useEffect(() => {
if (variant === 'disable-error' || variant === 'disable-vault-collateral' || !isOpen) return;

form.setFieldValue(LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD, transaction.fee.defaultCurrency.ticker, true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen, variant]);

const content = getContentMap(t, variant, asset);

const isBtnDisabled = isTransactionFormDisabled(form, transaction.fee);
const content = getContentMap(t, variant);

return (
<Modal
Expand All @@ -146,28 +97,15 @@ const CollateralModal = ({ asset, position, onClose, isOpen, ...props }: Collate
<ModalFooter>
{variant === 'disable-error' || variant === 'disable-vault-collateral' ? (
<CTA size='large' onPress={onClose}>
{content.buttonLabel}
{t('dismiss')}
</CTA>
) : (
<form onSubmit={form.handleSubmit}>
<Flex direction='column' gap='spacing4'>
<TransactionFeeDetails
fee={transaction.fee}
selectProps={{
...form.getSelectFieldProps(LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD),
modalRef: overlappingModalRef
}}
/>
<AuthCTA type='submit' size='large' disabled={isBtnDisabled} loading={transaction.isLoading}>
{content.buttonLabel}
</AuthCTA>
</Flex>
</form>
<CollateralForm asset={asset} onSigning={onClose} variant={variant} isOpen={isOpen} />
)}
</ModalFooter>
</Modal>
);
};

export { CollateralModal };
export type { CollateralModalProps };
export type { CollateralModalProps, CollateralModalVariant };
27 changes: 12 additions & 15 deletions src/pages/Loans/LoansOverview/components/LoansTables/LendTables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { LoanModal } from '../LoanModal';
import { StyledLendAssetsTable, StyledLendPositionsTable } from './LoansTables.style';

type UseAssetState = {
isOpen: boolean;
type?: 'toggle-collateral' | 'change-loan';
data?: LoanAsset;
position?: CollateralPosition;
};

const defaultAssetState: UseAssetState = { type: undefined, data: undefined, position: undefined };

type LendTablesProps = {
assets: TickerToData<LoanAsset>;
positions: CollateralPosition[];
Expand All @@ -22,23 +21,23 @@ type LendTablesProps = {
};

const LendTables = ({ assets, positions, disabledAssets, hasPositions }: LendTablesProps): JSX.Element => {
const [selectedAsset, setAsset] = useState<UseAssetState>(defaultAssetState);
const [selectedAsset, setAsset] = useState<UseAssetState>({ isOpen: false });

const handleRowAction = (ticker: Key) => {
const asset = assets[ticker as string];
const position = getPosition(positions, ticker as string);

setAsset({ type: 'change-loan', data: asset, position });
setAsset({ isOpen: true, type: 'change-loan', data: asset, position });
};

const handlePressCollateralSwitch = (ticker: string) => {
const asset = assets[ticker];
const position = getPosition(positions, ticker);

setAsset({ type: 'toggle-collateral', data: asset, position });
setAsset({ isOpen: true, type: 'toggle-collateral', data: asset, position });
};

const handleClose = () => setAsset(defaultAssetState);
const handleClose = () => setAsset((s) => ({ ...s, isOpen: false }));

return (
<>
Expand All @@ -55,19 +54,17 @@ const LendTables = ({ assets, positions, disabledAssets, hasPositions }: LendTab
<StyledLendAssetsTable assets={assets} onRowAction={handleRowAction} disabledKeys={disabledAssets} />
<LoanModal
variant='lend'
isOpen={selectedAsset.type === 'change-loan'}
isOpen={selectedAsset.isOpen && selectedAsset.type === 'change-loan'}
asset={selectedAsset.data}
position={selectedAsset.position}
onClose={handleClose}
/>
<CollateralModal
isOpen={selectedAsset.isOpen && selectedAsset.type === 'toggle-collateral'}
asset={selectedAsset.data}
position={selectedAsset.position}
onClose={handleClose}
/>
{selectedAsset.data && selectedAsset.position && (
<CollateralModal
isOpen={selectedAsset.type === 'toggle-collateral'}
asset={selectedAsset.data}
position={selectedAsset.position}
onClose={handleClose}
/>
)}
</>
);
};
Expand Down
13 changes: 9 additions & 4 deletions src/pages/Pools/components/PoolsTables/PoolsTables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ import { AccountLiquidityPool } from '@/utils/hooks/api/amm/use-get-account-pool

import { PoolModal } from '../PoolModal/PoolModal';

type ModalState = {
isOpen: boolean;
data?: LiquidityPool;
};

type PoolsTablesProps = {
pools: LiquidityPool[];
accountPools?: AccountLiquidityPool[];
};

const PoolsTables = ({ pools, accountPools }: PoolsTablesProps): JSX.Element => {
const [liquidityPool, setLiquidityPool] = useState<LiquidityPool>();
const [state, setState] = useState<ModalState>({ isOpen: false });

const handleRowAction = (ticker: Key) => {
const pool = pools.find((pool) => pool.lpToken.ticker === ticker);
setLiquidityPool(pool);
setState({ isOpen: true, data: pool });
};

const handleClose = () => setLiquidityPool(undefined);
const handleClose = () => setState((s) => ({ ...s, isOpen: false }));

const otherPools = accountPools
? pools.filter(
Expand All @@ -42,7 +47,7 @@ const PoolsTables = ({ pools, accountPools }: PoolsTablesProps): JSX.Element =>
/>
)}
</Flex>
<PoolModal isOpen={!!liquidityPool} pool={liquidityPool} onClose={handleClose} />
<PoolModal isOpen={state.isOpen} pool={state.data} onClose={handleClose} />
</>
);
};
Expand Down

2 comments on commit c26b788

@vercel
Copy link

@vercel vercel bot commented on c26b788 Jul 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on c26b788 Jul 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.