Alexis Bruneteau dde0164b27 feat: Implement Phase 4 - Blockchain Visualization
Add comprehensive blockchain viewer with:
- BlockchainViewer component: Display blocks in expandable cards
- Hash visualization: Show SHA-256 hashes for each block
- Chain verification: Visual integrity status and verification button
- Block details: Expand to see full block information
  - Index, timestamp, previous hash, block hash
  - Encrypted vote data, transaction ID
  - Digital signatures
- Election selector: View blockchain for different elections
- Mock data: Demo blockchain included for testing
- Responsive design: Works on mobile and desktop

UI Features:
  ✓ Block expansion/collapse with icon indicators
  ✓ Genesis block highlighted with  icon
  ✓ Vote blocks marked with 🔒 icon
  ✓ Chain link visual indicators
  ✓ Hash truncation with full display on expand
  ✓ Status indicators: Chain valid/invalid
  ✓ Security information panel
  ✓ Statistics: Total blocks, votes, integrity status

Integration:
  ✓ Fetch elections list from API
  ✓ Fetch blockchain state for selected election
  ✓ Verify blockchain integrity
  ✓ Handle empty blockchain state
  ✓ Error handling with user feedback
  ✓ Loading states during API calls

Routes:
  ✓ /dashboard/blockchain - Main blockchain viewer
  ✓ Added to sidebar navigation
  ✓ 13 total routes now (added 1 new)

Frontend Build:
  ✓ No TypeScript errors
  ✓ Zero unused imports
  ✓ Production build successful
  ✓ All routes prerendered

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 01:59:46 +01:00

122 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Menu, LogOut, User as UserIcon } from "lucide-react"
import { useState } from "react"
import { useAuth } from "@/lib/auth-context"
import { ProtectedRoute } from "@/components/protected-route"
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const router = useRouter()
const { user, logout } = useAuth()
const [sidebarOpen, setSidebarOpen] = useState(false)
const handleLogout = () => {
logout()
router.push("/auth/login")
}
const navItems = [
{ href: "/dashboard", label: "Tableau de Bord", icon: "📊" },
{ href: "/dashboard/votes/active", label: "Votes Actifs", icon: "🗳️" },
{ href: "/dashboard/votes/upcoming", label: "Votes à Venir", icon: "📅" },
{ href: "/dashboard/votes/history", label: "Historique", icon: "📜" },
{ href: "/dashboard/votes/archives", label: "Archives", icon: "🗂️" },
{ href: "/dashboard/blockchain", label: "Blockchain", icon: "⛓️" },
{ href: "/dashboard/profile", label: "Profil", icon: "👤" },
]
return (
<div className="min-h-screen bg-background flex">
{/* Sidebar */}
<aside
className={`fixed inset-y-0 left-0 z-40 w-64 bg-card border-r border-border transition-transform duration-300 ${
sidebarOpen ? "translate-x-0" : "-translate-x-full"
} lg:static lg:translate-x-0`}
>
<div className="flex flex-col h-full">
{/* Logo */}
<div className="flex items-center gap-2 p-6 border-b border-border">
<span className="text-2xl">🗳</span>
<span className="font-bold text-lg text-accent">E-Voting</span>
</div>
{/* Navigation */}
<nav className="flex-1 p-4 space-y-2">
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-muted transition-colors text-foreground hover:text-accent"
onClick={() => setSidebarOpen(false)}
>
<span className="text-xl">{item.icon}</span>
<span className="text-sm font-medium">{item.label}</span>
</Link>
))}
</nav>
{/* Footer */}
<div className="p-4 border-t border-border space-y-2">
<Link href="/dashboard/profile">
<Button variant="ghost" className="w-full justify-start gap-2">
<UserIcon className="w-4 h-4" />
Mon Profil
</Button>
</Link>
<Button
onClick={handleLogout}
variant="ghost"
className="w-full justify-start gap-2 text-destructive hover:text-destructive"
>
<LogOut className="w-4 h-4" />
Déconnexion
</Button>
</div>
</div>
</aside>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Top Bar */}
<header className="sticky top-0 z-30 border-b border-border bg-card/50 backdrop-blur-sm">
<div className="flex items-center justify-between px-6 py-4">
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="lg:hidden p-2 hover:bg-muted rounded-lg"
>
<Menu className="w-5 h-5" />
</button>
<div className="ml-auto flex items-center gap-4">
{user && (
<span className="text-sm text-muted-foreground">
Bienvenue, {user.first_name} {user.last_name}
</span>
)}
</div>
</div>
</header>
{/* Content Area */}
<main className="flex-1 overflow-auto p-6 max-w-7xl w-full mx-auto">
<ProtectedRoute>{children}</ProtectedRoute>
</main>
</div>
{/* Mobile Overlay */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-30 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
</div>
)
}