diff --git a/index.html b/index.html
index e4b78ea..3f3c44e 100644
--- a/index.html
+++ b/index.html
@@ -2,9 +2,9 @@
-
+
- Vite + React + TS
+ VoteChain
diff --git a/package-lock.json b/package-lock.json
index bc1b2c6..20b245c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,8 @@
"name": "votechain",
"version": "0.0.0",
"dependencies": {
+ "@hookform/resolvers": "^4.1.3",
+ "@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@tailwindcss/vite": "^4.0.9",
"class-variance-authority": "^0.7.1",
@@ -16,10 +18,13 @@
"lucide-react": "^0.477.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-hook-form": "^7.54.2",
+ "react-router": "^7.2.0",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.9",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
@@ -320,6 +325,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"optional": true,
"os": [
"aix"
@@ -335,6 +341,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -350,6 +357,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -365,6 +373,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -380,6 +389,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -395,6 +405,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -410,6 +421,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -425,6 +437,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -440,6 +453,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -455,6 +469,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -470,6 +485,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -485,6 +501,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -500,6 +517,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -515,6 +533,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -530,6 +549,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -545,6 +565,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -560,6 +581,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -575,6 +597,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -590,6 +613,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -605,6 +629,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -620,6 +645,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -635,6 +661,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"sunos"
@@ -650,6 +677,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -665,6 +693,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -680,6 +709,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -1520,6 +1550,18 @@
"@ethersproject/strings": "^5.7.0"
}
},
+ "node_modules/@hookform/resolvers": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-4.1.3.tgz",
+ "integrity": "sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.0.0"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1678,10 +1720,57 @@
}
}
},
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz",
+ "integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.2"
+ },
+ "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-primitive": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
+ "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.1.2"
+ },
+ "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",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
+ "license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
@@ -1702,6 +1791,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -1714,6 +1804,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -1726,6 +1817,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -1738,6 +1830,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -1750,6 +1843,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1762,6 +1856,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1774,6 +1869,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1786,6 +1882,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1798,6 +1895,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1810,6 +1908,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1822,6 +1921,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1834,6 +1934,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1846,6 +1947,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1858,6 +1960,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1870,6 +1973,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1882,6 +1986,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -1894,6 +1999,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -1906,6 +2012,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -1918,11 +2025,18 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
]
},
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
"node_modules/@tailwindcss/node": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.9.tgz",
@@ -2174,10 +2288,17 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
@@ -2189,7 +2310,7 @@
"version": "22.13.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz",
"integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"undici-types": "~6.20.0"
}
@@ -2198,7 +2319,7 @@
"version": "19.0.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -2675,6 +2796,15 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2693,7 +2823,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true
+ "dev": true
},
"node_modules/debug": {
"version": "4.4.0",
@@ -2772,6 +2902,7 @@
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
+ "dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -3161,6 +3292,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -3727,6 +3859,7 @@
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -3832,7 +3965,8 @@
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -3850,6 +3984,7 @@
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3945,6 +4080,22 @@
"react": "^19.0.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.54.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
+ "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -3954,6 +4105,30 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz",
+ "integrity": "sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cookie": "^0.6.0",
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3977,6 +4152,7 @@
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz",
"integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==",
+ "dev": true,
"dependencies": {
"@types/estree": "1.0.6"
},
@@ -4053,6 +4229,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4088,6 +4270,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4170,6 +4353,12 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
+ "license": "ISC"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4221,7 +4410,7 @@
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
- "devOptional": true
+ "dev": true
},
"node_modules/update-browserslist-db": {
"version": "1.1.3",
@@ -4266,6 +4455,7 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz",
"integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==",
+ "dev": true,
"dependencies": {
"esbuild": "^0.25.0",
"postcss": "^8.5.3",
@@ -4394,6 +4584,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.24.2",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
+ "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index eba08e0..342b0e0 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,8 @@
"format": "prettier --write **/*.{js,ts,tsx}"
},
"dependencies": {
+ "@hookform/resolvers": "^4.1.3",
+ "@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@tailwindcss/vite": "^4.0.9",
"class-variance-authority": "^0.7.1",
@@ -20,10 +22,13 @@
"lucide-react": "^0.477.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-hook-form": "^7.54.2",
+ "react-router": "^7.2.0",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.9",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..b43a409
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/images/online-voting.svg b/public/images/online-voting.svg
new file mode 100644
index 0000000..92f00ba
--- /dev/null
+++ b/public/images/online-voting.svg
@@ -0,0 +1,784 @@
+
+
+
diff --git a/src/App.tsx b/src/App.tsx
index 090503b..b74b61c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,10 +1,11 @@
-import { Button } from '@/components/ui/button';
+import { Route, Routes } from 'react-router';
+import LoginPage from './pages/LoginPage';
function App() {
return (
-
-
-
+
+ } />
+
);
}
diff --git a/src/components/shared/Form-error.tsx b/src/components/shared/Form-error.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/shared/auth/Login-form.tsx b/src/components/shared/auth/Login-form.tsx
new file mode 100644
index 0000000..414fc39
--- /dev/null
+++ b/src/components/shared/auth/Login-form.tsx
@@ -0,0 +1,119 @@
+import { Vote } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import connectToBlockchain from '@/config';
+import { useContext, useState } from 'react';
+import AuthContext from '@/context/AuthContext';
+import { toast } from 'sonner';
+
+export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
+ const auth = useContext(AuthContext);
+ const [name, setName] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ if (!auth) {
+ return Loading...
;
+ }
+
+ const { dispatch } = auth;
+
+ const connectWallet = async (e: React.FormEvent) => {
+ e.preventDefault(); // Prevent form submission
+ setLoading(true);
+
+ try {
+ const blockchainConnection = await connectToBlockchain();
+ console.log('blockchainConnection', blockchainConnection);
+ if (!blockchainConnection) {
+ setLoading(false);
+ return;
+ }
+
+ const { contractInstance, signer } = blockchainConnection;
+ console.log('contractInstacne', contractInstance);
+ console.log('signer', signer);
+ const account = await signer.getAddress();
+ console.log('account', signer);
+ console.log('account1', account);
+
+ console.log('contractInstance', contractInstance);
+
+ toast.success(`Connected to ${account}`);
+
+ let isAdmin = false;
+ let isVoter = true;
+
+ try {
+ isAdmin = await contractInstance.isAdmin(account);
+ console.log('isAdmin', isAdmin);
+ } catch (err) {
+ console.error('Error checking admin status:', err);
+ }
+
+ if (!isAdmin) {
+ try {
+ const voter = await contractInstance.getVoterdetails(account);
+ if (voter?.votername) {
+ isVoter = false;
+ }
+ } catch (error) {
+ console.error('Error fetching voter details:', error);
+ }
+ }
+
+ dispatch({
+ type: 'login',
+ payload: { account, instance: contractInstance, is_admin: isAdmin, flag: isVoter },
+ });
+
+ toast.success('Wallet connected successfully!');
+ } catch (error) {
+ console.error('MetaMask connection error:', error);
+ toast.error('Failed to connect wallet.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
new file mode 100644
index 0000000..3ddf5bb
--- /dev/null
+++ b/src/components/ui/form.tsx
@@ -0,0 +1,165 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+import {
+ Controller,
+ ControllerProps,
+ FieldPath,
+ FieldValues,
+ FormProvider,
+ useFormContext,
+ useFormState,
+} from "react-hook-form"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+> = {
+ name: TName
+}
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+)
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ )
+}
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState } = useFormContext()
+ const formState = useFormState({ name: fieldContext.name })
+ const fieldState = getFieldState(fieldContext.name, formState)
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ")
+ }
+
+ const { id } = itemContext
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
+
+type FormItemContextValue = {
+ id: string
+}
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue
+)
+
+function FormItem({ className, ...props }: React.ComponentProps<"div">) {
+ const id = React.useId()
+
+ return (
+
+
+
+ )
+}
+
+function FormLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ )
+}
+
+function FormControl({ ...props }: React.ComponentProps) {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ )
+}
+
+function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ )
+}
+
+function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message ?? "") : props.children
+
+ if (!body) {
+ return null
+ }
+
+ return (
+
+ {body}
+
+ )
+}
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000..4bfe9cc
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000..fb5fbc3
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,24 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+
+import { cn } from "@/lib/utils"
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/src/main.tsx b/src/main.tsx
index 0fd1853..95e1ead 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -4,12 +4,15 @@ import './index.css';
import App from './App.tsx';
import AuthProvider from './context/AuthProvider.tsx';
import { Toaster } from 'sonner';
+import { BrowserRouter } from 'react-router';
createRoot(document.getElementById('root')!).render(
-
-
-
-
+
+
+
+
+
+
);
diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..3a87567
--- /dev/null
+++ b/src/pages/LoginPage.tsx
@@ -0,0 +1,31 @@
+import { Vote } from 'lucide-react';
+import { LoginForm } from '@/components/shared/auth/Login-form';
+
+export default function LoginPage() {
+ return (
+
+
+
+

+
+
+ );
+}
diff --git a/src/schemas/index.ts b/src/schemas/index.ts
new file mode 100644
index 0000000..e69de29