"use client"; import { useState, useEffect, useMemo } from "react"; import Image from "next/image"; import { toast } from "sonner"; import { useApp } from "@/contexts/AppContext"; import { useQuery } from "@tanstack/react-query"; import { useReadContract } from "wagmi"; import { fetchContracts } from "@/lib/api/contracts"; import { abis } from "@/lib/contracts"; import { ProductDetail } from "@/lib/api/fundmarket"; interface ProductHeaderProps { product: ProductDetail; } export default function ProductHeader({ product }: ProductHeaderProps) { const { t } = useApp(); const [copied, setCopied] = useState(false); // 从合约读取状态 const { data: contractConfigs = [] } = useQuery({ queryKey: ['contract-registry'], queryFn: fetchContracts, staleTime: 5 * 60 * 1000, }); const factoryAddress = useMemo(() => { const c = contractConfigs.find(c => c.name === 'YTAssetFactory' && c.chain_id === product.chainId); return c?.address as `0x${string}` | undefined; }, [contractConfigs, product.chainId]); const { data: vaultInfo, isLoading: isVaultLoading } = useReadContract({ address: factoryAddress, abi: abis.YTAssetFactory as any, functionName: 'getVaultInfo', args: product.contractAddress ? [product.contractAddress as `0x${string}`] : undefined, chainId: product.chainId, query: { enabled: !!factoryAddress && !!product.contractAddress }, }); const [vaultTimedOut, setVaultTimedOut] = useState(false); useEffect(() => { if (!factoryAddress || !product.contractAddress) return; setVaultTimedOut(false); const timer = setTimeout(() => setVaultTimedOut(true), 3000); return () => clearTimeout(timer); }, [factoryAddress, product.contractAddress]); const totalSupply: bigint = vaultInfo ? ((vaultInfo as any[])[4] as bigint) ?? 0n : 0n; const hardCap: bigint = vaultInfo ? ((vaultInfo as any[])[5] as bigint) ?? 0n : 0n; const nextRedemptionTime = vaultInfo ? Number((vaultInfo as any[])[8]) : 0; const isMatured = nextRedemptionTime > 0 && nextRedemptionTime * 1000 < Date.now(); const isFull = hardCap > 0n && totalSupply >= hardCap; type StatusCfg = { label: string; dot: string; bg: string; border: string; text: string }; const statusCfg: StatusCfg | null = (() => { if (isVaultLoading && !vaultTimedOut) return null; if (isMatured) return { label: t("product.statusEnded"), dot: "bg-gray-400", bg: "bg-gray-100 dark:bg-gray-700", border: "border-gray-300 dark:border-gray-500", text: "text-gray-500 dark:text-gray-400" }; if (isFull) return { label: t("product.statusFull"), dot: "bg-orange-500", bg: "bg-orange-50 dark:bg-orange-900/30", border: "border-orange-200 dark:border-orange-700", text: "text-orange-600 dark:text-orange-400" }; return { label: t("product.statusActive"), dot: "bg-green-500", bg: "bg-green-50 dark:bg-green-900/30", border: "border-green-200 dark:border-green-700", text: "text-green-600 dark:text-green-400" }; })(); const shortenAddress = (address: string) => { if (!address || address.length < 10) return address; return `${address.slice(0, 6)}...${address.slice(-4)}`; }; const contractAddress = product.contractAddress || ''; const handleCopy = async () => { if (!contractAddress) return; try { if (navigator.clipboard) { await navigator.clipboard.writeText(contractAddress); } else { // fallback for insecure context (HTTP) const textarea = document.createElement('textarea'); textarea.value = contractAddress; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); } setCopied(true); toast.success(t("product.addressCopied") || "Contract address copied!"); setTimeout(() => setCopied(false), 2000); } catch { toast.error(t("product.copyFailed") || "Failed to copy address"); } }; return (
{/* Product Title Section */}
{product.name}

{product.name}

{statusCfg && (
{statusCfg.label}
)}

{product.subtitle}

); }