包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
504 lines
22 KiB
TypeScript
504 lines
22 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect, useMemo } from "react";
|
||
import Image from "next/image";
|
||
import { useApp } from "@/contexts/AppContext";
|
||
import { Tabs, Tab, Button } from "@heroui/react";
|
||
import ReviewModal from "@/components/common/ReviewModal";
|
||
import WithdrawModal from "@/components/common/WithdrawModal";
|
||
import { buttonStyles } from "@/lib/buttonStyles";
|
||
import { useAccount, useReadContract } from 'wagmi';
|
||
import { useQuery } from '@tanstack/react-query';
|
||
import { fetchContracts } from '@/lib/api/contracts';
|
||
import { abis } from '@/lib/contracts';
|
||
import { useUSDCBalance, useTokenBalance } from '@/hooks/useBalance';
|
||
import { useDeposit } from '@/hooks/useDeposit';
|
||
import { useWithdraw } from '@/hooks/useWithdraw';
|
||
import { getTxUrl } from '@/lib/contracts';
|
||
import { useTokenBySymbol } from '@/hooks/useTokenBySymbol';
|
||
import { toast } from "sonner";
|
||
import TokenSelector from '@/components/common/TokenSelector';
|
||
import { Token } from '@/lib/api/tokens';
|
||
import { useAppKit } from "@reown/appkit/react";
|
||
|
||
interface MintSwapPanelProps {
|
||
tokenType?: string;
|
||
decimals?: number;
|
||
onVaultRefresh?: () => void;
|
||
}
|
||
|
||
export default function MintSwapPanel({ tokenType = 'YT-A', decimals, onVaultRefresh }: MintSwapPanelProps) {
|
||
const { t } = useApp();
|
||
const [activeAction, setActiveAction] = useState<"deposit" | "withdraw">("deposit");
|
||
const [amount, setAmount] = useState<string>("");
|
||
const [selectedToken, setSelectedToken] = useState<Token | undefined>();
|
||
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
|
||
const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false);
|
||
const [mounted, setMounted] = useState(false);
|
||
|
||
// 避免 hydration 错误
|
||
useEffect(() => {
|
||
setMounted(true);
|
||
}, []);
|
||
|
||
// Web3 集成
|
||
const { isConnected } = useAccount();
|
||
const { open } = useAppKit();
|
||
const { formattedBalance: usdcBalance, isLoading: isBalanceLoading, refetch: refetchBalance } = useUSDCBalance();
|
||
const ytToken = useTokenBySymbol(tokenType ?? '');
|
||
const { formattedBalance: ytBalance, refetch: refetchYT } = useTokenBalance(
|
||
ytToken?.contractAddress,
|
||
ytToken?.decimals ?? decimals ?? 18
|
||
);
|
||
const {
|
||
status: depositStatus,
|
||
error: depositError,
|
||
isLoading: isDepositLoading,
|
||
approveHash,
|
||
depositHash,
|
||
executeApproveAndDeposit,
|
||
reset: resetDeposit,
|
||
} = useDeposit(ytToken);
|
||
|
||
const {
|
||
status: withdrawStatus,
|
||
error: withdrawError,
|
||
isLoading: isWithdrawLoading,
|
||
approveHash: withdrawApproveHash,
|
||
withdrawHash,
|
||
executeApproveAndWithdraw,
|
||
reset: resetWithdraw,
|
||
} = useWithdraw(ytToken);
|
||
|
||
// 从合约读取 nextRedemptionTime,复用 contract-registry 缓存
|
||
const { data: contractConfigs = [] } = useQuery({
|
||
queryKey: ['contract-registry'],
|
||
queryFn: fetchContracts,
|
||
staleTime: 5 * 60 * 1000,
|
||
});
|
||
const vaultChainId = ytToken?.chainId ?? 97;
|
||
const factoryAddress = useMemo(() => {
|
||
const c = contractConfigs.find(c => c.name === 'YTAssetFactory' && c.chain_id === vaultChainId);
|
||
return c?.address as `0x${string}` | undefined;
|
||
}, [contractConfigs, vaultChainId]);
|
||
|
||
const priceFeedAddress = useMemo(() => {
|
||
const c = contractConfigs.find(c => c.name === 'YTPriceFeed' && c.chain_id === vaultChainId);
|
||
return c?.address as `0x${string}` | undefined;
|
||
}, [contractConfigs, vaultChainId]);
|
||
|
||
// Read selected token price from YTPriceFeed (30 dec precision)
|
||
const { data: tokenPriceRaw } = useReadContract({
|
||
address: priceFeedAddress,
|
||
abi: abis.YTPriceFeed as any,
|
||
functionName: 'getPrice',
|
||
args: selectedToken?.contractAddress
|
||
? [selectedToken.contractAddress as `0x${string}`, false]
|
||
: undefined,
|
||
chainId: vaultChainId,
|
||
query: { enabled: !!priceFeedAddress && !!selectedToken?.contractAddress },
|
||
});
|
||
const selectedTokenUSDPrice = tokenPriceRaw && (tokenPriceRaw as bigint) > 0n
|
||
? Number(tokenPriceRaw as bigint) / 1e30
|
||
: null; // null = price not yet loaded
|
||
|
||
const { data: vaultInfo, isLoading: isVaultInfoLoading } = useReadContract({
|
||
address: factoryAddress,
|
||
abi: abis.YTAssetFactory as any,
|
||
functionName: 'getVaultInfo',
|
||
args: ytToken?.contractAddress ? [ytToken.contractAddress as `0x${string}`] : undefined,
|
||
chainId: vaultChainId,
|
||
query: { enabled: !!factoryAddress && !!ytToken?.contractAddress },
|
||
});
|
||
|
||
// 超时 3 秒后不再等合约数据
|
||
const [vaultInfoTimedOut, setVaultInfoTimedOut] = useState(false);
|
||
useEffect(() => {
|
||
if (!factoryAddress || !ytToken?.contractAddress) return;
|
||
setVaultInfoTimedOut(false);
|
||
const t = setTimeout(() => setVaultInfoTimedOut(true), 3000);
|
||
return () => clearTimeout(t);
|
||
}, [factoryAddress, ytToken?.contractAddress]);
|
||
|
||
// nextRedemptionTime: getVaultInfo 返回值 index[8]
|
||
const nextRedemptionTime = vaultInfo ? Number((vaultInfo as any[])[8]) : 0;
|
||
const isMatured = nextRedemptionTime > 0 && nextRedemptionTime * 1000 < Date.now();
|
||
|
||
// isFull: totalSupply[4] >= hardCap[5]
|
||
const vaultTotalSupply: bigint = vaultInfo ? ((vaultInfo as any[])[4] as bigint) ?? 0n : 0n;
|
||
const vaultHardCap: bigint = vaultInfo ? ((vaultInfo as any[])[5] as bigint) ?? 0n : 0n;
|
||
const isFull = vaultHardCap > 0n && vaultTotalSupply >= vaultHardCap;
|
||
const maturityDateStr = nextRedemptionTime > 0
|
||
? new Date(nextRedemptionTime * 1000).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
|
||
: '';
|
||
|
||
// ytPrice: getVaultInfo 返回值 index[7],30 位小数
|
||
const ytPriceRaw: bigint = vaultInfo ? ((vaultInfo as any[])[7] as bigint) ?? 0n : 0n;
|
||
const ytPriceDisplay = (() => {
|
||
if (!ytPriceRaw || ytPriceRaw <= 0n) return '--';
|
||
const divisor = 10n ** 30n;
|
||
const intPart = ytPriceRaw / divisor;
|
||
const fracScaled = ((ytPriceRaw % divisor) * 1_000_000n) / divisor;
|
||
return `$${intPart}.${fracScaled.toString().padStart(6, '0')}`;
|
||
})();
|
||
const usdcToken = useTokenBySymbol('USDC');
|
||
const usdcDecimals = usdcToken?.onChainDecimals ?? usdcToken?.decimals ?? 18;
|
||
const ytDecimals = ytToken?.onChainDecimals ?? ytToken?.decimals ?? decimals ?? 18;
|
||
const inputDecimals = activeAction === 'deposit' ? usdcDecimals : ytDecimals;
|
||
const displayDecimals = Math.min(inputDecimals, 6);
|
||
|
||
const truncateDecimals = (s: string, d: number) => { const p = s.split('.'); return p.length > 1 ? `${p[0]}.${p[1].slice(0, d)}` : s; };
|
||
const isValidAmount = (v: string) => v !== '' && !isNaN(parseFloat(v)) && parseFloat(v) > 0;
|
||
|
||
// ytPrice as float for You Get calculation (30 dec)
|
||
const ytPrice = ytPriceRaw > 0n ? Number(ytPriceRaw) / 1e30 : 0;
|
||
// You Get = selectedTokenUSDPrice × amount / ytPrice
|
||
const depositYouGet = ytPrice > 0 && selectedTokenUSDPrice !== null && isValidAmount(amount)
|
||
? (selectedTokenUSDPrice * parseFloat(amount)) / ytPrice
|
||
: null;
|
||
|
||
const handleAmountChange = (value: string) => {
|
||
if (value === '') { setAmount(value); return; }
|
||
if (!/^\d*\.?\d*$/.test(value)) return;
|
||
const parts = value.split('.');
|
||
if (parts.length > 1 && parts[1].length > displayDecimals) return;
|
||
const maxBalance = activeAction === 'deposit' ? parseFloat(usdcBalance) : parseFloat(ytBalance);
|
||
if (parseFloat(value) > maxBalance) {
|
||
setAmount(truncateDecimals(activeAction === 'deposit' ? usdcBalance : ytBalance, displayDecimals));
|
||
return;
|
||
}
|
||
setAmount(value);
|
||
};
|
||
|
||
const DEPOSIT_TOAST_ID = 'mint-deposit-tx';
|
||
const WITHDRAW_TOAST_ID = 'mint-withdraw-tx';
|
||
|
||
// Approve 交易提交 toast
|
||
useEffect(() => {
|
||
if (approveHash) {
|
||
toast.loading(t("mintSwap.toast.approvalSubmitted"), {
|
||
id: DEPOSIT_TOAST_ID,
|
||
description: t("mintSwap.toast.waitingConfirmation"),
|
||
action: { label: t("mintSwap.toast.viewTx"), onClick: () => window.open(getTxUrl(approveHash), '_blank') },
|
||
});
|
||
}
|
||
}, [approveHash]);
|
||
|
||
useEffect(() => {
|
||
if (withdrawApproveHash) {
|
||
toast.loading(t("mintSwap.toast.approvalSubmitted"), {
|
||
id: WITHDRAW_TOAST_ID,
|
||
description: t("mintSwap.toast.waitingConfirmation"),
|
||
action: { label: t("mintSwap.toast.viewTx"), onClick: () => window.open(getTxUrl(withdrawApproveHash), '_blank') },
|
||
});
|
||
}
|
||
}, [withdrawApproveHash]);
|
||
|
||
// 主交易提交 toast
|
||
useEffect(() => {
|
||
if (depositHash && depositStatus === 'depositing') {
|
||
toast.loading(t("mintSwap.toast.depositSubmitted"), {
|
||
id: DEPOSIT_TOAST_ID,
|
||
description: t("mintSwap.toast.waitingConfirmation"),
|
||
action: { label: t("mintSwap.toast.viewTx"), onClick: () => window.open(getTxUrl(depositHash), '_blank') },
|
||
});
|
||
}
|
||
}, [depositHash, depositStatus]);
|
||
|
||
useEffect(() => {
|
||
if (withdrawHash && withdrawStatus === 'withdrawing') {
|
||
toast.loading(t("mintSwap.toast.withdrawSubmitted"), {
|
||
id: WITHDRAW_TOAST_ID,
|
||
description: t("mintSwap.toast.waitingConfirmation"),
|
||
action: { label: t("mintSwap.toast.viewTx"), onClick: () => window.open(getTxUrl(withdrawHash), '_blank') },
|
||
});
|
||
}
|
||
}, [withdrawHash, withdrawStatus]);
|
||
|
||
// 存款成功后刷新余额
|
||
useEffect(() => {
|
||
if (depositStatus === 'success') {
|
||
toast.success(t("mintSwap.toast.depositSuccess"), {
|
||
id: DEPOSIT_TOAST_ID,
|
||
description: t("mintSwap.toast.depositSuccessDesc"),
|
||
duration: 5000,
|
||
});
|
||
refetchBalance();
|
||
refetchYT();
|
||
onVaultRefresh?.();
|
||
const timer1 = setTimeout(() => { refetchBalance(); refetchYT(); onVaultRefresh?.(); }, 3000);
|
||
const timer2 = setTimeout(() => { resetDeposit(); setAmount(""); }, 6000);
|
||
return () => { clearTimeout(timer1); clearTimeout(timer2); };
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [depositStatus]);
|
||
|
||
// 取款成功后刷新余额
|
||
useEffect(() => {
|
||
if (withdrawStatus === 'success') {
|
||
toast.success(t("mintSwap.toast.withdrawSuccess"), {
|
||
id: WITHDRAW_TOAST_ID,
|
||
description: t("mintSwap.toast.withdrawSuccessDesc"),
|
||
duration: 5000,
|
||
});
|
||
refetchBalance();
|
||
refetchYT();
|
||
onVaultRefresh?.();
|
||
const timer1 = setTimeout(() => { refetchBalance(); refetchYT(); onVaultRefresh?.(); }, 3000);
|
||
const timer2 = setTimeout(() => { resetWithdraw(); setAmount(""); }, 6000);
|
||
return () => { clearTimeout(timer1); clearTimeout(timer2); };
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [withdrawStatus]);
|
||
|
||
// 错误 toast
|
||
useEffect(() => {
|
||
if (depositError) {
|
||
if (depositError === 'Transaction cancelled') {
|
||
toast.dismiss(DEPOSIT_TOAST_ID);
|
||
} else {
|
||
toast.error(t("mintSwap.toast.depositFailed"), { id: DEPOSIT_TOAST_ID, duration: 5000 });
|
||
}
|
||
}
|
||
}, [depositError]);
|
||
|
||
useEffect(() => {
|
||
if (withdrawError) {
|
||
if (withdrawError === 'Transaction cancelled') {
|
||
toast.dismiss(WITHDRAW_TOAST_ID);
|
||
} else {
|
||
toast.error(t("mintSwap.toast.withdrawFailed"), { id: WITHDRAW_TOAST_ID, duration: 5000 });
|
||
}
|
||
}
|
||
}, [withdrawError]);
|
||
|
||
return (
|
||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 flex flex-col overflow-hidden">
|
||
{/* Content */}
|
||
<div className="flex flex-col gap-4 md:gap-6 p-4 md:p-6">
|
||
{/* Deposit/Withdraw Toggle */}
|
||
<Tabs
|
||
selectedKey={activeAction}
|
||
onSelectionChange={(key) => {
|
||
const next = key as "deposit" | "withdraw";
|
||
setActiveAction(next);
|
||
setAmount("");
|
||
if (next === "deposit") { resetWithdraw(); } else { resetDeposit(); }
|
||
}}
|
||
variant="solid"
|
||
classNames={{
|
||
base: "w-full",
|
||
tabList: "bg-[#f9fafb] dark:bg-gray-700 rounded-xl p-1 gap-0 w-full",
|
||
cursor: "bg-bg-surface dark:bg-gray-600 shadow-sm",
|
||
tab: "h-8 px-4",
|
||
tabContent: "text-body-small font-medium text-text-tertiary dark:text-gray-400 group-data-[selected=true]:font-bold group-data-[selected=true]:text-text-primary dark:group-data-[selected=true]:text-white",
|
||
}}
|
||
>
|
||
<Tab key="deposit" title={t("mintSwap.deposit")} />
|
||
<Tab key="withdraw" title={t("mintSwap.withdraw")} />
|
||
</Tabs>
|
||
|
||
{/* Input Area */}
|
||
<div className="flex flex-col gap-2">
|
||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-3">
|
||
{/* Label and Balance */}
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
|
||
{activeAction === 'deposit' ? t("mintSwap.deposit") : t("mintSwap.withdraw")}
|
||
</span>
|
||
<div className="flex items-center gap-1">
|
||
<Image src="/components/common/icon0.svg" alt="" width={12} height={12} />
|
||
<span className="text-caption-tiny font-medium text-[#4b5563] dark:text-gray-400">
|
||
{t("mintSwap.balance")}: {!mounted ? '0' : (isBalanceLoading ? '...' : activeAction === 'deposit'
|
||
? `$${parseFloat(truncateDecimals(usdcBalance, displayDecimals)).toLocaleString('en-US', { maximumFractionDigits: displayDecimals })}`
|
||
: `${parseFloat(truncateDecimals(ytBalance, displayDecimals)).toLocaleString('en-US', { maximumFractionDigits: displayDecimals })} ${tokenType}`)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Input Row */}
|
||
<div className="flex items-center justify-between gap-2">
|
||
<div className="flex flex-col items-start flex-1">
|
||
<input
|
||
type="text" inputMode="decimal"
|
||
placeholder="0.00"
|
||
value={amount}
|
||
onChange={(e) => handleAmountChange(e.target.value)}
|
||
className="w-full text-left text-heading-h3 font-bold text-text-primary dark:text-white placeholder:text-[#d1d5db] dark:placeholder:text-gray-500 bg-transparent border-none outline-none"
|
||
/>
|
||
<span className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400">
|
||
{amount ? (activeAction === 'deposit' ? `≈ $${amount}` : `≈ $${amount} USDC`) : "--"}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Button
|
||
size="sm"
|
||
className={buttonStyles({ intent: "max" })}
|
||
onPress={() => setAmount(truncateDecimals(activeAction === 'deposit' ? usdcBalance : ytBalance, displayDecimals))}
|
||
>
|
||
{t("mintSwap.max")}
|
||
</Button>
|
||
{activeAction === 'deposit' ? (
|
||
<TokenSelector
|
||
selectedToken={selectedToken}
|
||
onSelect={setSelectedToken}
|
||
filterTypes={['stablecoin']}
|
||
/>
|
||
) : (
|
||
<div className="bg-white dark:bg-gray-700 rounded-full px-4 h-[46px] flex items-center gap-2">
|
||
<Image
|
||
src={ytToken?.iconUrl || '/assets/tokens/default.svg'}
|
||
alt={tokenType}
|
||
width={32}
|
||
height={32}
|
||
className="rounded-full"
|
||
onError={(e) => { (e.target as HTMLImageElement).src = '/assets/tokens/default.svg'; }}
|
||
/>
|
||
<span className="text-body-default font-bold text-text-primary dark:text-white">{tokenType}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Your YT Balance */}
|
||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-2">
|
||
<span className="text-body-small font-bold text-[#4b5563] dark:text-gray-300">
|
||
{t("mintSwap.yourTokenBalance").replace('{token}', tokenType)}
|
||
</span>
|
||
<div className="flex items-center justify-between gap-2">
|
||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 shrink-0">
|
||
{t("mintSwap.currentBalance")}
|
||
</span>
|
||
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400 text-right min-w-0 truncate">
|
||
{!mounted ? '0' : `${parseFloat(ytBalance).toLocaleString('en-US', { maximumFractionDigits: 6 })} ${tokenType}`}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center justify-between gap-2">
|
||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 shrink-0">
|
||
{t("mintSwap.valueUsdc")}
|
||
</span>
|
||
<span className="text-body-small font-bold text-text-primary dark:text-white whitespace-nowrap">
|
||
{!mounted ? '$0' : `≈ $${parseFloat(ytBalance).toLocaleString('en-US', { maximumFractionDigits: 6 })}`}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Transaction Summary */}
|
||
<div className="bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 p-4 flex flex-col gap-2">
|
||
<span className="text-body-small font-bold text-[#4b5563] dark:text-gray-300">
|
||
{t("mintSwap.transactionSummary")}
|
||
</span>
|
||
<div className="flex items-center justify-between gap-2">
|
||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 shrink-0">
|
||
{t("mintSwap.youGet")}
|
||
</span>
|
||
<span className="text-body-small font-bold text-[#10b981] dark:text-green-400 text-right min-w-0 truncate">
|
||
{isValidAmount(amount)
|
||
? activeAction === 'deposit'
|
||
? depositYouGet !== null
|
||
? `≈ ${depositYouGet.toLocaleString('en-US', { maximumFractionDigits: 6 })} ${tokenType}`
|
||
: (!selectedToken ? '--' : '...')
|
||
: `≈ $${parseFloat(amount).toLocaleString('en-US', { maximumFractionDigits: 6 })} USDC`
|
||
: '--'}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center justify-between gap-2">
|
||
<span className="text-body-small font-regular text-text-tertiary dark:text-gray-400 shrink-0">
|
||
{t("mintSwap.salesPrice")}
|
||
</span>
|
||
<span className="text-body-small font-bold text-text-primary dark:text-white whitespace-nowrap">
|
||
{isVaultInfoLoading && !vaultInfoTimedOut ? '...' : ytPriceDisplay}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Submit Button */}
|
||
<Button
|
||
isDisabled={isConnected && (
|
||
!isValidAmount(amount) ||
|
||
isDepositLoading ||
|
||
isWithdrawLoading ||
|
||
((isMatured || isFull) && activeAction === 'deposit') ||
|
||
(!isMatured && activeAction === 'withdraw')
|
||
)}
|
||
color="default"
|
||
variant="solid"
|
||
className={buttonStyles({ intent: "theme" })}
|
||
endContent={<Image src="/components/common/icon11.svg" alt="" width={20} height={20} />}
|
||
onPress={() => {
|
||
if (!isConnected) { open(); return; }
|
||
if (amount && parseFloat(amount) > 0) {
|
||
if (activeAction === "deposit") {
|
||
executeApproveAndDeposit(amount);
|
||
} else {
|
||
executeApproveAndWithdraw(amount);
|
||
}
|
||
}
|
||
}}
|
||
>
|
||
{!isConnected
|
||
? t("common.connectWallet")
|
||
: isFull && activeAction === 'deposit'
|
||
? t("mintSwap.poolFull")
|
||
: isMatured && activeAction === 'deposit'
|
||
? t("mintSwap.productMatured").replace("{date}", maturityDateStr)
|
||
: activeAction === 'deposit'
|
||
? depositStatus === 'approving' ? t("common.approving")
|
||
: depositStatus === 'approved' ? t("mintSwap.approvedDepositing")
|
||
: depositStatus === 'depositing' ? t("mintSwap.depositing")
|
||
: depositStatus === 'success' ? t("common.success")
|
||
: depositStatus === 'error' ? t("common.failed")
|
||
: !!amount && !isValidAmount(amount) ? t("common.invalidAmount")
|
||
: t("mintSwap.approveDeposit")
|
||
: !isMatured
|
||
? nextRedemptionTime > 0
|
||
? t("mintSwap.withdrawNotMatured").replace("{date}", maturityDateStr)
|
||
: t("mintSwap.withdrawNoMaturity")
|
||
: withdrawStatus === 'approving' ? t("common.approving")
|
||
: withdrawStatus === 'approved' ? t("mintSwap.approvedWithdrawing")
|
||
: withdrawStatus === 'withdrawing' ? t("mintSwap.withdrawing")
|
||
: withdrawStatus === 'success' ? t("common.success")
|
||
: withdrawStatus === 'error' ? t("common.failed")
|
||
: !!amount && !isValidAmount(amount) ? t("common.invalidAmount")
|
||
: t("mintSwap.approveWithdraw")
|
||
}
|
||
</Button>
|
||
|
||
{/* Review Modal for Deposit */}
|
||
<ReviewModal
|
||
isOpen={isReviewModalOpen}
|
||
onClose={() => setIsReviewModalOpen(false)}
|
||
amount={amount}
|
||
/>
|
||
|
||
{/* Withdraw Modal */}
|
||
<WithdrawModal
|
||
isOpen={isWithdrawModalOpen}
|
||
onClose={() => setIsWithdrawModalOpen(false)}
|
||
amount={amount}
|
||
/>
|
||
|
||
{/* Terms */}
|
||
<div className="flex flex-col gap-0 text-center">
|
||
<div className="text-caption-tiny font-regular">
|
||
<span className="text-[#9ca1af] dark:text-gray-400">
|
||
{t("mintSwap.termsText")}{" "}
|
||
</span>
|
||
<span className="text-[#10b981] dark:text-green-400">
|
||
{t("mintSwap.termsOfService")}
|
||
</span>
|
||
<span className="text-[#9ca1af] dark:text-gray-400">
|
||
{" "}{t("mintSwap.and")}
|
||
</span>
|
||
</div>
|
||
<span className="text-caption-tiny font-regular text-[#10b981] dark:text-green-400">
|
||
{t("mintSwap.privacyPolicy")}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|