Skip to content

Commit 842f129

Browse files
committed
feat: voting logic
1 parent e475c7e commit 842f129

File tree

23 files changed

+27442
-265
lines changed

23 files changed

+27442
-265
lines changed

platforms/eVoting/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
"@radix-ui/react-radio-group": "^1.2.4",
1717
"@radix-ui/react-slot": "^1.2.0",
1818
"@tailwindcss/typography": "^0.5.16",
19-
"better-auth": "^1.3.4",
19+
"axios": "^1.9.0",
2020
"class-variance-authority": "^0.7.1",
2121
"clsx": "^2.1.1",
2222
"lucide-react": "^0.453.0",
2323
"next": "15.4.2",
24-
"next-qrcode": "^2.5.1",
24+
"qrcode.react": "^3.1.0",
2525
"react": "19.1.0",
2626
"react-dom": "19.1.0",
2727
"react-hook-form": "^7.55.0",

platforms/eVoting/src/app/(app)/[id]/page.tsx

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
1616
import { Label } from "@/components/ui/label";
1717
import { Badge } from "@/components/ui/badge";
1818
import { useToast } from "@/hooks/use-toast";
19-
import { useAuth } from "@/hooks/useAuth";
20-
import { isUnauthorizedError } from "@/lib/authUtils";
19+
import { useAuth } from "@/lib/auth-context";
20+
import { pollApi, type Poll } from "@/lib/pollApi";
2121
import Link from "next/link";
2222

2323
export default function Vote({ params }: { params: Promise<{ id: string }> }) {
2424
const { id } = use(params);
25-
const pollId = id ? Number.parseInt(id) : null;
25+
const pollId = id || null;
2626
const { toast } = useToast();
2727
const { isAuthenticated, isLoading: authLoading } = useAuth();
2828
const [selectedOption, setSelectedOption] = useState<number | null>(null);
@@ -50,13 +50,29 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
5050
}
5151
}, [pollId]);
5252

53-
const { data: polls = [], isLoading } = { data: [], isLoading: false }; // TODO: replace with actual data fetching logic
53+
const [selectedPoll, setSelectedPoll] = useState<Poll | null>(null);
54+
const [isLoading, setIsLoading] = useState(true);
5455

55-
const selectedPoll = polls.find((p) => p.id === pollId);
56+
useEffect(() => {
57+
const fetchPoll = async () => {
58+
if (!pollId) return;
59+
60+
try {
61+
const poll = await pollApi.getPollById(pollId);
62+
setSelectedPoll(poll);
63+
} catch (error) {
64+
console.error("Failed to fetch poll:", error);
65+
} finally {
66+
setIsLoading(false);
67+
}
68+
};
69+
70+
fetchPoll();
71+
}, [pollId]);
5672

5773
// Check if voting is still allowed
5874
const isVotingAllowed =
59-
selectedPoll?.isActive &&
75+
selectedPoll &&
6076
(!selectedPoll?.deadline ||
6177
new Date() < new Date(selectedPoll.deadline));
6278

@@ -114,13 +130,56 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
114130
return () => clearInterval(interval);
115131
}, [selectedPoll?.deadline, pollExists]);
116132

117-
const { data: voteStatus } = { data: null }; // TODO: replace with actual vote status fetching logic
133+
const [voteStatus, setVoteStatus] = useState<{ hasVoted: boolean; vote: any } | null>(null);
134+
const [resultsData, setResultsData] = useState<any>(null);
135+
const [isSubmitting, setIsSubmitting] = useState(false);
118136

119-
const { data: resultsData } = { data: null }; // TODO: replace with actual results fetching logic
137+
// Fetch vote status and results
138+
useEffect(() => {
139+
const fetchVoteData = async () => {
140+
if (!selectedPoll || !pollId) return;
141+
142+
try {
143+
const [voteStatusData, resultsData] = await Promise.all([
144+
pollApi.getUserVote(pollId),
145+
pollApi.getPollResults(pollId)
146+
]);
147+
setVoteStatus(voteStatusData);
148+
setResultsData(resultsData);
149+
} catch (error) {
150+
console.error("Failed to fetch vote data:", error);
151+
}
152+
};
120153

121-
const handleVoteSubmit = () => {
122-
if (selectedPoll && selectedOption !== null) {
123-
// TODO: replace with actual vote submission logic
154+
fetchVoteData();
155+
}, [selectedPoll, pollId]);
156+
157+
const handleVoteSubmit = async () => {
158+
if (!selectedPoll || selectedOption === null || !pollId) return;
159+
160+
setIsSubmitting(true);
161+
try {
162+
await pollApi.submitVote(pollId, selectedOption);
163+
toast({
164+
title: "Success!",
165+
description: "Your vote has been submitted",
166+
});
167+
// Refresh vote data
168+
const [voteStatusData, resultsData] = await Promise.all([
169+
pollApi.getUserVote(pollId),
170+
pollApi.getPollResults(pollId)
171+
]);
172+
setVoteStatus(voteStatusData);
173+
setResultsData(resultsData);
174+
} catch (error) {
175+
console.error("Failed to submit vote:", error);
176+
toast({
177+
title: "Error",
178+
description: "Failed to submit vote. Please try again.",
179+
variant: "destructive",
180+
});
181+
} finally {
182+
setIsSubmitting(false);
124183
}
125184
};
126185

@@ -352,13 +411,13 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
352411
</p>
353412
<Badge
354413
variant={
355-
selectedPoll?.isActive
414+
isVotingAllowed
356415
? "success"
357416
: "warning"
358417
}
359418
className="text-lg px-4 py-2"
360419
>
361-
{selectedPoll?.isActive
420+
{isVotingAllowed
362421
? "Active"
363422
: "Ended"}
364423
</Badge>
@@ -464,25 +523,25 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
464523
disabled={!isVotingAllowed}
465524
>
466525
<div className="space-y-3">
467-
{selectedPoll.options.map((option) => (
526+
{selectedPoll.options.map((option, index) => (
468527
<div
469-
key={option.id}
528+
key={index}
470529
className="flex items-center space-x-3"
471530
>
472531
<RadioGroupItem
473-
value={option.id.toString()}
474-
id={option.id.toString()}
532+
value={index.toString()}
533+
id={index.toString()}
475534
disabled={!isVotingAllowed}
476535
/>
477536
<Label
478-
htmlFor={option.id.toString()}
537+
htmlFor={index.toString()}
479538
className={`text-base flex-1 py-2 ${
480539
isVotingAllowed
481540
? "cursor-pointer"
482541
: "cursor-not-allowed opacity-50"
483542
}`}
484543
>
485-
{option.text}
544+
{option}
486545
</Label>
487546
</div>
488547
))}
@@ -495,12 +554,12 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
495554
onClick={handleVoteSubmit}
496555
disabled={
497556
selectedOption === null ||
498-
submitVoteMutation.isPending ||
557+
isSubmitting ||
499558
!isVotingAllowed
500559
}
501560
className="bg-(--crimson) hover:bg-(--crimson-50) hover:text-(--crimson) hover:border-(--crimson) border text-white px-8"
502561
>
503-
{submitVoteMutation.isPending ? (
562+
{isSubmitting ? (
504563
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" />
505564
) : (
506565
<VoteIcon className="w-4 h-4 mr-2" />

platforms/eVoting/src/app/(app)/create/page.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { Input } from "@/components/ui/input";
1818
import { Label } from "@/components/ui/label";
1919
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
2020
import { useToast } from "@/hooks/use-toast";
21+
import { pollApi } from "@/lib/pollApi";
22+
import { useRouter } from "next/navigation";
2123
import Link from "next/link";
2224

2325
const createPollSchema = z.object({
@@ -41,7 +43,9 @@ type CreatePollForm = z.infer<typeof createPollSchema>;
4143

4244
export default function CreatePoll() {
4345
const { toast } = useToast();
46+
const router = useRouter();
4447
const [options, setOptions] = useState<string[]>(["", ""]);
48+
const [isSubmitting, setIsSubmitting] = useState(false);
4549

4650
const {
4751
register,
@@ -89,8 +93,33 @@ export default function CreatePoll() {
8993
setValue("options", newOptions);
9094
};
9195

92-
const onSubmit = (data: CreatePollForm) => {
93-
// TODO: replace with actual API call to create poll
96+
const onSubmit = async (data: CreatePollForm) => {
97+
setIsSubmitting(true);
98+
try {
99+
await pollApi.createPoll({
100+
title: data.title,
101+
mode: data.mode,
102+
visibility: data.visibility,
103+
options: data.options.filter(option => option.trim() !== ""),
104+
deadline: data.deadline || undefined
105+
});
106+
107+
toast({
108+
title: "Success!",
109+
description: "Poll created successfully",
110+
});
111+
112+
router.push("/");
113+
} catch (error) {
114+
console.error("Failed to create poll:", error);
115+
toast({
116+
title: "Error",
117+
description: "Failed to create poll. Please try again.",
118+
variant: "destructive",
119+
});
120+
} finally {
121+
setIsSubmitting(false);
122+
}
94123
};
95124

96125
return (

0 commit comments

Comments
 (0)