mobile-app/app/screens/AppNavigator/screens/Loans/VaultDetail/components/VaultDetailCollateralsRow.tsx
import BigNumber from "bignumber.js";
import { useEffect, useState } from "react";
import { ThemedIcon, ThemedTextV2, ThemedViewV2 } from "@components/themed";
import { tailwind } from "@tailwind";
import { View } from "@components";
import { SymbolIcon } from "@components/SymbolIcon";
import { NumericFormat as NumberFormat } from "react-number-format";
import { LoanVault } from "@store/loans";
import {
CollateralToken,
LoanVaultState,
} from "@defichain/whale-api-client/dist/api/loan";
import { translate } from "@translations";
import { ActiveUSDValueV2 } from "@screens/AppNavigator/screens/Loans/VaultDetail/components/ActiveUSDValueV2";
import { LoanAddRemoveActionButton } from "@screens/AppNavigator/screens/Loans/components/LoanActionButton";
import {
BottomSheetAlertInfoV2,
BottomSheetInfoV2,
} from "@components/BottomSheetInfoV2";
import { CollateralFactorTag } from "@components/CollateralFactorTag";
import { CollateralItem } from "@screens/AppNavigator/screens/Loans/screens/EditCollateralScreen";
import { VaultStatus } from "@screens/AppNavigator/screens/Loans/VaultStatusTypes";
import { useFeatureFlagContext } from "@contexts/FeatureFlagContext";
import { getCollateralPrice } from "../../hooks/CollateralPrice";
interface CollateralCardProps {
displaySymbol: string;
amount: BigNumber;
collateralItem: CollateralToken;
totalCollateralValue: BigNumber;
onAddCollateralPress: () => void;
onRemoveCollateralPress: () => void;
vaultStatus?: string;
}
export function VaultDetailCollateralsRow({
vault,
collateralTokens,
vaultStatus,
onAddPress,
onRemovePress,
}: {
vault: LoanVault;
collateralTokens: CollateralItem[];
vaultStatus?: string;
onAddPress: (collateralItem: CollateralItem) => void;
onRemovePress: (collateralItem: CollateralItem) => void;
}): JSX.Element {
const { isFeatureAvailable } = useFeatureFlagContext();
const [hideDFIStaticCard, setHideDFIStaticCard] = useState<boolean>(false);
const [isAffectedVault, setIsAffectedVault] = useState<boolean>(false); // Affected Vault means having DUSD in both collaterals and loans
const [info, setInfo] = useState<{
displayText: string;
title: string;
message: string;
}>();
useEffect(() => {
if (vault.state !== LoanVaultState.IN_LIQUIDATION) {
setHideDFIStaticCard(
vault.collateralAmounts.some((col) => col.displaySymbol === "DFI"),
);
setIsAffectedVault(
vault.collateralAmounts.some((col) => col.displaySymbol === "DUSD") &&
vault.loanAmounts.some((loan) => loan.displaySymbol === "DUSD"),
);
}
}, [vault]);
useEffect(() => {
const isLoopDusdAllowed = isFeatureAvailable("loop_dusd");
if (isAffectedVault && isLoopDusdAllowed) {
setInfo({
displayText:
"Maintain either 100% DUSD (recommended) or at least 50% DFI as collateral for DUSD loans",
title: "Why is this so?",
message:
"DUSD loans which contains DUSD as collateral are required to maintain at least 50% of the collateral in the form of DFI.\n\nThis only affects vaults that has DUSD as both collateral and loan.\n\nTake note that DUSD Loop Vault will become a regular Vault if DFI is added as collateral.",
});
} else if (isAffectedVault) {
setInfo({
displayText: "Maintain at least 50% DFI as collateral for DUSD loans",
title: "Why you need 50% DFI",
message:
"DUSD loans which contains DUSD as collateral are required to maintain at least 50% of the collateral in the form of DFI.\n\nThis only affects vaults that has DUSD as both collateral and loan.",
});
} else {
setInfo({
displayText:
"Your loan amount can be maximized by adding DFI/DUSD as collaterals",
title: "DFI/DUSD collaterals",
message:
"Adding in DFI and/or DUSD will boost your borrowing power and help maximize your vault's loan amount.",
});
}
}, [isAffectedVault]);
return (
<View style={tailwind("mx-5 mt-6")}>
<ThemedTextV2
light={tailwind("text-mono-light-v2-500")}
dark={tailwind("text-mono-dark-v2-500")}
style={tailwind("text-xs font-normal-v2 mb-2 px-5")}
>
{translate("screens/VaultDetailScreenCollateralSection", "COLLATERALS")}
</ThemedTextV2>
{vault.state === LoanVaultState.IN_LIQUIDATION &&
vault.batches.length > 0 &&
vault.batches[0].collaterals.map((collateral) => {
return (
<LiquidatedVaultCollateralCard
key={collateral.id}
displaySymbol={collateral.displaySymbol}
/>
);
})}
{!hideDFIStaticCard && (
<CollateralCardDfi
onDFIAdd={() => {
const collateralItem = collateralTokens.find(
(col) => col.token.displaySymbol === "DFI",
);
if (collateralItem !== undefined) {
onAddPress(collateralItem);
}
}}
/>
)}
{vault.state !== LoanVaultState.IN_LIQUIDATION &&
vault.collateralAmounts.map((collateral, index) => {
const collateralItem = collateralTokens.find(
(col) => col.token.id === collateral.id,
);
if (collateralItem !== undefined) {
return (
<CollateralCard
key={collateral.id}
collateralItem={collateralItem}
totalCollateralValue={new BigNumber(vault.collateralValue)}
displaySymbol={collateral.displaySymbol}
amount={new BigNumber(collateral.amount)}
onAddCollateralPress={() => onAddPress(collateralItem)}
onRemoveCollateralPress={() => onRemovePress(collateralItem)}
vaultStatus={vaultStatus}
/>
);
} else {
// TODO Add Skeleton Loader
return <View key={index} />;
}
})}
{info && (
<InfoText
displayText={translate(
"screens/VaultDetailScreenCollateralSection",
info.displayText,
)}
info={{
title: translate(
"screens/VaultDetailScreenCollateralSection",
info.title,
),
message: translate(
"screens/VaultDetailScreenCollateralSection",
info.message,
),
}}
isAffectedVault={isAffectedVault}
/>
)}
</View>
);
}
function LiquidatedVaultCollateralCard({
displaySymbol,
}: {
displaySymbol: string;
}): JSX.Element {
return (
<ThemedViewV2
light={tailwind("bg-mono-light-v2-00")}
dark={tailwind("bg-mono-dark-v2-00")}
style={tailwind("p-4 mb-2 rounded-lg-v2")}
>
<View style={tailwind("flex flex-row justify-between items-center")}>
<View style={tailwind("flex flex-row items-center")}>
<SymbolIcon symbol={displaySymbol} styleHeight={36} styleWidth={36} />
<ThemedTextV2
light={tailwind("text-gray-300")}
dark={tailwind("text-gray-600")}
style={tailwind("ml-2 font-medium")}
>
{displaySymbol}
</ThemedTextV2>
</View>
</View>
</ThemedViewV2>
);
}
function CollateralCardDfi({
onDFIAdd,
}: {
onDFIAdd: () => void;
}): JSX.Element {
return (
<ThemedViewV2
light={tailwind("bg-mono-light-v2-00")}
dark={tailwind("bg-mono-dark-v2-00")}
style={tailwind("py-4 px-5 mb-2 rounded-lg-v2")}
testID="collateral_card_dfi_empty"
>
<View style={tailwind("flex flex-row justify-between items-center")}>
<View style={tailwind("flex flex-row items-center")}>
<SymbolIcon symbol="DFI" styleWidth={36} styleHeight={36} />
<View style={tailwind("ml-2")}>
<ThemedTextV2
dark={tailwind("text-mono-dark-v2-900")}
light={tailwind("text-mono-light-v2-900")}
style={tailwind("text-sm font-semibold-v2")}
// eslint-disable-next-line react-native/no-raw-text
>
0.00
</ThemedTextV2>
<ThemedTextV2
dark={tailwind("text-mono-dark-v2-700")}
light={tailwind("text-mono-light-v2-700")}
style={tailwind("text-xs font-normal-v2")}
// eslint-disable-next-line react-native/no-raw-text
>
$ 0.00
</ThemedTextV2>
</View>
</View>
<LoanAddRemoveActionButton onAdd={onDFIAdd} leftDisabled token="DFI" />
</View>
</ThemedViewV2>
);
}
function CollateralCard(props: CollateralCardProps): JSX.Element {
const prices = getCollateralPrice(
props.amount,
props.collateralItem,
props.totalCollateralValue,
);
return (
<ThemedViewV2
light={tailwind("bg-mono-light-v2-00")}
dark={tailwind("bg-mono-dark-v2-00")}
style={tailwind("py-4 px-5 mb-2 rounded-lg-v2")}
testID={`vault_detail_collateral_${props.displaySymbol}`}
>
<View style={tailwind("flex flex-row justify-between items-center")}>
<View style={tailwind("flex flex-row items-center")}>
<SymbolIcon
symbol={props.displaySymbol}
styleWidth={36}
styleHeight={36}
/>
<View style={tailwind("ml-2")}>
<View style={tailwind("flex flex-row items-center")}>
<NumberFormat
value={props.amount?.toFixed(8)}
thousandSeparator
displayType="text"
renderText={(val: string) => (
<ThemedTextV2
dark={tailwind("text-mono-dark-v2-900")}
light={tailwind("text-mono-light-v2-900")}
style={tailwind("text-sm font-semibold-v2")}
testID={`vault_detail_collateral_${props.displaySymbol}_amount`}
>
{val}
</ThemedTextV2>
)}
/>
<CollateralFactorTag
factor={props.collateralItem.factor}
containerStyle={tailwind(
"h-5 flex flex-row items-center rounded px-2 py-1 ml-1 border-0.5",
)}
textStyle={tailwind("font-semibold-v2 text-2xs leading-3")}
/>
</View>
<View style={tailwind("flex flex-row")}>
<ActiveUSDValueV2
price={new BigNumber(props.amount).multipliedBy(
prices.activePrice,
)}
testId={`vault_detail_collateral_${props.displaySymbol}_usd`}
/>
<NumberFormat
value={prices.vaultShare?.toFixed(2)}
thousandSeparator
displayType="text"
suffix="%"
renderText={(val: string) => (
<ThemedTextV2
dark={tailwind("text-mono-dark-v2-700", {
"text-red-v2":
props.displaySymbol === "DFI" &&
new BigNumber(prices.vaultShare).lt(new BigNumber(50)),
})}
light={tailwind("text-mono-light-v2-700", {
"text-red-v2":
props.displaySymbol === "DFI" &&
new BigNumber(prices.vaultShare).lt(new BigNumber(50)),
})}
style={tailwind("text-xs font-normal-v2")}
testID={`vault_detail_collateral_${props.displaySymbol}_vault_share`}
>
{` (${val})`}
</ThemedTextV2>
)}
/>
</View>
</View>
</View>
<LoanAddRemoveActionButton
onAdd={props.onAddCollateralPress}
onRemove={props.onRemoveCollateralPress}
token={props.displaySymbol}
leftDisabled={props.vaultStatus === VaultStatus.Halted}
/>
</View>
</ThemedViewV2>
);
}
function InfoText({
displayText,
info,
isAffectedVault,
}: {
displayText: string;
info: BottomSheetAlertInfoV2;
isAffectedVault: boolean;
}): JSX.Element {
return (
<View style={tailwind("flex-row mx-5 items-center")}>
<ThemedTextV2
light={tailwind("text-mono-light-v2-500")}
dark={tailwind("text-mono-dark-v2-500")}
style={tailwind("flex-1 flex-wrap text-xs font-normal-v2 pr-1")}
testID="info_text"
>
{displayText}
</ThemedTextV2>
<BottomSheetInfoV2
alertInfo={info}
name="info-text"
infoIconStyle={tailwind("text-sm")}
snapPoints={isAffectedVault ? ["50%"] : ["40%"]}
triggerComponent={
<ThemedIcon
size={16}
name="info-outline"
iconType="MaterialIcons"
dark={tailwind("text-mono-dark-v2-500")}
light={tailwind("text-mono-light-v2-500")}
/>
}
/>
</View>
);
}