From 397b6d4032d4e8c81165639997061a03ea9ea289 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:07:26 +0530 Subject: [PATCH 01/12] feat: added eReputation weighted calculation --- platforms/eVoting/src/app/(app)/[id]/page.tsx | 55 +++++++++++++------ .../eVoting/src/app/(app)/create/page.tsx | 13 ++++- platforms/eVoting/src/app/(app)/page.tsx | 16 +++++- .../evoting-api/src/services/PollService.ts | 5 ++ .../evoting-api/src/services/VoteService.ts | 10 ++-- 5 files changed, 75 insertions(+), 24 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/[id]/page.tsx b/platforms/eVoting/src/app/(app)/[id]/page.tsx index c3a26858..b6148c93 100644 --- a/platforms/eVoting/src/app/(app)/[id]/page.tsx +++ b/platforms/eVoting/src/app/(app)/[id]/page.tsx @@ -11,6 +11,8 @@ import { Users, BarChart3, Shield, + ChartLine, + CircleUser, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -308,23 +310,42 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { Back to Home - - {selectedPoll.visibility === "public" ? ( - <> - - Public - - ) : ( - <> - - Private - - )} - +
+ + {selectedPoll.visibility === "public" ? ( + <> + + Public + + ) : ( + <> + + Private + + )} + + + {selectedPoll.votingWeight === "ereputation" ? ( + <> + + eReputation Weighted + + ) : ( + <> + + 1P 1V + + )} + +
diff --git a/platforms/eVoting/src/app/(app)/create/page.tsx b/platforms/eVoting/src/app/(app)/create/page.tsx index feee8286..ab959bec 100644 --- a/platforms/eVoting/src/app/(app)/create/page.tsx +++ b/platforms/eVoting/src/app/(app)/create/page.tsx @@ -141,6 +141,17 @@ export default function CreatePoll() { } }, [watchedVotingWeight, watchedMode, setValue]); + // Prevent blind voting (private visibility) + eReputation weighted combination + React.useEffect(() => { + if (watchedVisibility === "private" && watchedVotingWeight === "ereputation") { + // If private visibility is selected and user tries to select eReputation, force to 1p1v + setValue("votingWeight", "1p1v"); + } else if (watchedVotingWeight === "ereputation" && watchedVisibility === "private") { + // If eReputation is selected and user tries to select private visibility, force to public + setValue("visibility", "public"); + } + }, [watchedVisibility, watchedVotingWeight, setValue]); + const addOption = () => { const newOptions = [...options, ""]; setOptions(newOptions); @@ -581,7 +592,7 @@ export default function CreatePoll() { {watchedVotingWeight === "ereputation" && (

- Votes will be weighted by each voter's eReputation score. The poll title will automatically include "(eReputation Weighted)". + Votes will be weighted by each voter's eReputation score.

)} {errors.votingWeight && ( diff --git a/platforms/eVoting/src/app/(app)/page.tsx b/platforms/eVoting/src/app/(app)/page.tsx index 6022b2ea..fae0b3cd 100644 --- a/platforms/eVoting/src/app/(app)/page.tsx +++ b/platforms/eVoting/src/app/(app)/page.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { Plus, Vote, BarChart3, LogOut, Eye, UserX, Search, ChevronLeft, ChevronRight } from "lucide-react"; +import { Plus, Vote, BarChart3, LogOut, Eye, UserX, Search, ChevronLeft, ChevronRight, ChartLine, CircleUser } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -163,6 +163,11 @@ export default function Home() { > Visibility {getSortIcon("visibility")} + + Voting Weight + @@ -211,6 +216,15 @@ export default function Home() { )} + + + {poll.votingWeight === "ereputation" ? ( + <>eReputation Weighted + ) : ( + <>1P 1V + )} + + {poll.group ? ( diff --git a/platforms/evoting-api/src/services/PollService.ts b/platforms/evoting-api/src/services/PollService.ts index f6cb2cd9..8b61fe15 100644 --- a/platforms/evoting-api/src/services/PollService.ts +++ b/platforms/evoting-api/src/services/PollService.ts @@ -216,6 +216,11 @@ export class PollService { throw new Error("eReputation weighted voting cannot be combined with Rank Based Voting (RBV). Please use Simple or PBV mode instead."); } + // Validate that blind voting (private visibility) and eReputation weighted are not combined + if (pollData.visibility === "private" && votingWeight === "ereputation") { + throw new Error("Blind voting (private visibility) cannot be combined with eReputation weighted voting."); + } + const pollDataForEntity = { title: pollData.title, mode: pollData.mode as "normal" | "point" | "rank", diff --git a/platforms/evoting-api/src/services/VoteService.ts b/platforms/evoting-api/src/services/VoteService.ts index 976d4537..32640619 100644 --- a/platforms/evoting-api/src/services/VoteService.ts +++ b/platforms/evoting-api/src/services/VoteService.ts @@ -44,20 +44,20 @@ export class VoteService { /** * Get reputation score for a user from reputation results using ename + * Returns the actual eReputation score (1-5) to be used as a multiplier */ private getReputationScore(ename: string, reputationResults: VoteReputationResult | null): number { if (!reputationResults || !reputationResults.results) { - return 1.0; // Default weight if no reputation data + return 1.0; // Default score if no reputation data } const memberRep = reputationResults.results.find((r: MemberReputation) => r.ename === ename); if (!memberRep) { - return 1.0; // Default weight if user not found in reputation results + return 1.0; // Default score if user not found in reputation results } - // Normalize score to a weight (assuming score is 0-5, convert to 0-1 weight) - // You can adjust this formula based on your needs - return Math.max(0.1, memberRep.score / 5.0); // Minimum weight of 0.1, max of 1.0 + // Return the actual eReputation score (1-5) to multiply votes/points by + return memberRep.score; } /** From e6cce94f8c53a2ca4548544e5227a3b99b0fa14b Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:27:24 +0530 Subject: [PATCH 02/12] chore: add logs to check weight --- platforms/eVoting/src/app/(app)/[id]/page.tsx | 11 +++++++---- .../evoting-api/src/services/VoteService.ts | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/[id]/page.tsx b/platforms/eVoting/src/app/(app)/[id]/page.tsx index b6148c93..e374ce53 100644 --- a/platforms/eVoting/src/app/(app)/[id]/page.tsx +++ b/platforms/eVoting/src/app/(app)/[id]/page.tsx @@ -493,11 +493,14 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { let isWinner: boolean; let percentage: number; - if (resultsData.mode === "point") { + if (resultsData.mode === "point" || resultsData.mode === "ereputation" || result.totalPoints !== undefined) { // Point-based voting: show total points and average - displayValue = `${result.totalPoints} points (avg: ${result.averagePoints})`; - isWinner = result.totalPoints === Math.max(...resultsData.results.map(r => r.totalPoints || 0)); - percentage = resultsData.totalVotes > 0 ? ((result.totalPoints || 0) / resultsData.results.reduce((sum, r) => sum + (r.totalPoints || 0), 0)) * 100 : 0; + // Check for totalPoints to handle eReputation weighted points-based voting (mode: "ereputation") + const totalPoints = result.totalPoints || 0; + displayValue = `${totalPoints} points${result.averagePoints !== undefined ? ` (avg: ${result.averagePoints})` : ''}`; + isWinner = totalPoints === Math.max(...resultsData.results.map(r => r.totalPoints || 0)); + const totalAllPoints = resultsData.results.reduce((sum, r) => sum + (r.totalPoints || 0), 0); + percentage = totalAllPoints > 0 ? (totalPoints / totalAllPoints) * 100 : 0; } else if (resultsData.mode === "rank") { // Rank-based voting: show winner status instead of misleading vote counts if (result.isTied) { diff --git a/platforms/evoting-api/src/services/VoteService.ts b/platforms/evoting-api/src/services/VoteService.ts index 32640619..47af92e2 100644 --- a/platforms/evoting-api/src/services/VoteService.ts +++ b/platforms/evoting-api/src/services/VoteService.ts @@ -48,15 +48,18 @@ export class VoteService { */ private getReputationScore(ename: string, reputationResults: VoteReputationResult | null): number { if (!reputationResults || !reputationResults.results) { + console.log(`[getReputationScore] No reputation results for ename: ${ename}, returning default 1.0`); return 1.0; // Default score if no reputation data } const memberRep = reputationResults.results.find((r: MemberReputation) => r.ename === ename); if (!memberRep) { + console.log(`[getReputationScore] User ${ename} not found in reputation results. Available enames: ${reputationResults.results.map(r => r.ename).join(', ')}, returning default 1.0`); return 1.0; // Default score if user not found in reputation results } // Return the actual eReputation score (1-5) to multiply votes/points by + console.log(`[getReputationScore] Found reputation for ${ename}: score=${memberRep.score}`); return memberRep.score; } @@ -210,6 +213,14 @@ export class VoteService { const isWeighted = this.isEReputationWeighted(poll); const reputationResults = isWeighted ? await this.getReputationResults(pollId) : null; + // Validate that reputation results exist if this is a weighted poll + if (isWeighted && !reputationResults) { + throw new Error("eReputation calculation is not yet complete. Results will be available once the calculation finishes."); + } + + // Debug logging + console.log(`[getPollResults] pollId: ${pollId}, isWeighted: ${isWeighted}, hasReputationResults: ${!!reputationResults}, reputationResultsCount: ${reputationResults?.results?.length || 0}`); + if (poll.mode === "normal") { // STEP 1: Calculate results normally (without eReputation weighting) const optionCounts: Record = {}; @@ -225,12 +236,16 @@ export class VoteService { const userEname = vote.user?.ename || null; const weight = isWeighted && userEname ? this.getReputationScore(userEname, reputationResults) : 1.0; + // Debug logging for normal voting + console.log(`[normal voting] userEname: ${userEname}, weight: ${weight}, isWeighted: ${isWeighted}, hasReputationResults: ${!!reputationResults}`); + vote.data.data.forEach(optionIndex => { const option = poll.options[parseInt(optionIndex)]; if (option) { // For weighted: multiply each vote by the voter's weight // For non-weighted: just add 1 const weightedVote = isWeighted ? weight : 1.0; + console.log(`[normal voting] option: ${option}, weightedVote: ${weightedVote}, adding to optionCounts`); optionCounts[option] += weightedVote; } }); @@ -294,6 +309,9 @@ export class VoteService { const userEname = vote.user?.ename || null; const weight = isWeighted && userEname ? this.getReputationScore(userEname, reputationResults) : 1.0; + // Debug logging for points-based voting + console.log(`[points voting] userEname: ${userEname}, weight: ${weight}, isWeighted: ${isWeighted}, hasReputationResults: ${!!reputationResults}`); + // Handle the actual stored format: {"0": 10, "1": 10, "2": 50, "3": 20, "5": 10} if (typeof vote.data.data === 'object' && !Array.isArray(vote.data.data)) { Object.entries(vote.data.data).forEach(([optionIndex, points]) => { @@ -304,6 +322,7 @@ export class VoteService { // For non-weighted: just add points // For weighted: multiply points by weight, then add const weightedPoints = isWeighted ? points * weight : points; + console.log(`[points voting] option: ${option}, points: ${points}, weight: ${weight}, weightedPoints: ${weightedPoints}, adding to optionPoints`); optionPoints[option] += weightedPoints; if (isWeighted && userEname) { From 4270cfde9307472bf47093c3d89ef7c3e97ca660 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:31:10 +0530 Subject: [PATCH 03/12] chore: fix ts error --- platforms/eVoting/src/lib/pollApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/eVoting/src/lib/pollApi.ts b/platforms/eVoting/src/lib/pollApi.ts index 2e986747..28142bf7 100644 --- a/platforms/eVoting/src/lib/pollApi.ts +++ b/platforms/eVoting/src/lib/pollApi.ts @@ -116,7 +116,7 @@ export interface PollResults { totalVotes: number; totalEligibleVoters?: number; turnout?: number; - mode?: "normal" | "point" | "rank"; + mode?: "normal" | "point" | "rank" | "ereputation"; results: PollResultOption[]; irvDetails?: IRVDetails; voterDetails?: VoterDetail[]; From bfa3e481b32cccc3f1b29c51c374ef9dbcd32b93 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:42:56 +0530 Subject: [PATCH 04/12] chore: fix eVoting adapter --- .../evoting-api/src/controllers/WebhookController.ts | 6 +++--- .../src/database/entities/VoteReputationResult.ts | 8 ++++---- .../mappings/vote-reputation-result.mapping.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/platforms/evoting-api/src/controllers/WebhookController.ts b/platforms/evoting-api/src/controllers/WebhookController.ts index 026cb4be..ed464717 100644 --- a/platforms/evoting-api/src/controllers/WebhookController.ts +++ b/platforms/evoting-api/src/controllers/WebhookController.ts @@ -235,8 +235,8 @@ export class WebhookController { } } - if (!pollId || !groupId) { - console.error("Missing pollId or groupId:", { pollId, groupId }); + if (!pollId) { + console.error("Missing pollId:", { pollId, groupId }); return res.status(400).send(); } @@ -258,7 +258,7 @@ export class WebhookController { // Create new result const newResult = voteReputationResultRepository.create({ pollId: pollId, - groupId: groupId, + groupId: groupId || null, results: results }); diff --git a/platforms/evoting-api/src/database/entities/VoteReputationResult.ts b/platforms/evoting-api/src/database/entities/VoteReputationResult.ts index c9c354eb..e77965cd 100644 --- a/platforms/evoting-api/src/database/entities/VoteReputationResult.ts +++ b/platforms/evoting-api/src/database/entities/VoteReputationResult.ts @@ -28,12 +28,12 @@ export class VoteReputationResult { @Column("uuid") pollId!: string; - @ManyToOne(() => Group) + @ManyToOne(() => Group, { nullable: true }) @JoinColumn({ name: "groupId" }) - group!: Group; + group!: Group | null; - @Column("uuid") - groupId!: string; + @Column("uuid", { nullable: true }) + groupId!: string | null; /** * Array of reputation scores for each group member diff --git a/platforms/evoting-api/src/web3adapter/mappings/vote-reputation-result.mapping.json b/platforms/evoting-api/src/web3adapter/mappings/vote-reputation-result.mapping.json index 104b78be..6e40b436 100644 --- a/platforms/evoting-api/src/web3adapter/mappings/vote-reputation-result.mapping.json +++ b/platforms/evoting-api/src/web3adapter/mappings/vote-reputation-result.mapping.json @@ -2,7 +2,7 @@ "tableName": "vote_reputation_results", "schemaId": "660e8400-e29b-41d4-a716-446655440102", "localToUniversalMap": { - "pollId": "pollId", + "pollId": "polls(pollId),pollId", "groupId": "groupId", "results": "results", "createdAt": "createdAt", From 13df71894fdd147d94240af5c2a3ec15c261def5 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:49:09 +0530 Subject: [PATCH 05/12] chore: fix erep pollId being null --- platforms/evoting-api/src/controllers/WebhookController.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/platforms/evoting-api/src/controllers/WebhookController.ts b/platforms/evoting-api/src/controllers/WebhookController.ts index ed464717..c66871b7 100644 --- a/platforms/evoting-api/src/controllers/WebhookController.ts +++ b/platforms/evoting-api/src/controllers/WebhookController.ts @@ -214,11 +214,7 @@ export class WebhookController { const pollIdValue = local.data.pollId.includes("(") ? local.data.pollId.split("(")[1].split(")")[0] : local.data.pollId; - pollId = await this.adapter.mappingDb.getLocalId(pollIdValue); - if (!pollId) { - console.error("Poll not found for globalId:", pollIdValue); - return res.status(400).send(); - } + pollId = pollIdValue; } // Resolve groupId from global to local ID From 0e2104478e5405efe785813932be83b73a569652 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:51:00 +0530 Subject: [PATCH 06/12] chore: add migration --- .../migrations/1763745645194-migration.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 platforms/evoting-api/src/database/migrations/1763745645194-migration.ts diff --git a/platforms/evoting-api/src/database/migrations/1763745645194-migration.ts b/platforms/evoting-api/src/database/migrations/1763745645194-migration.ts new file mode 100644 index 00000000..d1d94afa --- /dev/null +++ b/platforms/evoting-api/src/database/migrations/1763745645194-migration.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1763745645194 implements MigrationInterface { + name = 'Migration1763745645194' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "vote_reputation_results" DROP CONSTRAINT "FK_292664ed7ffc8782ab097e28ba5"`); + await queryRunner.query(`ALTER TABLE "vote_reputation_results" ALTER COLUMN "groupId" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "vote_reputation_results" ADD CONSTRAINT "FK_292664ed7ffc8782ab097e28ba5" FOREIGN KEY ("groupId") REFERENCES "group"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "vote_reputation_results" DROP CONSTRAINT "FK_292664ed7ffc8782ab097e28ba5"`); + await queryRunner.query(`ALTER TABLE "vote_reputation_results" ALTER COLUMN "groupId" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "vote_reputation_results" ADD CONSTRAINT "FK_292664ed7ffc8782ab097e28ba5" FOREIGN KEY ("groupId") REFERENCES "group"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + +} From cd2dba4454ff2f0a3c3295a395ecc97e8773b7ed Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:56:11 +0530 Subject: [PATCH 07/12] fix: refresh issue --- platforms/eVoting/src/app/(app)/[id]/page.tsx | 93 ++++++++++++------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/[id]/page.tsx b/platforms/eVoting/src/app/(app)/[id]/page.tsx index e374ce53..3517a5a9 100644 --- a/platforms/eVoting/src/app/(app)/[id]/page.tsx +++ b/platforms/eVoting/src/app/(app)/[id]/page.tsx @@ -585,15 +585,18 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { You voted:{" "} { (() => { - if (voteStatus?.vote?.data?.mode === "normal" && Array.isArray(voteStatus.vote.data.data)) { - const optionIndex = parseInt(voteStatus.vote.data.data[0] || "0"); + const voteData = voteStatus?.vote?.data; + if (!voteData) return "Unknown option"; + + if (voteData.mode === "normal" && Array.isArray(voteData.data)) { + const optionIndex = parseInt(voteData.data[0] || "0"); return selectedPoll.options[optionIndex] || "Unknown option"; - } else if (voteStatus?.vote?.data?.mode === "point" && Array.isArray(voteStatus.vote.data.data)) { - const pointData = voteStatus.vote.data.data as PointVoteData[]; - const totalPoints = pointData.reduce((sum, item) => sum + (item.points || 0), 0); + } else if (voteData.mode === "point" && typeof voteData.data === "object" && !Array.isArray(voteData.data)) { + // Point voting stores data as { "0": 50, "1": 50 } format + const totalPoints = Object.values(voteData.data as Record).reduce((sum, points) => sum + (points || 0), 0); return `distributed ${totalPoints} points across options`; - } else if (voteStatus?.vote?.data?.mode === "rank" && Array.isArray(voteStatus.vote.data.data)) { - const rankData = voteStatus.vote.data.data; + } else if (voteData.mode === "rank" && Array.isArray(voteData.data)) { + const rankData = voteData.data; const sortedRanks = [...rankData].sort((a, b) => a.points - b.points); const topChoice = selectedPoll.options[parseInt(sortedRanks[0]?.option || "0")]; return `ranked options (${topChoice} as 1st choice)`; @@ -621,29 +624,36 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
{selectedPoll.options.map((option, index) => { const isUserChoice = (() => { - if (voteStatus?.vote?.data?.mode === "normal" && Array.isArray(voteStatus.vote.data.data)) { - return voteStatus.vote.data.data.includes(index.toString()); - } else if (voteStatus?.vote?.data?.mode === "point" && Array.isArray(voteStatus.vote.data.data)) { - const pointData = voteStatus.vote.data.data as PointVoteData[]; - const optionPoints = pointData.find(item => item.option === index.toString())?.points || 0; - return optionPoints > 0; - } else if (voteStatus?.vote?.data?.mode === "rank" && Array.isArray(voteStatus.vote.data.data)) { - const rankData = voteStatus.vote.data.data as PointVoteData[]; - return rankData.some(item => item.option === index.toString()); + const voteData = voteStatus?.vote?.data; + if (!voteData) return false; + + if (voteData.mode === "normal" && Array.isArray(voteData.data)) { + return voteData.data.includes(index.toString()); + } else if (voteData.mode === "point" && typeof voteData.data === "object" && !Array.isArray(voteData.data)) { + // Point voting stores data as { "0": 50, "1": 50 } format + const points = (voteData.data as Record)[index.toString()]; + return points > 0; + } else if (voteData.mode === "rank" && Array.isArray(voteData.data)) { + const rankData = voteData.data; + return rankData.some((item: any) => item.option === index.toString()); } return false; })(); const userChoiceDetails = (() => { - if (voteStatus?.vote?.data?.mode === "normal" && Array.isArray(voteStatus.vote.data.data)) { - return voteStatus.vote.data.data.includes(index.toString()) ? "← You voted for this option" : null; - } else if (voteStatus?.vote?.data?.mode === "point" && Array.isArray(voteStatus.vote.data.data)) { - const pointData = voteStatus.vote.data.data as PointVoteData[]; - const optionPoints = pointData.find(item => item.option === index.toString())?.points || 0; - return optionPoints > 0 ? `← You gave ${optionPoints} points` : null; - } else if (voteStatus?.vote?.data?.mode === "rank" && Array.isArray(voteStatus.vote.data.data)) { - const rankData = voteStatus.vote.data.data as PointVoteData[]; - const optionRank = rankData.find(item => item.option === index.toString())?.points; + const voteData = voteStatus?.vote?.data; + if (!voteData) return null; + + if (voteData.mode === "normal" && Array.isArray(voteData.data)) { + return voteData.data.includes(index.toString()) ? "← You voted for this option" : null; + } else if (voteData.mode === "point" && typeof voteData.data === "object" && !Array.isArray(voteData.data)) { + // Point voting stores data as { "0": 50, "1": 50 } format + const points = (voteData.data as Record)[index.toString()]; + return points > 0 ? `← You gave ${points} points` : null; + } else if (voteData.mode === "rank" && Array.isArray(voteData.data)) { + const rankData = voteData.data; + const rankItem = rankData.find((item: any) => item.option === index.toString()); + const optionRank = rankItem?.points; return optionRank ? `← You ranked this ${optionRank}${optionRank === 1 ? 'st' : optionRank === 2 ? 'nd' : optionRank === 3 ? 'rd' : 'th'}` : null; } return null; @@ -1270,23 +1280,34 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { ? { ranks: rankVotes } : { points: pointVotes } } - onSigningComplete={(voteId) => { + onSigningComplete={async (voteId) => { setShowSigningInterface(false); - // Add a small delay to ensure backend has processed the vote - setTimeout(async () => { - try { - await fetchPoll(); - await fetchVoteData(); - } catch (error) { - console.error("Error during data refresh:", error); - } - }, 2000); // 2 second delay - toast({ title: "Success!", description: "Your vote has been signed and submitted.", }); + + // Immediately try to fetch vote data, then retry after a short delay if needed + const fetchWithRetry = async (retries = 3, delay = 1000) => { + for (let i = 0; i < retries; i++) { + try { + await fetchPoll(); + await fetchVoteData(); + // If successful, break out of retry loop + break; + } catch (error) { + console.error(`Error during data refresh (attempt ${i + 1}/${retries}):`, error); + if (i < retries - 1) { + // Wait before retrying + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + } + }; + + // Try immediately, then retry if needed + await fetchWithRetry(); }} onCancel={() => { setShowSigningInterface(false); From 8b6b05b45c6d942f2e521e8337158b137673becb Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:58:20 +0530 Subject: [PATCH 08/12] chore: remove logs --- platforms/evoting-api/src/services/VoteService.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/platforms/evoting-api/src/services/VoteService.ts b/platforms/evoting-api/src/services/VoteService.ts index 47af92e2..8b2fe225 100644 --- a/platforms/evoting-api/src/services/VoteService.ts +++ b/platforms/evoting-api/src/services/VoteService.ts @@ -48,18 +48,15 @@ export class VoteService { */ private getReputationScore(ename: string, reputationResults: VoteReputationResult | null): number { if (!reputationResults || !reputationResults.results) { - console.log(`[getReputationScore] No reputation results for ename: ${ename}, returning default 1.0`); return 1.0; // Default score if no reputation data } const memberRep = reputationResults.results.find((r: MemberReputation) => r.ename === ename); if (!memberRep) { - console.log(`[getReputationScore] User ${ename} not found in reputation results. Available enames: ${reputationResults.results.map(r => r.ename).join(', ')}, returning default 1.0`); return 1.0; // Default score if user not found in reputation results } // Return the actual eReputation score (1-5) to multiply votes/points by - console.log(`[getReputationScore] Found reputation for ${ename}: score=${memberRep.score}`); return memberRep.score; } @@ -218,9 +215,6 @@ export class VoteService { throw new Error("eReputation calculation is not yet complete. Results will be available once the calculation finishes."); } - // Debug logging - console.log(`[getPollResults] pollId: ${pollId}, isWeighted: ${isWeighted}, hasReputationResults: ${!!reputationResults}, reputationResultsCount: ${reputationResults?.results?.length || 0}`); - if (poll.mode === "normal") { // STEP 1: Calculate results normally (without eReputation weighting) const optionCounts: Record = {}; @@ -236,16 +230,12 @@ export class VoteService { const userEname = vote.user?.ename || null; const weight = isWeighted && userEname ? this.getReputationScore(userEname, reputationResults) : 1.0; - // Debug logging for normal voting - console.log(`[normal voting] userEname: ${userEname}, weight: ${weight}, isWeighted: ${isWeighted}, hasReputationResults: ${!!reputationResults}`); - vote.data.data.forEach(optionIndex => { const option = poll.options[parseInt(optionIndex)]; if (option) { // For weighted: multiply each vote by the voter's weight // For non-weighted: just add 1 const weightedVote = isWeighted ? weight : 1.0; - console.log(`[normal voting] option: ${option}, weightedVote: ${weightedVote}, adding to optionCounts`); optionCounts[option] += weightedVote; } }); @@ -309,9 +299,6 @@ export class VoteService { const userEname = vote.user?.ename || null; const weight = isWeighted && userEname ? this.getReputationScore(userEname, reputationResults) : 1.0; - // Debug logging for points-based voting - console.log(`[points voting] userEname: ${userEname}, weight: ${weight}, isWeighted: ${isWeighted}, hasReputationResults: ${!!reputationResults}`); - // Handle the actual stored format: {"0": 10, "1": 10, "2": 50, "3": 20, "5": 10} if (typeof vote.data.data === 'object' && !Array.isArray(vote.data.data)) { Object.entries(vote.data.data).forEach(([optionIndex, points]) => { @@ -322,7 +309,6 @@ export class VoteService { // For non-weighted: just add points // For weighted: multiply points by weight, then add const weightedPoints = isWeighted ? points * weight : points; - console.log(`[points voting] option: ${option}, points: ${points}, weight: ${weight}, weightedPoints: ${weightedPoints}, adding to optionPoints`); optionPoints[option] += weightedPoints; if (isWeighted && userEname) { From 825182587df3aa1f33559c04b72e0f075ab35e10 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 22:59:51 +0530 Subject: [PATCH 09/12] fix: ui on evoting --- platforms/eVoting/src/app/(app)/[id]/page.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/[id]/page.tsx b/platforms/eVoting/src/app/(app)/[id]/page.tsx index 3517a5a9..40911dc0 100644 --- a/platforms/eVoting/src/app/(app)/[id]/page.tsx +++ b/platforms/eVoting/src/app/(app)/[id]/page.tsx @@ -493,15 +493,7 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { let isWinner: boolean; let percentage: number; - if (resultsData.mode === "point" || resultsData.mode === "ereputation" || result.totalPoints !== undefined) { - // Point-based voting: show total points and average - // Check for totalPoints to handle eReputation weighted points-based voting (mode: "ereputation") - const totalPoints = result.totalPoints || 0; - displayValue = `${totalPoints} points${result.averagePoints !== undefined ? ` (avg: ${result.averagePoints})` : ''}`; - isWinner = totalPoints === Math.max(...resultsData.results.map(r => r.totalPoints || 0)); - const totalAllPoints = resultsData.results.reduce((sum, r) => sum + (r.totalPoints || 0), 0); - percentage = totalAllPoints > 0 ? (totalPoints / totalAllPoints) * 100 : 0; - } else if (resultsData.mode === "rank") { + if (resultsData.mode === "rank") { // Rank-based voting: show winner status instead of misleading vote counts if (result.isTied) { displayValue = "🏆 Tied Winner"; @@ -518,10 +510,18 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { // If multiple rounds, there might have been ties console.log(`[IRV Debug] Poll had ${resultsData.irvDetails.rounds.length} rounds, check console for tie warnings`); } + } else if (resultsData.mode === "point" || resultsData.mode === "ereputation") { + // Point-based voting: show total points and average + // Check for eReputation weighted points-based voting (mode: "ereputation") + const totalPoints = result.totalPoints || 0; + displayValue = `${totalPoints} points${result.averagePoints !== undefined ? ` (avg: ${result.averagePoints})` : ''}`; + isWinner = totalPoints === Math.max(...resultsData.results.map(r => r.totalPoints || 0)); + const totalAllPoints = resultsData.results.reduce((sum, r) => sum + (r.totalPoints || 0), 0); + percentage = totalAllPoints > 0 ? (totalPoints / totalAllPoints) * 100 : 0; } else { // Normal voting: show votes and percentage displayValue = `${result.votes} votes`; - isWinner = result.votes === Math.max(...resultsData.results.map(r => r.votes)); + isWinner = result.votes === Math.max(...resultsData.results.map(r => r.votes || 0)); percentage = resultsData.totalVotes > 0 ? (result.votes / resultsData.totalVotes) * 100 : 0; } From 70842d5576e70ea7e4b0ed70ec38f9946bd1f829 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 23:24:28 +0530 Subject: [PATCH 10/12] fix: rep based normal voting --- platforms/eVoting/src/app/(app)/[id]/page.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/[id]/page.tsx b/platforms/eVoting/src/app/(app)/[id]/page.tsx index 40911dc0..aec120dc 100644 --- a/platforms/eVoting/src/app/(app)/[id]/page.tsx +++ b/platforms/eVoting/src/app/(app)/[id]/page.tsx @@ -510,19 +510,22 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { // If multiple rounds, there might have been ties console.log(`[IRV Debug] Poll had ${resultsData.irvDetails.rounds.length} rounds, check console for tie warnings`); } - } else if (resultsData.mode === "point" || resultsData.mode === "ereputation") { + } else if (resultsData.mode === "point" || (resultsData.mode === "ereputation" && result.totalPoints !== undefined)) { // Point-based voting: show total points and average - // Check for eReputation weighted points-based voting (mode: "ereputation") + // For eReputation mode, only treat as points if totalPoints exists const totalPoints = result.totalPoints || 0; displayValue = `${totalPoints} points${result.averagePoints !== undefined ? ` (avg: ${result.averagePoints})` : ''}`; isWinner = totalPoints === Math.max(...resultsData.results.map(r => r.totalPoints || 0)); const totalAllPoints = resultsData.results.reduce((sum, r) => sum + (r.totalPoints || 0), 0); percentage = totalAllPoints > 0 ? (totalPoints / totalAllPoints) * 100 : 0; } else { - // Normal voting: show votes and percentage - displayValue = `${result.votes} votes`; - isWinner = result.votes === Math.max(...resultsData.results.map(r => r.votes || 0)); - percentage = resultsData.totalVotes > 0 ? (result.votes / resultsData.totalVotes) * 100 : 0; + // Normal voting (including eReputation weighted normal voting): show votes and percentage + // For eReputation weighted normal voting, use totalWeightedVotes for percentage calculation + const voteCount = result.votes || 0; + displayValue = `${voteCount} votes`; + const totalVotesForPercentage = resultsData.totalWeightedVotes || resultsData.totalVotes || 1; + isWinner = voteCount === Math.max(...resultsData.results.map(r => r.votes || 0)); + percentage = totalVotesForPercentage > 0 ? (voteCount / totalVotesForPercentage) * 100 : 0; } return ( From d476950444b82e2db1560d0bcc128576c787d39e Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 23:25:31 +0530 Subject: [PATCH 11/12] chore: fix build --- platforms/eVoting/src/lib/pollApi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/platforms/eVoting/src/lib/pollApi.ts b/platforms/eVoting/src/lib/pollApi.ts index 28142bf7..c0dadbb9 100644 --- a/platforms/eVoting/src/lib/pollApi.ts +++ b/platforms/eVoting/src/lib/pollApi.ts @@ -114,6 +114,7 @@ export interface VoterDetail { export interface PollResults { poll: Poll; totalVotes: number; + totalWeightedVotes?: number; totalEligibleVoters?: number; turnout?: number; mode?: "normal" | "point" | "rank" | "ereputation"; From 9bcab56e25f9c94a5cb0f0fc169bdbc91abb6895 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Fri, 21 Nov 2025 23:29:40 +0530 Subject: [PATCH 12/12] chore: disable erep on ranked and private --- platforms/eVoting/src/app/(app)/[id]/page.tsx | 4 ++-- platforms/eVoting/src/app/(app)/create/page.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/[id]/page.tsx b/platforms/eVoting/src/app/(app)/[id]/page.tsx index aec120dc..0219be44 100644 --- a/platforms/eVoting/src/app/(app)/[id]/page.tsx +++ b/platforms/eVoting/src/app/(app)/[id]/page.tsx @@ -520,10 +520,10 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { percentage = totalAllPoints > 0 ? (totalPoints / totalAllPoints) * 100 : 0; } else { // Normal voting (including eReputation weighted normal voting): show votes and percentage - // For eReputation weighted normal voting, use totalWeightedVotes for percentage calculation const voteCount = result.votes || 0; displayValue = `${voteCount} votes`; - const totalVotesForPercentage = resultsData.totalWeightedVotes || resultsData.totalVotes || 1; + // Calculate total from results array for percentage (handles both weighted and non-weighted) + const totalVotesForPercentage = resultsData.results.reduce((sum, r) => sum + (r.votes || 0), 0); isWinner = voteCount === Math.max(...resultsData.results.map(r => r.votes || 0)); percentage = totalVotesForPercentage > 0 ? (voteCount / totalVotesForPercentage) * 100 : 0; } diff --git a/platforms/eVoting/src/app/(app)/create/page.tsx b/platforms/eVoting/src/app/(app)/create/page.tsx index ab959bec..92536e02 100644 --- a/platforms/eVoting/src/app/(app)/create/page.tsx +++ b/platforms/eVoting/src/app/(app)/create/page.tsx @@ -563,7 +563,7 @@ export default function CreatePoll() {