init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
503
webapp/components/product/MintSwapPanel.tsx
Normal file
503
webapp/components/product/MintSwapPanel.tsx
Normal file
@@ -0,0 +1,503 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user