diff --git a/package-lock.json b/package-lock.json
index 67ea1a3..9ad2c3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,10 @@
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-popover": "^1.1.6",
+ "@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slot": "^1.1.2",
+ "@radix-ui/react-switch": "^1.1.3",
+ "@radix-ui/react-tabs": "^1.1.3",
"@tailwindcss/vite": "^4.0.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -1748,6 +1751,12 @@
"node": ">= 8"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
+ "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==",
+ "license": "MIT"
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
@@ -2267,6 +2276,49 @@
}
}
},
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz",
+ "integrity": "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.5",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.2",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.2",
+ "@radix-ui/react-portal": "1.1.4",
+ "@radix-ui/react-primitive": "2.0.2",
+ "@radix-ui/react-slot": "1.1.2",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
@@ -2285,6 +2337,65 @@
}
}
},
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.3.tgz",
+ "integrity": "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.2",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz",
+ "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.2",
+ "@radix-ui/react-roving-focus": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
diff --git a/package.json b/package.json
index 1da26c5..1cc6813 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,10 @@
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-popover": "^1.1.6",
+ "@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slot": "^1.1.2",
+ "@radix-ui/react-switch": "^1.1.3",
+ "@radix-ui/react-tabs": "^1.1.3",
"@tailwindcss/vite": "^4.0.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/src/App.tsx b/src/App.tsx
index 9eb80d1..560da9f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -7,6 +7,8 @@ import Layout from '@/pages/layout';
import CandidatePage from '@/pages/AddCandidatePage';
import CreateElection from '@/pages/CreateElection';
import HomePage from '@/pages/Home';
+import AdminLayout from '@/pages/admin/AdminLayout';
+import AdminVerifyVotersPage from './pages/admin/Verify-voters';
function App() {
const auth = React.useContext(AuthContext);
@@ -71,6 +73,9 @@ function App() {
upcoming election} />
active election} />
+ }>
+ } />
+
} />
not found return 404} />
diff --git a/src/components/shared/admin/sidebar.tsx b/src/components/shared/admin/sidebar.tsx
new file mode 100644
index 0000000..e94ce68
--- /dev/null
+++ b/src/components/shared/admin/sidebar.tsx
@@ -0,0 +1,78 @@
+import { Link } from 'react-router';
+import { BarChart, Calendar, FileText, Home, Settings, Shield, Users, Vote } from 'lucide-react';
+
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+
+const sidebarItems = [
+ {
+ title: 'Dashboard',
+ href: '/admin',
+ icon: Home,
+ },
+ {
+ title: 'Elections',
+ href: '/admin/elections',
+ icon: Vote,
+ },
+ {
+ title: 'Voters',
+ href: '/admin/verify-voters',
+ icon: Users,
+ },
+ {
+ title: 'Results',
+ href: '/admin/results',
+ icon: BarChart,
+ },
+ {
+ title: 'Reports',
+ href: '/admin/reports',
+ icon: FileText,
+ },
+ {
+ title: 'Calendar',
+ href: '/admin/calendar',
+ icon: Calendar,
+ },
+ {
+ title: 'Security',
+ href: '/admin/security',
+ icon: Shield,
+ },
+ {
+ title: 'Settings',
+ href: '/admin/settings',
+ icon: Settings,
+ },
+];
+
+export function AdminSidebar() {
+ return (
+
+
+
+
Admin Panel
+
+ {sidebarItems.map((item) => (
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..1b608b2
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,133 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { XIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 0000000..aa49c82
--- /dev/null
+++ b/src/components/ui/select.tsx
@@ -0,0 +1,179 @@
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "popper",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 0000000..fce06f7
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as SwitchPrimitive from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+function Switch({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 0000000..7b81be9
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,114 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Table({ className, ...props }: React.ComponentProps<"table">) {
+ return (
+
+ )
+}
+
+function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
+ return (
+
+ )
+}
+
+function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
+ return (
+
+ )
+}
+
+function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
+ return (
+ tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ )
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ | [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..7096b65
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,64 @@
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/pages/admin/AdminLayout.tsx b/src/pages/admin/AdminLayout.tsx
new file mode 100644
index 0000000..15c8859
--- /dev/null
+++ b/src/pages/admin/AdminLayout.tsx
@@ -0,0 +1,15 @@
+import { Outlet } from 'react-router';
+import { AdminSidebar } from '@/components/shared/admin/sidebar';
+
+const AdminLayout = () => {
+ return (
+
+ );
+};
+
+export default AdminLayout;
diff --git a/src/pages/admin/Verify-voters.tsx b/src/pages/admin/Verify-voters.tsx
new file mode 100644
index 0000000..b776fdd
--- /dev/null
+++ b/src/pages/admin/Verify-voters.tsx
@@ -0,0 +1,321 @@
+import React from 'react';
+import { Link } from 'react-router';
+import { Search, ShieldCheck } from 'lucide-react';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Input } from '@/components/ui/input';
+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 {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import { Switch } from '@/components/ui/switch';
+import { Label } from '@/components/ui/label';
+import AuthContext from '@/context/AuthContext';
+
+const voters = [
+ {
+ id: 1,
+ name: 'Alex Johnson',
+ email: 'alex.johnson@example.com',
+ walletAddress: '0x1a2b3c4d5e6f...',
+ registrationDate: '2023-11-20T14:23:45',
+ status: 'pending',
+ electionId: 3,
+ electionTitle: 'City Council Representative Election',
+ },
+ {
+ id: 2,
+ name: 'Maria Garcia',
+ email: 'maria.garcia@example.com',
+ walletAddress: '0x7g8h9i0j1k2l...',
+ registrationDate: '2023-11-21T09:15:30',
+ status: 'verified',
+ electionId: 3,
+ electionTitle: 'City Council Representative Election',
+ },
+ {
+ id: 3,
+ name: 'James Smith',
+ email: 'james.smith@example.com',
+ walletAddress: '0x3m4n5o6p7q8r...',
+ registrationDate: '2023-11-21T11:42:18',
+ status: 'pending',
+ electionId: 3,
+ electionTitle: 'City Council Representative Election',
+ },
+ {
+ id: 4,
+ name: 'Sarah Williams',
+ email: 'sarah.williams@example.com',
+ walletAddress: '0x9s0t1u2v3w4x...',
+ registrationDate: '2023-11-22T08:30:55',
+ status: 'rejected',
+ electionId: 2,
+ electionTitle: 'Cooperative Annual Board Election',
+ },
+ {
+ id: 5,
+ name: 'David Brown',
+ email: 'david.brown@example.com',
+ walletAddress: '0x5y6z7a8b9c0d...',
+ registrationDate: '2023-11-22T13:10:22',
+ status: 'pending',
+ electionId: 1,
+ electionTitle: 'University Faculty Senate Election',
+ },
+ {
+ id: 6,
+ name: 'Jennifer Miller',
+ email: 'jennifer.miller@example.com',
+ walletAddress: '0x1e2f3g4h5i6j...',
+ registrationDate: '2023-11-23T10:05:17',
+ status: 'verified',
+ electionId: 1,
+ electionTitle: 'University Faculty Senate Election',
+ },
+ {
+ id: 7,
+ name: 'Michael Davis',
+ email: 'michael.davis@example.com',
+ walletAddress: '0x7k8l9m0n1o2p...',
+ registrationDate: '2023-11-23T15:48:33',
+ status: 'pending',
+ electionId: 3,
+ electionTitle: 'City Council Representative Election',
+ },
+];
+
+export default function AdminVerifyVotersPage() {
+ const [selectedVoter, setSelectedVoter] = React.useState<(typeof voters)[0] | null>(null);
+ const [verificationDialogOpen, setVerificationDialogOpen] = React.useState(false);
+ const [filteredVoters, setFilteredVoters] = React.useState(voters);
+ const [searchQuery, setSearchQuery] = React.useState('');
+ const [statusFilter, setStatusFilter] = React.useState('all');
+ const [electionFilter, setElectionFilter] = React.useState('all');
+ const { state } = React.useContext(AuthContext);
+
+ React.useEffect(() => {
+ const getVoters = async () => {
+ if (state.instance !== null) {
+ const voters_list = await state.instance.methods.totalRegisteredVoters().call({
+ from: state.account,
+ });
+
+ console.log('voters_list', voters_list);
+ }
+ };
+ getVoters();
+ }, [state.account, state.instance]);
+
+ const handleSearch = (query: string) => {
+ setSearchQuery(query);
+ applyFilters(query, statusFilter, electionFilter);
+ };
+
+ const handleStatusFilter = (status: string) => {
+ setStatusFilter(status);
+ applyFilters(searchQuery, status, electionFilter);
+ };
+
+ const applyFilters = (query: string, status: string, election: string) => {
+ let filtered = voters;
+
+ if (query) {
+ filtered = filtered.filter(
+ (voter) =>
+ voter.name.toLowerCase().includes(query.toLowerCase()) ||
+ voter.email.toLowerCase().includes(query.toLowerCase()) ||
+ voter.walletAddress.toLowerCase().includes(query.toLowerCase())
+ );
+ }
+
+ if (status !== 'all') {
+ filtered = filtered.filter((voter) => voter.status === status);
+ }
+
+ if (election !== 'all') {
+ filtered = filtered.filter((voter) => voter.electionId.toString() === election);
+ }
+
+ setFilteredVoters(filtered);
+ };
+
+ const openVerificationDialog = (voter: (typeof voters)[0]) => {
+ setSelectedVoter(voter);
+ setVerificationDialogOpen(true);
+ };
+
+ const getStatusBadge = (status: string) => {
+ switch (status) {
+ case 'verified':
+ return Verified;
+ case 'pending':
+ return (
+
+ Pending
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ Verify Voters
+
+ Review and verify voter registrations for blockchain elections.
+
+
+
+
+
+
+ handleStatusFilter('all')}>
+ All
+
+ handleStatusFilter('pending')}>
+ Pending
+
+ handleStatusFilter('verified')}>
+ Verified
+
+
+
+
+
+
+
+ handleSearch(e.target.value)}
+ />
+
+
+
+
+
+
+
+ Voter Registrations
+
+ {filteredVoters.filter((v) => v.status === 'pending').length} Pending
+
+
+
+
+
+
+
+ Name
+ Wallet Address
+ Status
+ Actions
+
+
+
+ {filteredVoters.map((voter) => (
+
+ {voter.name}
+
+ {voter.walletAddress}
+
+ {getStatusBadge(voter.status)}
+
+
+ {voter.status === 'pending' && (
+ <>
+
+ >
+ )}
+ {voter.status !== 'pending' && (
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
|