"use client" import { useState, useEffect } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { ChevronDown, ChevronUp, CheckCircle, AlertCircle, Lock, Zap, Copy, CheckCheck, Shield, Activity, } from "lucide-react" export interface Block { index: number prev_hash: string timestamp: number encrypted_vote: string transaction_id: string block_hash: string signature: string } export interface BlockchainData { blocks: Block[] verification: { chain_valid: boolean total_blocks: number total_votes: number } } interface BlockchainVisualizerProps { data: BlockchainData isLoading?: boolean isVerifying?: boolean onVerify?: () => void } export function BlockchainVisualizer({ data, isLoading = false, isVerifying = false, onVerify, }: BlockchainVisualizerProps) { const [expandedBlocks, setExpandedBlocks] = useState([]) const [copiedHash, setCopiedHash] = useState(null) const [animatingBlocks, setAnimatingBlocks] = useState([]) // Validate data parameter - must be after hooks const isValidData = data && Array.isArray(data.blocks) && data.verification && typeof data.verification.total_blocks === "number" && typeof data.verification.total_votes === "number" // Animate blocks on load useEffect(() => { if (!isValidData || !data?.blocks || data.blocks.length === 0) return data.blocks.forEach((_, index) => { setTimeout(() => { setAnimatingBlocks((prev) => [...prev, index]) }, index * 100) }) }, [data?.blocks, isValidData]) const toggleBlockExpand = (index: number) => { setExpandedBlocks((prev) => prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index] ) } const copyToClipboard = (text: string, hashType: string) => { navigator.clipboard.writeText(text) setCopiedHash(hashType) setTimeout(() => setCopiedHash(null), 2000) } const truncateHash = (hash: string, length: number = 16) => { if (!hash || typeof hash !== "string") { console.error(`truncateHash: invalid hash parameter: ${typeof hash}, value: ${hash}`) return "N/A" } return hash.length > length ? `${hash.slice(0, length)}...` : hash } const formatTimestamp = (timestamp: number) => { return new Date(timestamp * 1000).toLocaleString("fr-FR") } if (isLoading) { return (

Chargement de la blockchain...

) } // Validate data after hooks if (!isValidData) { return ( Erreur Format blockchain invalide ou données non disponibles ) } return (
{/* Stats Dashboard */}
{/* Total Blocks */}

Blocs

{data.verification.total_blocks}

{/* Total Votes */}

Votes

{data.verification.total_votes}

{/* Chain Status */}

Statut

{data.verification.chain_valid ? "✓ Valide" : "✗ Invalide"}

{data.verification.chain_valid ? ( ) : ( )}
{/* Security Score */}

Sécurité

100%

{/* Verification Button */} {onVerify && ( )} {/* Blockchain Visualization */} Chaîne de Blocs
{data && Array.isArray(data.blocks) && data.blocks.map((block, index) => { const isAnimating = animatingBlocks.includes(index) const isExpanded = expandedBlocks.includes(index) return (
{/* Block Card */} {/* Expanded Details */} {isExpanded && (
{/* Block Index */}
{block.index}
{/* Timestamp */}
{formatTimestamp(block.timestamp)}
{/* Previous Hash */}
{block.prev_hash}
{/* Block Hash */}
{block.block_hash}
{/* Encrypted Vote */} {block.encrypted_vote && (
{truncateHash(block.encrypted_vote, 60)}
)} {/* Transaction ID */}
{block.transaction_id}
{/* Signature */} {block.signature && (
{truncateHash(block.signature, 60)}
)}
{/* Verification Status */}
Hash valide
{block.index > 0 && (
Chaîne liée
)}
)} {/* Chain Link Indicator */} {data && Array.isArray(data.blocks) && index < data.blocks.length - 1 && (
)}
) })}
{/* Security Info Panel */} Information de Sécurité

Immuabilité: Chaque bloc contient le hash du bloc précédent. Toute modification invalide toute la chaîne.

Transparence: Tous les votes sont enregistrés et vérifiables sans révéler le contenu du vote.

Chiffrement: Les votes sont chiffrés avec ElGamal. Seul le dépouillement utilise les clés privées.

) }