"use client" import { useState } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { AlertCircle, CheckCircle, Loader2 } from "lucide-react" import { createSignedBallot, PublicKeysResponse } from "@/lib/crypto-client" export interface Candidate { id: number name: string description?: string } export interface VotingInterfaceProps { electionId: number candidates: Candidate[] publicKeys?: PublicKeysResponse onVoteSubmitted?: (success: boolean, transactionId?: string) => void } type VotingStep = "select" | "confirm" | "submitting" | "success" | "error" /** * Voting Interface Component * Handles ballot creation, encryption, and submission */ export function VotingInterface({ electionId, candidates, publicKeys, onVoteSubmitted }: VotingInterfaceProps) { const [step, setStep] = useState("select") const [selectedCandidate, setSelectedCandidate] = useState(null) const [error, setError] = useState("") const [transactionId, setTransactionId] = useState("") const [loading, setLoading] = useState(false) /** * Handle candidate selection */ const handleSelectCandidate = (candidateId: number) => { setSelectedCandidate(candidateId) setStep("confirm") setError("") } /** * Handle back button to reselect */ const handleBack = () => { setSelectedCandidate(null) setStep("select") setError("") } /** * Handle vote submission with encryption and signing */ const handleSubmitVote = async () => { if (selectedCandidate === null) { setError("Veuillez sélectionner un candidat") return } setLoading(true) setError("") try { // 1. Get voter context from API const voterResponse = await fetch("/api/auth/profile", { headers: { "Authorization": `Bearer ${localStorage.getItem("auth_token")}` } }) if (!voterResponse.ok) { throw new Error("Impossible de récupérer le profil du votant") } const voterData = await voterResponse.json() const voterId = voterData.id.toString() // 2. Get or use provided public keys let keysToUse = publicKeys if (!keysToUse) { // Fetch public keys from server const keysResponse = await fetch(`/api/votes/public-keys?election_id=${electionId}`) if (!keysResponse.ok) { throw new Error("Impossible de récupérer les clés publiques") } keysToUse = await keysResponse.json() } if (!keysToUse?.elgamal_pubkey) { throw new Error("Clés publiques non disponibles") } // 3. Create signed ballot with client-side encryption // For MVP: Use simple vote encoding (0 or 1) // Selected candidate = 1, others = 0 const voteValue = selectedCandidate ? 1 : 0 const ballot = createSignedBallot( voteValue, voterId, keysToUse.elgamal_pubkey, "" // Empty private key for signing in MVP ) // 4. Submit encrypted ballot setStep("submitting") const submitResponse = await fetch("/api/votes/submit", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("auth_token")}` }, body: JSON.stringify({ election_id: electionId, candidate_id: selectedCandidate, encrypted_vote: ballot.encrypted_vote, zkp_proof: ballot.zkp_proof, signature: ballot.signature, timestamp: ballot.timestamp }) }) if (!submitResponse.ok) { const errorData = await submitResponse.json() throw new Error(errorData.detail || "Erreur lors de la soumission du vote") } const result = await submitResponse.json() // 5. Success setTransactionId(result.transaction_id || result.id) setStep("success") // Notify parent component if (onVoteSubmitted) { onVoteSubmitted(true, result.transaction_id || result.id) } } catch (err) { const errorMessage = err instanceof Error ? err.message : "Erreur inconnue" setError(errorMessage) setStep("error") if (onVoteSubmitted) { onVoteSubmitted(false) } } finally { setLoading(false) } } /** * Reset to allow new vote (in testing mode only) */ const handleReset = () => { setSelectedCandidate(null) setStep("select") setError("") setTransactionId("") } return (
{/* Selection Step */} {step === "select" && (

Sélectionnez votre vote

Choisissez le candidat ou l'option pour lequel vous souhaitez voter

{candidates.map((candidate) => ( handleSelectCandidate(candidate.id)} >

{candidate.name}

{candidate.description && (

{candidate.description}

)}
))}
{error && (

{error}

)}
)} {/* Confirmation Step */} {step === "confirm" && selectedCandidate !== null && (

Confirmez votre vote

Veuillez vérifier votre sélection avant de soumettre

Vote sélectionné

{candidates.find((c) => c.id === selectedCandidate)?.name}

{candidates.find((c) => c.id === selectedCandidate)?.description}

Sécurité du vote: Votre bulletin sera chiffré avec ElGamal homomorphe avant transmission. Seul le décompte final sera connu, pas votre vote individuel.

{error && (

{error}

)}
)} {/* Submitting Step */} {step === "submitting" && (

Soumission du vote en cours...

Votre bulletin est en cours de chiffrement et d'enregistrement

)} {/* Success Step */} {step === "success" && (

Vote enregistré avec succès

Votre bulletin chiffré a été ajouté à la blockchain électorale

Détails du vote

{transactionId}

✓ Bulletin chiffré avec ElGamal

✓ Signature Dilithium appliquée

✓ Enregistré dans la blockchain

)} {/* Error Step */} {step === "error" && (

Erreur lors de la soumission

{error}

)}
) }