From 524bab27de1a635c7ce538fdc858f718eb3943c4 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Wed, 29 Oct 2025 23:35:43 -0400 Subject: [PATCH] make it much clearer poll votes are public --- .../[rkey]/PublishedPollBlock.tsx | 150 +++++++++++++----- .../[rkey]/getVoterIdentities.ts | 35 ++++ components/Popover.tsx | 2 +- 3 files changed, 145 insertions(+), 42 deletions(-) create mode 100644 app/lish/[did]/[publication]/[rkey]/getVoterIdentities.ts diff --git a/app/lish/[did]/[publication]/[rkey]/PublishedPollBlock.tsx b/app/lish/[did]/[publication]/[rkey]/PublishedPollBlock.tsx index c0eac21e..cab8f0f5 100644 --- a/app/lish/[did]/[publication]/[rkey]/PublishedPollBlock.tsx +++ b/app/lish/[did]/[publication]/[rkey]/PublishedPollBlock.tsx @@ -1,6 +1,10 @@ "use client"; -import { PubLeafletBlocksPoll, PubLeafletPollDefinition, PubLeafletPollVote } from "lexicons/api"; +import { + PubLeafletBlocksPoll, + PubLeafletPollDefinition, + PubLeafletPollVote, +} from "lexicons/api"; import { useState, useEffect } from "react"; import { ButtonPrimary, ButtonSecondary } from "components/Buttons"; import { useIdentityData } from "components/IdentityProvider"; @@ -10,6 +14,9 @@ import { PollData } from "./fetchPollData"; import { Popover } from "components/Popover"; import LoginForm from "app/login/LoginForm"; import { BlueskyTiny } from "components/Icons/BlueskyTiny"; +import { getVoterIdentities, VoterIdentity } from "./getVoterIdentities"; +import { Json } from "supabase/database.types"; +import { InfoSmall } from "components/Icons/InfoSmall"; // Helper function to extract the first option from a vote record const getVoteOption = (voteRecord: any): string | null => { @@ -101,7 +108,7 @@ export const PublishedPollBlock = (props: { setShowResults={setShowResults} optimisticVote={optimisticVote} /> - {isCreator && !hasVoted && ( + {!hasVoted && (
+ {identity?.atp_did ? ( + - See Results - + {isVoting ? "Voting..." : "Vote!"} + + ) : ( + + Login to vote + + } + > + {isClient && ( + + )} + )}
- {identity?.atp_did ? ( - - {isVoting ? "Voting..." : "Vote!"} - - ) : ( - - Login to vote - - } - > - {isClient && ( - - )} - - )} )} @@ -221,16 +227,17 @@ const PollResults = (props: { return ( <> {pollRecord.options.map((option, index) => { - const votes = allVotes.filter( + const voteRecords = allVotes.filter( (v) => getVoteOption(v.record) === index.toString(), - ).length; - const isWinner = totalVotes > 0 && votes === highestVotes; + ); + const isWinner = totalVotes > 0 && voteRecords.length === highestVotes; return ( @@ -240,9 +247,70 @@ const PollResults = (props: { ); }; +const VoterListPopover = (props: { + votes: number; + voteRecords: { voter_did: string; record: Json }[]; +}) => { + const [voterIdentities, setVoterIdentities] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [hasFetched, setHasFetched] = useState(false); + + const handleOpenChange = async () => { + if (!hasFetched && props.voteRecords.length > 0) { + setIsLoading(true); + setHasFetched(true); + try { + const dids = props.voteRecords.map((v) => v.voter_did); + const identities = await getVoterIdentities(dids); + setVoterIdentities(identities); + } catch (error) { + console.error("Failed to fetch voter identities:", error); + } finally { + setIsLoading(false); + } + } + }; + + return ( + + {props.votes} + + } + onOpenChange={handleOpenChange} + className="w-64 max-h-80" + > + {isLoading ? ( +
+
Loading...
+
+ ) : ( +
+ {voterIdentities.map((voter) => ( + + @{voter.handle || voter.did} + + ))} +
+ )} +
+ ); +}; + const PollResult = (props: { option: PubLeafletPollDefinition.Option; votes: number; + voteRecords: { voter_did: string; record: Json }[]; totalVotes: number; winner: boolean; }) => { @@ -258,7 +326,7 @@ const PollResult = (props: { className="pollResultContent text-accent-contrast relative flex gap-2 justify-between z-10" >
{props.option.text}
-
{props.votes}
+
{ + const identities = await Promise.all( + dids.map(async (did) => { + try { + const resolved = await idResolver.did.resolve(did); + const handle = resolved?.alsoKnownAs?.[0] + ? resolved.alsoKnownAs[0].slice(5) // Remove "at://" prefix + : null; + return { + did, + handle, + }; + } catch (error) { + console.error(`Failed to resolve DID ${did}:`, error); + return { + did, + handle: null, + }; + } + }), + ); + + return identities; +} diff --git a/components/Popover.tsx b/components/Popover.tsx index e30fcdfb..0816d76e 100644 --- a/components/Popover.tsx +++ b/components/Popover.tsx @@ -43,7 +43,7 @@ export const Popover = (props: { max-w-(--radix-popover-content-available-width) max-h-(--radix-popover-content-available-height) border border-border rounded-md shadow-md - overflow-y-scroll no-scrollbar + overflow-y-scroll ${props.className} `} side={props.side} -- 2.51.2