Skip to content

Commit fe075e5

Browse files
authored
feat: show what user voted for in blind pbv and rcv (#818)
1 parent 5b48ef7 commit fe075e5

1 file changed

Lines changed: 175 additions & 22 deletions

File tree

  • platforms/evoting/client/src/app/(app)/[id]

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

Lines changed: 175 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
907907
onVoteSubmitted={onVoteSubmitted}
908908
/>
909909
) : selectedPoll.visibility === "private" && (selectedPoll.mode === "point" || selectedPoll.mode === "rank") ? (
910-
// For private PBV/RBV polls that user has voted on, show vote details with privacy info
910+
// For private PBV/RBV polls that user has voted on, show read-only vote values
911911
<div className="space-y-6">
912912
{/* Privacy limitation warning for private PBV/RBV */}
913913
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg">
@@ -925,17 +925,92 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
925925
</div>
926926
</div>
927927

928-
{/* Vote submitted confirmation */}
929-
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
930-
<div className="flex items-center">
931-
<CheckCircle className="text-green-500 h-5 w-5 mr-2" />
932-
<div>
933-
<h3 className="text-lg font-semibold text-green-900">Vote Submitted</h3>
934-
<p className="text-sm text-green-700">
935-
Your vote has been submitted. Your identity is hidden in results. Results will be shown when the poll ends.
936-
</p>
937-
</div>
938-
</div>
928+
<div>
929+
<h3 className="text-lg font-semibold text-gray-900 mb-4">
930+
Voting Options:
931+
</h3>
932+
{(() => {
933+
const rawVote = selectedContextVoteStatus?.vote;
934+
const fallbackVoteData = selectedPoll.mode === "point"
935+
? (rawVote?.points ? { mode: "point", data: rawVote.points } : null)
936+
: selectedPoll.mode === "rank" && rawVote?.ranks
937+
? {
938+
mode: "rank",
939+
data: [{
940+
option: "ranks",
941+
points: Object.entries(rawVote.ranks as Record<string, string | number>).reduce((acc, [key, value]) => {
942+
const keyNum = Number(key);
943+
const valNum = Number(value);
944+
if (!Number.isNaN(keyNum) && keyNum >= 1 && keyNum <= selectedPoll.options.length && !Number.isNaN(valNum)) {
945+
acc[valNum] = keyNum;
946+
} else if (!Number.isNaN(keyNum) && !Number.isNaN(valNum)) {
947+
acc[keyNum] = valNum;
948+
}
949+
return acc;
950+
}, {} as Record<number, number>)
951+
}]
952+
}
953+
: null;
954+
const voteData = rawVote?.data ?? fallbackVoteData;
955+
if (!voteData) return null;
956+
957+
return (
958+
<div className="space-y-3">
959+
{selectedPoll.options.map((option, index) => {
960+
const isUserChoice = (() => {
961+
if (voteData.mode === "point" && typeof voteData.data === "object") {
962+
return Number((voteData.data as Record<string, number>)[index]) > 0;
963+
} else if (voteData.mode === "rank" && Array.isArray(voteData.data)) {
964+
const rawRankData = voteData.data[0]?.points;
965+
const rankData = rawRankData?.ranks && typeof rawRankData.ranks === "object"
966+
? rawRankData.ranks
967+
: rawRankData;
968+
return rankData && typeof rankData[index] === "number";
969+
}
970+
return false;
971+
})();
972+
973+
const userChoiceDetails = (() => {
974+
if (voteData.mode === "point" && typeof voteData.data === "object") {
975+
const points = Number((voteData.data as Record<string, number>)[index] || 0);
976+
return points > 0 ? `← You gave ${points} points` : null;
977+
} else if (voteData.mode === "rank" && Array.isArray(voteData.data)) {
978+
const rawRankData = voteData.data[0]?.points;
979+
const rankData = rawRankData?.ranks && typeof rawRankData.ranks === "object"
980+
? rawRankData.ranks
981+
: rawRankData;
982+
const rank = rankData?.[index];
983+
return typeof rank === "number"
984+
? `← You ranked this ${rank}${rank === 1 ? "st" : rank === 2 ? "nd" : rank === 3 ? "rd" : "th"}`
985+
: null;
986+
}
987+
return null;
988+
})();
989+
990+
return (
991+
<div
992+
key={index}
993+
className={`flex items-center space-x-3 p-3 border rounded-lg ${isUserChoice
994+
? "bg-green-50 border-green-200"
995+
: "bg-gray-50 border-gray-200 opacity-60"
996+
}`}
997+
>
998+
<div className="flex-1">
999+
<Label className={`text-base ${isUserChoice ? "text-green-900 font-medium" : "text-gray-500"}`}>
1000+
{option}
1001+
</Label>
1002+
{userChoiceDetails && (
1003+
<div className="mt-1 text-sm text-green-600">
1004+
<span className="font-medium">{userChoiceDetails}</span>
1005+
</div>
1006+
)}
1007+
</div>
1008+
</div>
1009+
);
1010+
})}
1011+
</div>
1012+
);
1013+
})()}
9391014
</div>
9401015
</div>
9411016
) : null}
@@ -973,16 +1048,94 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
9731048
</div>
9741049

9751050
{selectedContextHasVoted ? (
976-
// User has already voted on private PBV/RBV and has no delegations
977-
<div className="bg-green-50 border border-green-200 rounded-lg p-4 hidden">
978-
<div className="flex items-center">
979-
<CheckCircle className="text-green-500 h-5 w-5 mr-2" />
980-
<div>
981-
<h3 className="text-lg font-semibold text-green-900">Vote Submitted</h3>
982-
<p className="text-sm text-green-700">
983-
Your vote has been submitted. Your identity is hidden in results. Results will be shown when the poll ends.
984-
</p>
985-
</div>
1051+
// User has already voted on private PBV/RBV - show read-only selections
1052+
<div className="space-y-4">
1053+
<div>
1054+
<h3 className="text-lg font-semibold text-gray-900 mb-4">
1055+
Voting Options:
1056+
</h3>
1057+
{(() => {
1058+
const rawVote = selectedContextVoteStatus?.vote;
1059+
const fallbackVoteData = selectedPoll.mode === "point"
1060+
? (rawVote?.points ? { mode: "point", data: rawVote.points } : null)
1061+
: selectedPoll.mode === "rank" && rawVote?.ranks
1062+
? {
1063+
mode: "rank",
1064+
data: [{
1065+
option: "ranks",
1066+
points: Object.entries(rawVote.ranks as Record<string, string | number>).reduce((acc, [key, value]) => {
1067+
const keyNum = Number(key);
1068+
const valNum = Number(value);
1069+
if (!Number.isNaN(keyNum) && keyNum >= 1 && keyNum <= selectedPoll.options.length && !Number.isNaN(valNum)) {
1070+
acc[valNum] = keyNum;
1071+
} else if (!Number.isNaN(keyNum) && !Number.isNaN(valNum)) {
1072+
acc[keyNum] = valNum;
1073+
}
1074+
return acc;
1075+
}, {} as Record<number, number>)
1076+
}]
1077+
}
1078+
: null;
1079+
const voteData = rawVote?.data ?? fallbackVoteData;
1080+
if (!voteData) return null;
1081+
1082+
return (
1083+
<div className="space-y-3">
1084+
{selectedPoll.options.map((option, index) => {
1085+
const isUserChoice = (() => {
1086+
if (voteData.mode === "point" && typeof voteData.data === "object") {
1087+
return Number((voteData.data as Record<string, number>)[index]) > 0;
1088+
} else if (voteData.mode === "rank" && Array.isArray(voteData.data)) {
1089+
const rawRankData = voteData.data[0]?.points;
1090+
const rankData = rawRankData?.ranks && typeof rawRankData.ranks === "object"
1091+
? rawRankData.ranks
1092+
: rawRankData;
1093+
return rankData && typeof rankData[index] === "number";
1094+
}
1095+
return false;
1096+
})();
1097+
1098+
const userChoiceDetails = (() => {
1099+
if (voteData.mode === "point" && typeof voteData.data === "object") {
1100+
const points = Number((voteData.data as Record<string, number>)[index] || 0);
1101+
return points > 0 ? `← You gave ${points} points` : null;
1102+
} else if (voteData.mode === "rank" && Array.isArray(voteData.data)) {
1103+
const rawRankData = voteData.data[0]?.points;
1104+
const rankData = rawRankData?.ranks && typeof rawRankData.ranks === "object"
1105+
? rawRankData.ranks
1106+
: rawRankData;
1107+
const rank = rankData?.[index];
1108+
return typeof rank === "number"
1109+
? `← You ranked this ${rank}${rank === 1 ? "st" : rank === 2 ? "nd" : rank === 3 ? "rd" : "th"}`
1110+
: null;
1111+
}
1112+
return null;
1113+
})();
1114+
1115+
return (
1116+
<div
1117+
key={index}
1118+
className={`flex items-center space-x-3 p-3 border rounded-lg ${isUserChoice
1119+
? "bg-green-50 border-green-200"
1120+
: "bg-gray-50 border-gray-200 opacity-60"
1121+
}`}
1122+
>
1123+
<div className="flex-1">
1124+
<Label className={`text-base ${isUserChoice ? "text-green-900 font-medium" : "text-gray-500"}`}>
1125+
{option}
1126+
</Label>
1127+
{userChoiceDetails && (
1128+
<div className="mt-1 text-sm text-green-600">
1129+
<span className="font-medium">{userChoiceDetails}</span>
1130+
</div>
1131+
)}
1132+
</div>
1133+
</div>
1134+
);
1135+
})}
1136+
</div>
1137+
);
1138+
})()}
9861139
</div>
9871140
</div>
9881141
) : (

0 commit comments

Comments
 (0)