diff --git a/e-voting-system/frontend/app/dashboard/blockchain/page.tsx b/e-voting-system/frontend/app/dashboard/blockchain/page.tsx index ec6867c..c33a5ac 100644 --- a/e-voting-system/frontend/app/dashboard/blockchain/page.tsx +++ b/e-voting-system/frontend/app/dashboard/blockchain/page.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react" import Link from "next/link" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { BlockchainViewer, BlockchainData } from "@/components/blockchain-viewer" +import { BlockchainVisualizer, BlockchainData } from "@/components/blockchain-visualizer" import { ArrowLeft, RefreshCw } from "lucide-react" export default function BlockchainPage() { @@ -238,10 +238,10 @@ export default function BlockchainPage() { )} - {/* Blockchain Viewer */} + {/* Blockchain Visualizer */} {blockchainData && selectedElection && ( <> - void +} + +export function BlockchainVisualizer({ + data, + isLoading = false, + isVerifying = false, + onVerify, +}: BlockchainVisualizerProps) { + const [expandedBlocks, setExpandedBlocks] = useState([]) + const [copiedHash, setCopiedHash] = useState(null) + const [animatingBlocks, setAnimatingBlocks] = useState([]) + + // Animate blocks on load + useEffect(() => { + if (data.blocks.length > 0) { + data.blocks.forEach((_, index) => { + setTimeout(() => { + setAnimatingBlocks((prev) => [...prev, index]) + }, index * 100) + }) + } + }, [data.blocks]) + + 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) => { + 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...

+
+ + + ) + } + + 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.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 */} + {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. +

+
+
+
+ ) +}