-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/evoting #273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/evoting #273
Changes from 8 commits
c9f4a17
1ace84b
9edc741
7776a35
e1115c2
093a692
1fe79d8
4b41c8d
8fbe7b0
a4d467e
85b1cbb
8940bed
e2f1332
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
services: | ||
db: | ||
image: postgres:16 | ||
container_name: dev-evoting-db | ||
environment: | ||
POSTGRES_USER: evoting | ||
POSTGRES_PASSWORD: evoting | ||
POSTGRES_DB: evoting | ||
volumes: | ||
- ./db/data:/var/lib/postgresql/data | ||
ports: | ||
- "5432:5432" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
cache |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,39 +1,42 @@ | ||||||
{ | ||||||
"name": "evoting", | ||||||
"version": "0.1.0", | ||||||
"private": true, | ||||||
"scripts": { | ||||||
"dev": "next dev --turbopack", | ||||||
"build": "next build", | ||||||
"start": "next start", | ||||||
"lint": "next lint" | ||||||
}, | ||||||
"dependencies": { | ||||||
"@hookform/resolvers": "^3.10.0", | ||||||
"@radix-ui/react-label": "^2.1.3", | ||||||
"@radix-ui/react-radio-group": "^1.2.4", | ||||||
"@radix-ui/react-slot": "^1.2.0", | ||||||
"@tailwindcss/typography": "^0.5.16", | ||||||
"class-variance-authority": "^0.7.1", | ||||||
"clsx": "^2.1.1", | ||||||
"lucide-react": "^0.453.0", | ||||||
"next": "15.4.2", | ||||||
"react": "19.1.0", | ||||||
"react-dom": "19.1.0", | ||||||
"react-hook-form": "^7.55.0", | ||||||
"tailwind-merge": "^3.3.1", | ||||||
"tailwindcss-animate": "^1.0.7", | ||||||
"zod": "^3.24.2" | ||||||
}, | ||||||
"devDependencies": { | ||||||
"@eslint/eslintrc": "^3", | ||||||
"@tailwindcss/postcss": "^4", | ||||||
"@types/node": "^20", | ||||||
"@types/react": "^19", | ||||||
"@types/react-dom": "^19", | ||||||
"eslint": "^9", | ||||||
"eslint-config-next": "15.4.2", | ||||||
"tailwindcss": "^4", | ||||||
"typescript": "^5" | ||||||
} | ||||||
"name": "evoting", | ||||||
"version": "0.1.0", | ||||||
"private": true, | ||||||
"scripts": { | ||||||
"dev": "next dev --turbopack", | ||||||
"build": "next build", | ||||||
"start": "next start", | ||||||
"lint": "next lint" | ||||||
}, | ||||||
"dependencies": { | ||||||
"@hookform/resolvers": "^5.2.1", | ||||||
"@radix-ui/react-dropdown-menu": "^2.1.15", | ||||||
"@radix-ui/react-label": "^2.1.3", | ||||||
"@radix-ui/react-radio-group": "^1.2.4", | ||||||
"@radix-ui/react-slot": "^1.2.0", | ||||||
"@tailwindcss/typography": "^0.5.16", | ||||||
"better-auth": "^1.3.4", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify the better-auth version for security and stability. The 🌐 Web query:
💡 Result: As of August 2, 2025, the latest stable version of the Better Auth library is 1.3.3. (better-auth.com) Regarding version 1.3.4, there is no information available in the provided sources. It's possible that this version has not been released yet or that details about it are not publicly accessible. However, earlier versions of Better Auth have had known security vulnerabilities. For instance, versions prior to 1.1.6 were susceptible to an open redirect vulnerability in the email verification endpoint, which could allow attackers to redirect users to malicious websites. (github.com) Additionally, versions before 1.1.21 had a similar open redirect issue due to improper validation of the callbackURL parameter. (nvd.nist.gov) To ensure your application remains secure, it's recommended to use the latest stable version of Better Auth and regularly monitor official channels for updates on security patches. Citations:
Update better-auth dependency to a valid, secure release The • Change the dependency to Suggested diff: - "better-auth": "^1.3.4",
+ "better-auth": "^1.3.3", 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||
"class-variance-authority": "^0.7.1", | ||||||
"clsx": "^2.1.1", | ||||||
"lucide-react": "^0.453.0", | ||||||
"next": "15.4.2", | ||||||
"next-qrcode": "^2.5.1", | ||||||
"react": "19.1.0", | ||||||
"react-dom": "19.1.0", | ||||||
Comment on lines
+23
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify React 19 and Next.js compatibility. Using React 19.1.0 with Next.js 15.4.2 represents major version updates. Ensure these versions are compatible and stable for production use. 🌐 Web query:
💡 Result: React 19.1.0 is officially compatible with Next.js 15.4.2, but you must be aware of breaking changes and potential compatibility issues, especially with third-party libraries and deprecated APIs[1][8][4]. Compatibility and Known Issues:
Summary of Breaking Changes Most Likely to Cause Issues:
Best Practices for Upgrading:
Conclusion: Citations:
React 19.1.0 + Next.js 15.4.2 Upgrade: Confirm Breaking Changes and Migration Steps File: platforms/eVoting/package.json "next": "15.4.2",
"next-qrcode": "^2.5.1",
"react": "19.1.0",
"react-dom": "19.1.0", Upgrading both React and Next.js together is officially supported, but introduces major breaking changes. Before merging to production:
Verify all issues are addressed before shipping. 🤖 Prompt for AI Agents
|
||||||
"react-hook-form": "^7.55.0", | ||||||
"tailwind-merge": "^3.3.1", | ||||||
"tailwindcss-animate": "^1.0.7", | ||||||
"zod": "^4.0.14" | ||||||
}, | ||||||
"devDependencies": { | ||||||
"@eslint/eslintrc": "^3", | ||||||
"@tailwindcss/postcss": "^4", | ||||||
"@types/node": "^20", | ||||||
"@types/react": "^19", | ||||||
"@types/react-dom": "^19", | ||||||
"eslint": "^9", | ||||||
"eslint-config-next": "15.4.2", | ||||||
"tailwindcss": "^4", | ||||||
"typescript": "^5" | ||||||
} | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Update the lockfile to resolve pipeline failures. The CI pipeline is failing because Run the following command to fix the lockfile: pnpm install --no-frozen-lockfile Then commit the updated 🧰 Tools🪛 GitHub Actions: Check Lint[error] 1-1: pnpm install failed due to outdated lockfile. The pnpm-lock.yaml is not up to date with package.json dependencies. Run 'pnpm install --no-frozen-lockfile' to update the lockfile. 🪛 GitHub Actions: Check Format[error] 1-1: pnpm install failed due to outdated lockfile. The pnpm-lock.yaml is not up to date with package.json dependencies. Run 'pnpm install --no-frozen-lockfile' to update the lockfile. 🪛 GitHub Actions: Check Code[error] 1-1: pnpm install failed due to outdated lockfile. The pnpm-lock.yaml is not up to date with package.json dependencies. Use 'pnpm install --no-frozen-lockfile' to bypass this in CI. 🤖 Prompt for AI Agents
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -1,22 +1,29 @@ | ||||||||||
"use client"; | ||||||||||
|
||||||||||
import { useState, useEffect } from "react"; | ||||||||||
import { useState } from "react"; | ||||||||||
import { useForm } from "react-hook-form"; | ||||||||||
import { zodResolver } from "@hookform/resolvers/zod"; | ||||||||||
import { z } from "zod"; | ||||||||||
import { Plus, X, Eye, UserX } from "lucide-react"; | ||||||||||
import { | ||||||||||
Plus, | ||||||||||
X, | ||||||||||
Eye, | ||||||||||
UserX, | ||||||||||
ChartLine, | ||||||||||
ListOrdered, | ||||||||||
CircleUser, | ||||||||||
} from "lucide-react"; | ||||||||||
import { Button } from "@/components/ui/button"; | ||||||||||
import { Input } from "@/components/ui/input"; | ||||||||||
import { Label } from "@/components/ui/label"; | ||||||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; | ||||||||||
import { useToast } from "@/hooks/use-toast"; | ||||||||||
import { useAuth } from "@/hooks/useAuth"; | ||||||||||
import { isUnauthorizedError } from "@/lib/authUtils"; | ||||||||||
import Link from "next/link"; | ||||||||||
|
||||||||||
const createPollSchema = z.object({ | ||||||||||
title: z.string().min(1, "Poll title is required"), | ||||||||||
mode: z.enum(["public", "private"]), | ||||||||||
mode: z.enum(["normal", "point", "rank"]), | ||||||||||
visibility: z.enum(["public", "private"]), | ||||||||||
options: z | ||||||||||
.array(z.string().min(1, "Option cannot be empty")) | ||||||||||
.min(2, "At least 2 options required"), | ||||||||||
|
@@ -26,32 +33,16 @@ const createPollSchema = z.object({ | |||||||||
.refine((val) => { | ||||||||||
if (!val) return true; // Allow empty deadline | ||||||||||
const date = new Date(val); | ||||||||||
return !isNaN(date.getTime()) && date > new Date(); | ||||||||||
return !Number.isNaN(date.getTime()) && date > new Date(); | ||||||||||
}, "Deadline must be a valid future date"), | ||||||||||
}); | ||||||||||
|
||||||||||
type CreatePollForm = z.infer<typeof createPollSchema>; | ||||||||||
|
||||||||||
export default function CreatePoll() { | ||||||||||
const { toast } = useToast(); | ||||||||||
const { isAuthenticated, isLoading: authLoading } = useAuth(); | ||||||||||
const [options, setOptions] = useState<string[]>(["", ""]); | ||||||||||
|
||||||||||
// TODO: Redirect to login if not authenticated | ||||||||||
// useEffect(() => { | ||||||||||
// if (!authLoading && !isAuthenticated) { | ||||||||||
// toast({ | ||||||||||
// title: "Unauthorized", | ||||||||||
// description: "You are logged out. Logging in again...", | ||||||||||
// variant: "destructive", | ||||||||||
// }); | ||||||||||
// setTimeout(() => { | ||||||||||
// window.location.href = "/api/login"; | ||||||||||
// }, 500); | ||||||||||
// return; | ||||||||||
// } | ||||||||||
// }, [isAuthenticated, authLoading, toast]); | ||||||||||
|
||||||||||
const { | ||||||||||
register, | ||||||||||
handleSubmit, | ||||||||||
|
@@ -62,13 +53,20 @@ export default function CreatePoll() { | |||||||||
resolver: zodResolver(createPollSchema), | ||||||||||
defaultValues: { | ||||||||||
title: "", | ||||||||||
mode: "public", | ||||||||||
mode: "normal", | ||||||||||
visibility: "public", | ||||||||||
options: ["", ""], | ||||||||||
deadline: "", | ||||||||||
}, | ||||||||||
}); | ||||||||||
|
||||||||||
handleSubmit((data) => { | ||||||||||
console.log("Form submitted:", data); | ||||||||||
console.log(data); | ||||||||||
}); | ||||||||||
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove immediate handleSubmit invocation The - handleSubmit((data) => {
- console.log("Form submitted:", data);
- console.log(data);
- });
- 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||
|
||||||||||
const watchedMode = watch("mode"); | ||||||||||
const watchedVisibility = watch("visibility"); | ||||||||||
|
||||||||||
const addOption = () => { | ||||||||||
const newOptions = [...options, ""]; | ||||||||||
|
@@ -132,7 +130,107 @@ export default function CreatePoll() { | |||||||||
<RadioGroup | ||||||||||
value={watchedMode} | ||||||||||
onValueChange={(value) => | ||||||||||
setValue("mode", value as "public" | "private") | ||||||||||
setValue( | ||||||||||
"mode", | ||||||||||
value as "normal" | "point" | "rank" | ||||||||||
) | ||||||||||
} | ||||||||||
className="mt-2" | ||||||||||
> | ||||||||||
<div className="flex items-center space-x-4"> | ||||||||||
<Label className="flex items-center cursor-pointer"> | ||||||||||
<RadioGroupItem | ||||||||||
value="normal" | ||||||||||
className="sr-only" | ||||||||||
/> | ||||||||||
<div | ||||||||||
className={`border-2 rounded-lg p-4 flex-1 transition-all ${ | ||||||||||
watchedMode === "normal" | ||||||||||
? "border-(--crimson) bg-(--crimson-50)" | ||||||||||
: "border-gray-300 hover:border-(--crimson)" | ||||||||||
}`} | ||||||||||
> | ||||||||||
<div className="flex items-center"> | ||||||||||
<CircleUser className="text-(--crimson) w-6 h-6 mr-3" /> | ||||||||||
<div> | ||||||||||
<div className="font-semibold text-gray-900"> | ||||||||||
1P 1V | ||||||||||
</div> | ||||||||||
<div className="text-sm text-gray-600"> | ||||||||||
One person, one vote | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</Label> | ||||||||||
|
||||||||||
<Label className="flex items-center cursor-pointer"> | ||||||||||
<RadioGroupItem | ||||||||||
value="point" | ||||||||||
className="sr-only" | ||||||||||
/> | ||||||||||
<div | ||||||||||
className={`border-2 rounded-lg p-4 flex-1 transition-all ${ | ||||||||||
watchedMode === "point" | ||||||||||
? "border-(--crimson) bg-(--crimson-50)" | ||||||||||
: "border-gray-300 hover:border-(--crimson)" | ||||||||||
}`} | ||||||||||
> | ||||||||||
<div className="flex items-center"> | ||||||||||
<ChartLine className="text-(--crimson) w-6 h-6 mr-3" /> | ||||||||||
<div> | ||||||||||
<div className="font-semibold text-gray-900"> | ||||||||||
PBV | ||||||||||
</div> | ||||||||||
<div className="text-sm text-gray-600"> | ||||||||||
Each voter gets 100 points | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</Label> | ||||||||||
|
||||||||||
<Label className="flex items-center cursor-pointer"> | ||||||||||
<RadioGroupItem | ||||||||||
value="rank" | ||||||||||
className="sr-only" | ||||||||||
/> | ||||||||||
<div | ||||||||||
className={`border-2 rounded-lg p-4 flex-1 transition-all ${ | ||||||||||
watchedMode === "rank" | ||||||||||
? "border-(--crimson) bg-(--crimson-50)" | ||||||||||
: "border-gray-300 hover:border-(--crimson)" | ||||||||||
}`} | ||||||||||
> | ||||||||||
<div className="flex items-center"> | ||||||||||
<ListOrdered className="text-(--crimson) w-6 h-6 mr-3" /> | ||||||||||
<div> | ||||||||||
<div className="font-semibold text-gray-900"> | ||||||||||
RBV | ||||||||||
</div> | ||||||||||
<div className="text-sm text-gray-600"> | ||||||||||
Voters can rank order the | ||||||||||
choices | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
</Label> | ||||||||||
</div> | ||||||||||
</RadioGroup> | ||||||||||
</div> | ||||||||||
|
||||||||||
<div> | ||||||||||
<Label className="text-sm font-semibold text-gray-700"> | ||||||||||
Vote Visibility | ||||||||||
</Label> | ||||||||||
<RadioGroup | ||||||||||
value={watchedVisibility} | ||||||||||
onValueChange={(value) => | ||||||||||
setValue( | ||||||||||
"visibility", | ||||||||||
value as "public" | "private" | ||||||||||
) | ||||||||||
} | ||||||||||
className="mt-2" | ||||||||||
> | ||||||||||
|
@@ -144,7 +242,7 @@ export default function CreatePoll() { | |||||||||
/> | ||||||||||
<div | ||||||||||
className={`border-2 rounded-lg p-4 flex-1 transition-all ${ | ||||||||||
watchedMode === "public" | ||||||||||
watchedVisibility === "public" | ||||||||||
? "border-(--crimson) bg-(--crimson-50)" | ||||||||||
: "border-gray-300 hover:border-(--crimson)" | ||||||||||
}`} | ||||||||||
|
@@ -170,7 +268,7 @@ export default function CreatePoll() { | |||||||||
/> | ||||||||||
<div | ||||||||||
className={`border-2 rounded-lg p-4 flex-1 transition-all ${ | ||||||||||
watchedMode === "private" | ||||||||||
watchedVisibility === "private" | ||||||||||
? "border-(--crimson) bg-(--crimson-50)" | ||||||||||
: "border-gray-300 hover:border-(--crimson)" | ||||||||||
}`} | ||||||||||
|
@@ -224,6 +322,7 @@ export default function CreatePoll() { | |||||||||
<div className="mt-2 space-y-3"> | ||||||||||
{options.map((option, index) => ( | ||||||||||
<div | ||||||||||
// biome-ignore lint/suspicious/noArrayIndexKey: jatt dont care OOOOOOOOOO | ||||||||||
key={index} | ||||||||||
className="flex items-center space-x-2" | ||||||||||
> | ||||||||||
|
@@ -278,21 +377,8 @@ export default function CreatePoll() { | |||||||||
</Link> | ||||||||||
<Button | ||||||||||
type="submit" | ||||||||||
// disabled={createPollMutation.isPending} | ||||||||||
disabled={false} // TODO: replace with actual loading state | ||||||||||
className="flex-1 bg-(--crimson) hover:bg-(--crimson-50) hover:text-(--crimson) hover:border-(--crimson) border text-white" | ||||||||||
> | ||||||||||
{/* {createPollMutation.isPending ? ( | ||||||||||
<> | ||||||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" /> | ||||||||||
Creating... | ||||||||||
</> | ||||||||||
) : ( | ||||||||||
<> | ||||||||||
<Plus className="w-4 h-4 mr-2" /> | ||||||||||
Create Vote | ||||||||||
</> | ||||||||||
)} */} | ||||||||||
Create Vote | ||||||||||
</Button> | ||||||||||
</div> | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Weak default credentials for database.
Using
evoting
as both username and password creates a security vulnerability, even in development environments. For an eVoting system, this is particularly concerning.Consider using environment variables with stronger defaults:
Or add a comment indicating this is for development only:
+ # WARNING: Development only - use strong credentials in production db: image: postgres:16
🤖 Prompt for AI Agents