Admin Panel
@@ -39,8 +40,8 @@ export function AdminSidebar() {
key={item.href}
variant="ghost"
className={cn(
- 'w-full justify-start',
- item.href === '/admin/verify-voters' && 'bg-accent text-accent-foreground'
+ 'w-full justify-start transition-colors duration-200 ease-in-out hover:bg-accent hover:text-accent-foreground',
+ item.href === location.pathname && 'bg-accent text-accent-foreground'
)}
asChild
>
diff --git a/src/components/shared/auth/Add-candidate-form.tsx b/src/components/shared/auth/Add-candidate-form.tsx
index 17e1b0f..59f0d50 100644
--- a/src/components/shared/auth/Add-candidate-form.tsx
+++ b/src/components/shared/auth/Add-candidate-form.tsx
@@ -47,10 +47,7 @@ export function AddCandidateForm() {
try {
if (state.instance !== null) {
- await state.instance.methods.addCandidate(values.name, values.slogan, electionid).send({
- from: state.account,
- gas: 1000000,
- });
+ await state.instance.methods.addCandidate(values.name, values.slogan, electionid);
toast.success('Candidate added successfully');
form.reset();
diff --git a/src/components/shared/auth/Login-form.tsx b/src/components/shared/auth/Login-form.tsx
index 4135fa8..74a5f06 100644
--- a/src/components/shared/auth/Login-form.tsx
+++ b/src/components/shared/auth/Login-form.tsx
@@ -42,10 +42,7 @@ export function LoginForm() {
}
if (state.instance !== null) {
- await state.instance.addVoter(state.account, values.name).send({
- from: state.account,
- gas: 1000000,
- });
+ await state.instance.addVoter(state.account, values.name);
dispatch({
type: 'REGISTER',
diff --git a/src/components/shared/candidate-card.tsx b/src/components/shared/candidate-card.tsx
new file mode 100644
index 0000000..14bfb3b
--- /dev/null
+++ b/src/components/shared/candidate-card.tsx
@@ -0,0 +1,62 @@
+import { Link } from 'react-router';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+
+interface Candidate {
+ id: number;
+ name: string;
+ slogan: string;
+ votes?: number;
+ percentage?: number;
+ position?: number;
+ winner?: boolean;
+}
+
+interface CandidateCardProps {
+ candidate: Candidate;
+ electionId: number;
+ showResults?: boolean;
+}
+
+export function CandidateCard({ candidate, electionId, showResults = false }: CandidateCardProps) {
+ return (
+
+
+
+
+
{candidate.name}
+ {candidate.position && (
+
+ Rank: {candidate.position}
+ {candidate.winner && (
+ Winner
+ )}
+
+ )}
+
+
+
+
+ {candidate.slogan}
+
+ {showResults && candidate.votes !== undefined && candidate.percentage !== undefined && (
+
+
+ Votes: {candidate.votes}
+ {candidate.percentage}%
+
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/components/shared/election/create-election.tsx b/src/components/shared/election/create-election.tsx
index c926e2b..5b45b33 100644
--- a/src/components/shared/election/create-election.tsx
+++ b/src/components/shared/election/create-election.tsx
@@ -52,10 +52,7 @@ export function CreateElectionForm() {
}
}
- await state.instance.createElection(values.purpose).send({
- from: state.account,
- gas: 1000000,
- });
+ await state.instance.createElection(values.purpose);
toast.message('Election created successfully', {
description: 'A new election has been created.',
@@ -63,7 +60,6 @@ export function CreateElectionForm() {
}
} catch (error) {
console.error(`Error: ${error}`);
- toast.error('Error occurred while creating the election');
}
});
};
diff --git a/src/components/shared/election/page.tsx b/src/components/shared/election/page.tsx
new file mode 100644
index 0000000..bbc4ec7
--- /dev/null
+++ b/src/components/shared/election/page.tsx
@@ -0,0 +1,275 @@
+import { Link } from 'react-router';
+import { CalendarClock, Clock, ExternalLink, Info, Shield, Timer, Users, Vote } from 'lucide-react';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import { Separator } from '@/components/ui/separator';
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
+import { CandidateCard } from '@/components/shared/candidate-card';
+
+const election = {
+ id: 3,
+ title: 'City Council Representative Election',
+ description:
+ "Vote for your district's representative on the city council for the upcoming term. The elected representative will serve for a period of two years and will be responsible for representing the interests of residents in district meetings, proposing and voting on local ordinances, and addressing community concerns.",
+ startDate: '2023-11-15T09:00:00',
+ endDate: '2023-12-15T18:00:00',
+ organizerName: 'City Electoral Commission',
+ organizerDescription:
+ 'The City Electoral Commission is responsible for conducting fair and transparent elections within the city limits. The commission ensures that all eligible citizens have the opportunity to vote and that the electoral process adheres to established regulations.',
+ totalVoters: 2500,
+ totalVotes: 1243,
+ voterParticipation: 49.7,
+ status: 'active',
+ electionType: 'Single Choice',
+ blockchainAddress: '0x7a8b9c0d1e2f3g4h5i6j7k8l9m0n1o2p3q4r5s6t7u',
+ candidates: [
+ {
+ id: 1,
+ name: 'Rebecca Johnson',
+ slogan: 'Sustainable development and affordable housing for all',
+ votes: 487,
+ percentage: 39.2,
+ },
+ {
+ id: 2,
+ name: 'Michael Chen',
+ slogan: 'Economic growth, public safety, and efficient city services',
+ votes: 356,
+ percentage: 28.6,
+ },
+ {
+ id: 3,
+ name: 'Sophia Rodriguez',
+ slogan: 'Better public spaces and youth programs for our community',
+ votes: 289,
+ percentage: 23.3,
+ },
+ {
+ id: 4,
+ name: 'David Washington',
+ slogan: 'Improving infrastructure and environmental initiatives',
+ votes: 111,
+ percentage: 8.9,
+ },
+ ],
+};
+
+function formatDate(dateString: string) {
+ const options: Intl.DateTimeFormatOptions = {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ };
+ return new Date(dateString).toLocaleDateString(undefined, options);
+}
+
+function getDaysRemaining(endDate: string) {
+ const end = new Date(endDate);
+ const now = new Date();
+ const diffTime = end.getTime() - now.getTime();
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+ return diffDays;
+}
+
+export default function ElectionDetailPage() {
+ const daysRemaining = getDaysRemaining(election.endDate);
+ const isActive =
+ new Date() >= new Date(election.startDate) && new Date() <= new Date(election.endDate);
+
+ return (
+
+
+
+
+
+
+
+ {election.electionType}
+
+ {isActive ? (
+
+ Active
+
+ ) : (
+
+ Upcoming
+
+ )}
+
+
+
{election.title}
+
+
{election.description}
+
+
+
+
+ Start: {formatDate(election.startDate)}
+
+
+
+ End: {formatDate(election.endDate)}
+
+
+
+ {election.totalVotes} votes cast
+
+
+
+ {isActive && (
+
+
+
+
+ )}
+
+
+
+
+
Candidates
+
+ {election.candidates.map((candidate) => (
+
+ ))}
+
+
+
+
+
About the Organizer
+
+
+ {election.organizerName}
+
+
+ {election.organizerDescription}
+
+
+
+
+
+
+
+
+
+
+ Election Status
+
+
+ {isActive ? (
+
+
Voting Open
+
+
+ {daysRemaining > 0 ? `${daysRemaining} days remaining` : 'Ending today'}
+
+
+ ) : (
+
+
+ Upcoming Election
+
+
+
+ Starts {formatDate(election.startDate)}
+
+
+ )}
+
+
+
+ Voter Participation
+ {election.voterParticipation}%
+
+
+
+ {election.totalVotes} out of {election.totalVoters} registered voters
+
+
+
+
+
+
+
+ Election Type
+ {election.electionType}
+
+
+ Total Candidates
+ {election.candidates.length}
+
+
+
+
+
+
+
+
+
+ Blockchain Verification
+
+
+
+
+
+ This election is secured by blockchain technology, ensuring transparency and
+ immutability of all votes.
+
+
+
Contract Address
+
+
+
+
+ {election.blockchainAddress.substring(0, 10)}...
+
+
+
+
+ {election.blockchainAddress}
+
+
+
+
+
+
+
+
+
+
+
+ Share This Election
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx
new file mode 100644
index 0000000..10af7e6
--- /dev/null
+++ b/src/components/ui/progress.tsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+function Progress({
+ className,
+ value,
+ ...props
+}: React.ComponentProps
) {
+ return (
+
+
+
+ )
+}
+
+export { Progress }
diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx
new file mode 100644
index 0000000..ae6f99f
--- /dev/null
+++ b/src/components/ui/radio-group.tsx
@@ -0,0 +1,43 @@
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { CircleIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function RadioGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function RadioGroupItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { RadioGroup, RadioGroupItem }
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 0000000..3cf4f89
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..ee7ae86
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/src/pages/CreateElection.tsx b/src/pages/CreateElection.tsx
index 04e54a2..68beadc 100644
--- a/src/pages/CreateElection.tsx
+++ b/src/pages/CreateElection.tsx
@@ -1,11 +1,194 @@
-import { CreateElectionForm } from '@/components/shared/election/create-election';
+import type React from 'react';
+import { useState } from 'react';
+import { Link } from 'react-router';
+import { Info, Plus, Trash2 } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Textarea } from '@/components/ui/textarea';
+import { Switch } from '@/components/ui/switch';
+
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+
+export default function CreateElectionPage() {
+ const [purpose, setPurpose] = useState('');
+
+ const [candidates, setCandidates] = useState([{ id: 1, name: '', slogan: '' }]);
+ const [successDialogOpen, setSuccessDialogOpen] = useState(false);
+
+ const addCandidate = () => {
+ setCandidates([...candidates, { id: candidates.length + 1, name: '', slogan: '' }]);
+ };
+
+ const removeCandidate = (id: number) => {
+ if (candidates.length > 1) {
+ setCandidates(candidates.filter((candidate) => candidate.id !== id));
+ }
+ };
+
+ const updateCandidate = (id: number, field: string, value: string) => {
+ setCandidates(
+ candidates.map((candidate) =>
+ candidate.id === id ? { ...candidate, [field]: value } : candidate
+ )
+ );
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ setSuccessDialogOpen(true);
+ };
-const CreateElection = () => {
return (
-
-
-
- );
-};
+
+
+
Create a New Election
+
+ Set up a secure blockchain-based election in minutes.
+
+
-export default CreateElection;
+
+
+ {/* Success Dialog */}
+
+
+ );
+}
diff --git a/src/pages/admin/Election.tsx b/src/pages/admin/Election.tsx
new file mode 100644
index 0000000..f3b5253
--- /dev/null
+++ b/src/pages/admin/Election.tsx
@@ -0,0 +1,147 @@
+import { Link } from 'react-router';
+import { Plus, Users } from 'lucide-react';
+import React, { useContext } from 'react';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+import Loading from '../loading';
+import AuthContext from '@/context/AuthContext';
+
+export default function AdminElectionsPage() {
+ const [loading, setLoading] = React.useState(true);
+ const { state } = useContext(AuthContext);
+ const [elections, setElections] = React.useState<
+ { id: number; title: string; status: string; totalVotes: number }[] | null
+ >(null);
+ const [filter, setFilter] = React.useState('all');
+
+ React.useEffect(() => {
+ const getElections = async () => {
+ if (!state.instance) return;
+
+ setLoading(true);
+
+ try {
+ const election_count = await state.instance!.noOfElections();
+ const elections_ = [];
+
+ for (let i = 1; i <= election_count.toNumber(); i++) {
+ const result = await state.instance!.getElection(i);
+
+ if (result.purpose !== '') {
+ const statusNumber = result.status.toNumber();
+ const status =
+ statusNumber === 1 ? 'upcoming' : statusNumber === 2 ? 'active' : 'completed';
+
+ elections_.push({
+ id: i,
+ title: result.purpose,
+ status,
+ totalVotes: result.totalVotes.toNumber(),
+ });
+ }
+ }
+
+ setElections(elections_);
+ } catch (error) {
+ console.error('Error fetching elections:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ getElections();
+ }, [state]);
+
+ function getFilteredElections() {
+ if (!elections) return [];
+ return filter === 'all' ? elections : elections.filter((e) => e.status === filter);
+ }
+
+ if (loading) return ;
+
+ const filteredElections = getFilteredElections();
+
+ return (
+
+
+
Elections
+
+
+
+
+
+ All
+ Active
+ Upcoming
+ Completed
+
+
+
+ {filteredElections.length > 0 ? (
+
+
+ All Elections
+
+
+
+
+
+ Title
+ Status
+ Participation
+ Actions
+
+
+
+ {filteredElections.map((election) => (
+
+ {election.title}
+
+ {election.status === 'active' ? (
+ Active
+ ) : election.status === 'completed' ? (
+ Completed
+ ) : (
+
+ Upcoming
+
+ )}
+
+
+
+
+ {election.totalVotes} Votes
+
+
+
+
+
+
+ ))}
+
+
+
+
+ ) : (
+ No elections available.
+ )}
+
+ );
+}
diff --git a/src/pages/admin/Verify-voters.tsx b/src/pages/admin/Verify-voters.tsx
index 0f556d9..7a48cab 100644
--- a/src/pages/admin/Verify-voters.tsx
+++ b/src/pages/admin/Verify-voters.tsx
@@ -131,10 +131,7 @@ export default function AdminVerifyVotersPage() {
const verifyUser = async (address: string) => {
try {
console.log('state.instance', state.instance);
- const result = await state.instance!.authorize(address).send({
- from: state.account,
- gas: 1000000,
- });
+ const result = await state.instance!.authorize(address);
console.log('result', result);
setVerificationDialogOpen(false);
setVoters((prev) =>
diff --git a/src/pages/election/ResultPage.tsx b/src/pages/election/ResultPage.tsx
new file mode 100644
index 0000000..2770dba
--- /dev/null
+++ b/src/pages/election/ResultPage.tsx
@@ -0,0 +1,455 @@
+import { Link } from 'react-router';
+import {
+ ArrowLeft,
+ Calendar,
+ ChevronRight,
+ Download,
+ ExternalLink,
+ Info,
+ Medal,
+ Shield,
+ Trophy,
+ Users,
+} from 'lucide-react';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import { Separator } from '@/components/ui/separator';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
+
+// Mock election results data
+const electionResults = {
+ id: 3,
+ title: 'City Council Representative Election',
+ description: "Vote for your district's representative on the city council for the upcoming term.",
+ startDate: '2023-11-15T09:00:00',
+ endDate: '2023-12-15T18:00:00',
+ organizerName: 'City Electoral Commission',
+ totalVoters: 2500,
+ totalVotes: 1243,
+ voterParticipation: 49.7,
+ status: 'completed',
+ electionType: 'Single Choice',
+ blockchainAddress: '0x7a8b9c0d1e2f3g4h5i6j7k8l9m0n1o2p3q4r5s6t7u',
+ transactionHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
+ candidates: [
+ {
+ id: 1,
+ name: 'Rebecca Johnson',
+ description:
+ 'Experienced community organizer with a focus on sustainable urban development and affordable housing initiatives.',
+ image: '/placeholder.svg?height=200&width=200',
+ votes: 487,
+ percentage: 39.2,
+ position: 1,
+ winner: true,
+ },
+ {
+ id: 2,
+ name: 'Michael Chen',
+ description:
+ 'Former business owner advocating for local economic growth, improved public safety, and efficient city services.',
+ image: '/placeholder.svg?height=200&width=200',
+ votes: 356,
+ percentage: 28.6,
+ position: 2,
+ winner: false,
+ },
+ {
+ id: 3,
+ name: 'Sophia Rodriguez',
+ description:
+ 'Education advocate and former school board member committed to improving public spaces and youth programs.',
+ image: '/placeholder.svg?height=200&width=200',
+ votes: 289,
+ percentage: 23.3,
+ position: 3,
+ winner: false,
+ },
+ {
+ id: 4,
+ name: 'David Washington',
+ description:
+ 'Civil engineer with plans to improve infrastructure, transportation options, and environmental initiatives.',
+ image: '/placeholder.svg?height=200&width=200',
+ votes: 111,
+ percentage: 8.9,
+ position: 4,
+ winner: false,
+ },
+ ],
+ votingStats: {
+ totalEligible: 2500,
+ totalRegistered: 1850,
+ totalVoted: 1243,
+ byTime: [
+ { date: '2023-11-15', votes: 245 },
+ { date: '2023-11-16', votes: 132 },
+ { date: '2023-11-17', votes: 98 },
+ { date: '2023-11-18', votes: 76 },
+ { date: '2023-11-19', votes: 65 },
+ { date: '2023-11-20', votes: 89 },
+ { date: '2023-11-21', votes: 112 },
+ { date: '2023-11-22', votes: 78 },
+ { date: '2023-11-23', votes: 56 },
+ { date: '2023-11-24', votes: 45 },
+ { date: '2023-11-25', votes: 34 },
+ { date: '2023-11-26', votes: 23 },
+ { date: '2023-11-27', votes: 45 },
+ { date: '2023-11-28', votes: 67 },
+ { date: '2023-11-29', votes: 43 },
+ { date: '2023-11-30', votes: 35 },
+ ],
+ },
+};
+
+// Helper function to format date
+function formatDate(dateString: string) {
+ const options: Intl.DateTimeFormatOptions = {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ };
+ return new Date(dateString).toLocaleDateString(undefined, options);
+}
+
+export default function ElectionResultsPage() {
+ const winner = electionResults.candidates.find((candidate) => candidate.winner);
+
+ return (
+
+
+
+
+
+ Elections
+
+
+
+ {electionResults.title}
+
+
+ Results
+
+
+
+
+
+
+ {electionResults.electionType}
+
+
+ Completed
+
+
+
+
+ {electionResults.title} Results
+
+
+
{electionResults.description}
+
+
+
+
+ {formatDate(electionResults.startDate)} - {formatDate(electionResults.endDate)}
+
+
+
+ {electionResults.totalVotes} votes cast ({electionResults.voterParticipation}%
+ participation)
+
+
+
+
+
+
+
+
+
+
+
+
+ {winner && (
+
+
+
+
+ Election Winner
+
+
+
+
+
+

+
+
+
+
+
+
{winner.name}
+
{winner.description}
+
+
+ {winner.votes}
+ votes
+
+
+ {winner.percentage}%
+ of total
+
+
+
+
+
+
+ )}
+
+
+
+ Vote Distribution
+ Breakdown of votes received by each candidate
+
+
+
+
+ {electionResults.candidates.map((candidate) => (
+
+
+
+
+ {candidate.position}
+
+
{candidate.name}
+ {candidate.winner && (
+
Winner
+ )}
+
+
{candidate.percentage}%
+
+
+
+
+ {candidate.votes} votes
+
+
+
+ ))}
+
+
+
+
+
+
Detailed Results
+
+
+
+ Rank
+ Candidate
+ Votes
+ Percentage
+
+
+
+ {electionResults.candidates.map((candidate) => (
+
+ {candidate.position}
+
+ {candidate.name}
+ {candidate.winner && (
+
+ Winner
+
+ )}
+
+ {candidate.votes}
+ {candidate.percentage}%
+
+ ))}
+
+
+
+
+
+
+
+
+
+ Voting Statistics
+
+
+
+
+
Eligible Voters
+
+ {electionResults.votingStats.totalEligible}
+
+
+
+
Registered Voters
+
+ {electionResults.votingStats.totalRegistered}
+
+
+
+
Total Votes Cast
+
{electionResults.votingStats.totalVoted}
+
+
+
+
+
Voter Participation
+
+
+ Participation Rate
+ {electionResults.voterParticipation}%
+
+
+
+ {electionResults.totalVotes} out of {electionResults.totalVoters} eligible
+ voters participated
+
+
+
+
+
+
+
+
+
+
+
+
+ Election Information
+
+
+
+
Organizer
+
{electionResults.organizerName}
+
+
+
Election Type
+
{electionResults.electionType}
+
+
+
Election Period
+
+ {formatDate(electionResults.startDate)} -{' '}
+ {formatDate(electionResults.endDate)}
+
+
+
+
+
+
+
+
+
+
+
+ Blockchain Verification
+
+
+
+
+
+ These results are secured and verified by blockchain technology, ensuring
+ transparency and immutability.
+
+
+
+
Contract Address
+
+
+
+
+ {electionResults.blockchainAddress.substring(0, 10)}...
+
+
+
+
+
+ {electionResults.blockchainAddress}
+
+
+
+
+
+
+
Transaction Hash
+
+
+
+
+ {electionResults.transactionHash.substring(0, 10)}...
+
+
+
+
+ {electionResults.transactionHash}
+
+
+
+
+
+
+
+
+
+
+
+
+ Share Results
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/election/VotePage.tsx b/src/pages/election/VotePage.tsx
new file mode 100644
index 0000000..214be74
--- /dev/null
+++ b/src/pages/election/VotePage.tsx
@@ -0,0 +1,273 @@
+import { useState } from 'react';
+import { Link } from 'react-router';
+import { ArrowLeft, Check, ChevronRight, Info, Lock, Shield, Vote } from 'lucide-react';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
+import { Label } from '@/components/ui/label';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+
+const election = {
+ id: 3,
+ title: 'City Council Representative Election',
+ description: "Vote for your district's representative on the city council for the upcoming term.",
+ candidates: [
+ {
+ id: 1,
+ name: 'Rebecca Johnson',
+ description:
+ 'Experienced community organizer with a focus on sustainable urban development and affordable housing initiatives.',
+ image: '/placeholder.svg?height=200&width=200',
+ },
+ {
+ id: 2,
+ name: 'Michael Chen',
+ description:
+ 'Former business owner advocating for local economic growth, improved public safety, and efficient city services.',
+ image: '/placeholder.svg?height=200&width=200',
+ },
+ {
+ id: 3,
+ name: 'Sophia Rodriguez',
+ description:
+ 'Education advocate and former school board member committed to improving public spaces and youth programs.',
+ image: '/placeholder.svg?height=200&width=200',
+ },
+ {
+ id: 4,
+ name: 'David Washington',
+ description:
+ 'Civil engineer with plans to improve infrastructure, transportation options, and environmental initiatives.',
+ image: '/placeholder.svg?height=200&width=200',
+ },
+ ],
+};
+
+export default function VotePage() {
+ const [selectedCandidate, setSelectedCandidate] = useState(null);
+ const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
+ const [successDialogOpen, setSuccessDialogOpen] = useState(false);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleVoteSubmit = () => {
+ setConfirmDialogOpen(false);
+ setIsSubmitting(true);
+
+ // Simulate blockchain transaction
+ setTimeout(() => {
+ setIsSubmitting(false);
+ setSuccessDialogOpen(true);
+ }, 2000);
+ };
+
+ return (
+
+
+
+
+
+ Active Elections
+
+
+
+ {election.title}
+
+
+ Vote
+
+
+
+
+
+
{election.title}
+
+ Cast your vote for one candidate. Your vote is secure and anonymous.
+
+
+
+
+
+
+
+ Secure Voting
+
+
+ Your vote is secured by blockchain technology and cannot be altered once submitted.
+
+
+
+
+
+
Select Your Candidate
+
+
+ {election.candidates.map((candidate) => (
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ {/* Confirmation Dialog */}
+
+
+ {/* Processing Dialog */}
+
+
+ {/* Success Dialog */}
+
+
+ );
+}
diff --git a/src/pages/loading.tsx b/src/pages/loading.tsx
index f948528..9da644e 100644
--- a/src/pages/loading.tsx
+++ b/src/pages/loading.tsx
@@ -3,7 +3,7 @@ import { Card } from '@/components/ui/card';
export default function Loading() {
return (
-