ghost-dao-interface/src/containers/Bond/components/BondInputArea.jsx
Uncle Fatso ff9ca1bb79
make max button works during the bond purchase
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-04-28 13:49:24 +03:00

238 lines
12 KiB
JavaScript

import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
import { Box, Checkbox, FormControlLabel, useMediaQuery } from "@mui/material";
import { useState, useMemo, useCallback } from "react";
import { useLocation } from "react-router-dom";
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
import { shorten, formatNumber, formatCurrency } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import BondDiscount from "./BondDiscount";
import BondVesting from "./BondVesting";
import BondConfirmModal from "./BondConfirmModal";
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { PrimaryButton } from "../../../components/Button";
import SwapCard from "../../../components/Swap/SwapCard";
import SwapCollection from "../../../components/Swap/SwapCollection";
import TokenStack from "../../../components/TokenStack/TokenStack";
import DataRow from "../../../components/DataRow/DataRow";
import Paper from "../../../components/Paper/Paper";
import { useCurrentIndex } from "../../../hooks/staking";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
const BondInputArea = ({
bond,
chainId,
slippage,
recipientAddress,
formatDecimals,
handleSettingsOpen,
address,
preparedQuoteToken,
connect
}) => {
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const { pathname } = useLocation();
const { currentIndex } = useCurrentIndex(chainId);
const { balance, refetch } = useBalance(chainId, preparedQuoteToken.address, address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const [amount, setAmount] = useState("");
const [checked, setChecked] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const parsedAmount = useMemo(() => {
return new DecimalBigNumber(amount, bond.quoteToken.decimals);
}, [bond, amount])
const amountInBaseToken = useMemo(() => {
if (bond.price.inBaseToken._value !== 0n) return parsedAmount.div(bond.price.inBaseToken);
return new DecimalBigNumber(0n, 0);
}, [parsedAmount]);
const showDisclaimer = useMemo(() => {
return new DecimalBigNumber("0").gt(bond.discount);
}, [bond]);
const handleConfirmClose = () => {
setAmount("");
setConfirmOpen(false);
refetch();
}
const setMax = useCallback(() => {
if (!balance) return;
if (bond.capacity.inQuoteToken.lt(bond.maxPayout.inQuoteToken)) {
return setAmount(
bond.capacity.inQuoteToken.lt(balance)
? bond.capacity.inQuoteToken.toString() // Capacity is the smallest
: balance.toString(),
);
}
setAmount(
bond.maxPayout.inQuoteToken.lt(balance)
? bond.maxPayout.inQuoteToken.toString() // Payout is the smallest
: balance.toString(),
);
}, [bond, balance]);
const baseTokenString = useMemo(() => (bond.maxPayout.inBaseToken.lt(bond.capacity.inBaseToken)
? bond.maxPayout.inBaseToken
: bond.capacity.inBaseToken
), [bond]);
return (
<Box minHeight="calc(100vh - 210px)" display="flex" flexDirection="column" justifyContent="center">
<Box display="flex" flexDirection="row" width="100%" justifyContent="center" mt="10px">
<Box display="flex" flexDirection="column" width="100%" maxWidth="476px">
<Box mb="21px">
<SwapCollection
UpperSwapCard={
<SwapCard
maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
id="from"
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={preparedQuoteToken.name}
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
endString="Max"
endStringOnClick={setMax}
value={amount}
onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
/>}
LowerSwapCard={
<SwapCard
maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
id="to"
token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={bond.baseToken.name}
value={amountInBaseToken.toString({ decimals: 9 })}
inputProps={{ "data-testid": "toInput" }}
/>
}
/>
</Box>
<TokenAllowanceGuard
spendAmount={parsedAmount}
tokenName={bond.quoteToken.quoteTokenAddress}
owner={address}
spender={BOND_DEPOSITORY_ADDRESSES[chainId]}
decimals={parsedAmount._decimals}
approvalText={`Approve ${bond.quoteToken.name} to Bond`}
approvalPendingText={"Approving..."}
connect={connect}
width="100%"
height="60px"
isNative={preparedQuoteToken.address === undefined}
isVertical
message={
<>
First time bonding <b>{bond.quoteToken.name}</b>? <br /> Please approve ghostDAO to use your{" "}
<b>{bond.quoteToken.name}</b> for bonding.
</>
}
>
{showDisclaimer && (
<FormControlLabel
control={
<Checkbox
checked={checked}
onChange={event => setChecked(event.target.checked)}
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
/>
}
label="I understand that I'm buying a negative discount bond"
/>
)}
<PrimaryButton
fullWidth
disabled={bond.isSoldOut || (showDisclaimer && !checked)}
onClick={() => setConfirmOpen(true)}
>
Bond
</PrimaryButton>
</TokenAllowanceGuard>
<Paper style={{ marginBottom: "7px", marginTop: "21px" }} fullWidth enableBackground>
<Box mt="24px">
<DataRow
title={"You Will Get"}
balance={
<span>
{formatCurrency(amountInBaseToken, formatDecimals, ftsoSymbol)}
{" "}
{!!currentIndex && (
<span>
({formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, ghstSymbol)})
</span>
)}
</span>
}
tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`}
/>
<DataRow
title="Max You Can Buy"
tooltip={`Maximum ${ftsoSymbol} that can be purchased in a single transaction. Prevents whales from buying entire bond supply at once.`}
balance={
<span>
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
? `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)}`
: `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)} (≈${formatCurrency(baseTokenString.div(currentIndex), formatDecimals, ghstSymbol)})`}
</span>
}
/>
<DataRow
title="Discount"
balance={<BondDiscount discount={bond.discount} textOnly />}
tooltip={`The discount (or premium) between ${ftsoSymbol} market price and bond price. Higher discount = better deal for bond buyers.`}
/>
<DataRow
title={`Vesting Term`}
balance={<BondVesting vesting={bond.vesting} />}
tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."}
/>
{recipientAddress !== address && (
<DataRow title={`Recipient`} balance={shorten(recipientAddress)} />
)}
</Box>
</Paper>
</Box>
</Box>
<BondConfirmModal
chainId={chainId}
bond={bond}
slippage={slippage}
recipientAddress={recipientAddress}
spendAmountValue={parsedAmount}
spendAmount={formatNumber(parsedAmount, formatDecimals)}
receiveAmount={formatNumber(amountInBaseToken, formatDecimals)}
bondQuoteTokenName={preparedQuoteToken.name}
bondQuoteTokenIcons={preparedQuoteToken.icons}
isNative={preparedQuoteToken.address === undefined}
handleSettingsOpen={handleSettingsOpen}
isOpen={confirmOpen}
sender={address}
handleConfirmClose={() => handleConfirmClose()}
/>
</Box>
);
};
export default BondInputArea;