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

Rui/loans modals lose close animation due to conditional render #1460

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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