From 1331fe596db8d14c507c90c2ab9d356398bdb62d Mon Sep 17 00:00:00 2001 From: shishiro Date: Thu, 6 Mar 2025 16:40:54 +0530 Subject: [PATCH] added verify voter page --- package-lock.json | 111 ++++++++ package.json | 3 + src/App.tsx | 5 + src/components/shared/admin/sidebar.tsx | 78 ++++++ src/components/ui/dialog.tsx | 133 ++++++++++ src/components/ui/select.tsx | 179 +++++++++++++ src/components/ui/switch.tsx | 29 +++ src/components/ui/table.tsx | 114 +++++++++ src/components/ui/tabs.tsx | 64 +++++ src/pages/admin/AdminLayout.tsx | 15 ++ src/pages/admin/Verify-voters.tsx | 321 ++++++++++++++++++++++++ 11 files changed, 1052 insertions(+) create mode 100644 src/components/shared/admin/sidebar.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/pages/admin/AdminLayout.tsx create mode 100644 src/pages/admin/Verify-voters.tsx 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' && ( + + )} +
+
+
+ ))} +
+
+
+
+ + + + + Verify Voter + + Verify this voter to allow them to participate in the election. + + + + {selectedVoter && ( +
+
+ Name: + {selectedVoter.name} +
+
+ Wallet: + {selectedVoter.walletAddress} +
+
+
+ + Verification Checks +
+
+
+ + +
+
+
+
+ )} + + + + + +
+
+
+ ); +}