"use client"; import React, { useEffect, useState, useCallback } from "react"; import { motion } from "motion/react"; import { Link2, Shield, Activity, Hash, Clock, FileText, CheckCircle2, Search, RefreshCw, ExternalLink, Blocks, Copy, Check, AlertCircle, Upload, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { getBlockchainTransactions, getBlockchainStats, verifyDocumentHashOnBlockchain, registerContractOnBlockchain, } from "@/features/blockchain/api/blockchain.action"; import { getContracts } from "@/features/contracts/api/contract.action"; import type { BlockchainTransactionView, BlockchainStats, } from "@/lib/services/blockchain.types"; import { toast } from "sonner"; // ═══════════════════════════════════════════════════════════════ // Blockchain Explorer Page // ═══════════════════════════════════════════════════════════════ export default function BlockchainExplorerPage() { const [transactions, setTransactions] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [verifyHash, setVerifyHash] = useState(""); const [verifyResult, setVerifyResult] = useState<{ exists: boolean; timestamp: number; depositor: string; } | null>(null); const [verifying, setVerifying] = useState(false); const [copiedTx, setCopiedTx] = useState(null); const [unregisteredContracts, setUnregisteredContracts] = useState< Array<{ id: string; title: string | null; fileName: string }> >([]); const [registeringId, setRegisteringId] = useState(null); const loadData = useCallback(async () => { setLoading(true); try { const [txResult, statsResult, contractsResult] = await Promise.all([ getBlockchainTransactions(), getBlockchainStats(), getContracts({ status: "COMPLETED" }), ]); if (txResult.success && txResult.transactions) { setTransactions(txResult.transactions); } if (statsResult.success && statsResult.stats) { setStats(statsResult.stats); } // Find contracts not yet on blockchain if (contractsResult.success && contractsResult.contracts) { const registered = new Set( txResult.transactions?.map((tx) => tx.contractId) ?? [] ); const unregistered = contractsResult.contracts .filter( (c: { id: string; txHash?: string | null; status: string }) => !c.txHash && !registered.has(c.id) && c.status === "COMPLETED" ) .map((c: { id: string; title: string | null; fileName: string }) => ({ id: c.id, title: c.title, fileName: c.fileName, })); setUnregisteredContracts(unregistered); } } catch (error) { console.error("Failed to load blockchain data:", error); } finally { setLoading(false); } }, []); useEffect(() => { loadData(); }, [loadData]); const handleVerify = async () => { if (!verifyHash.trim()) return; setVerifying(true); setVerifyResult(null); try { const result = await verifyDocumentHashOnBlockchain(verifyHash.trim()); if (result.success && result.verification) { setVerifyResult(result.verification); } else { toast.error(result.error || "Verification failed"); } } catch { toast.error("Failed to verify hash"); } finally { setVerifying(false); } }; const handleRegister = async (contractId: string) => { setRegisteringId(contractId); try { const result = await registerContractOnBlockchain(contractId); if (result.success) { toast.success("Contract registered on blockchain!"); await loadData(); } else { toast.error(result.error || "Registration failed"); } } catch { toast.error("Failed to register on blockchain"); } finally { setRegisteringId(null); } }; const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text); setCopiedTx(text); setTimeout(() => setCopiedTx(null), 2000); }; const formatTimestamp = (iso: string) => { return new Date(iso).toLocaleString(); }; const truncateHash = (hash: string, chars = 8) => { if (!hash || hash.length <= chars * 2 + 3) return hash; return `${hash.slice(0, chars + 2)}...${hash.slice(-chars)}`; }; return (
{/* Page Header */}

Blockchain Explorer

View on-chain proofs and verify document integrity

{/* Stats Cards */} } label="Verified Documents" value={stats?.totalVerified?.toString() ?? "0"} color="emerald" /> } label="Latest Block" value={stats?.latestBlockNumber ? `#${stats.latestBlockNumber.toLocaleString()}` : "—"} color="blue" /> } label="Network" value={stats?.networkName ? `${stats.networkName} ${stats.chainId ? `(Chain ${stats.chainId})` : ""}` : "Not Configured"} color={stats?.networkStatus === "connected" ? "emerald" : "red"} badge={stats?.networkStatus === "connected" ? "● Live" : "● Offline"} /> } label="Wallet" value={stats?.walletAddress ? truncateHash(stats.walletAddress, 6) : "—"} color="violet" /> {/* Unregistered contracts */} {unregisteredContracts.length > 0 && (

{unregisteredContracts.length} contract{unregisteredContracts.length > 1 ? "s" : ""} not yet on blockchain

{unregisteredContracts.map((contract) => (
{contract.title || contract.fileName}
))}
)}
{/* Transactions List */}

Transaction History

All documents registered on the blockchain

{loading ? (
Loading transactions...
) : transactions.length === 0 ? (

No transactions yet

Upload and analyze a contract to register it on-chain

) : (
{transactions.map((tx, idx) => (
{tx.contractTitle || tx.contractFileName} {tx.status}
Fingerprint: {truncateHash(tx.documentHash, 12)}
Tx: {truncateHash(tx.txHash, 12)}
Block #{tx.blockNumber.toLocaleString()}
{formatTimestamp(tx.blockTimestamp)}
{tx.network === "sepolia" ? "Sepolia" : "Hardhat"} {tx.explorerUrl && ( Etherscan )}
))}
)}
{/* Verification Panel */}

Verify Document

Check if a document hash exists on-chain