diff --git a/src/components/UniversalPostRenderer.tsx b/src/components/UniversalPostRenderer.tsx
index 22aa370..a2b1fac 100644
--- a/src/components/UniversalPostRenderer.tsx
+++ b/src/components/UniversalPostRenderer.tsx
@@ -15,6 +15,7 @@ import {
enableWafrnTextAtom,
imgCDNAtom,
} from "~/utils/atoms";
+import { useGetOneToOneState } from "~/utils/followState";
import { useHydratedEmbed } from "~/utils/useHydrated";
import {
constructConstellationQuery,
@@ -2221,6 +2222,51 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
}),
);
+ // Check if user has already voted for each option in this poll
+ const userVotesA = useGetOneToOneState(
+ agent?.did
+ ? {
+ target: pollUri,
+ user: agent?.did,
+ collection: "app.reddwarf.poll.vote.a",
+ path: ".subject.uri",
+ }
+ : undefined,
+ );
+
+ const userVotesB = useGetOneToOneState(
+ agent?.did
+ ? {
+ target: pollUri,
+ user: agent?.did,
+ collection: "app.reddwarf.poll.vote.b",
+ path: ".subject.uri",
+ }
+ : undefined,
+ );
+
+ const userVotesC = useGetOneToOneState(
+ agent?.did
+ ? {
+ target: pollUri,
+ user: agent?.did,
+ collection: "app.reddwarf.poll.vote.c",
+ path: ".subject.uri",
+ }
+ : undefined,
+ );
+
+ const userVotesD = useGetOneToOneState(
+ agent?.did
+ ? {
+ target: pollUri,
+ user: agent?.did,
+ collection: "app.reddwarf.poll.vote.d",
+ path: ".subject.uri",
+ }
+ : undefined,
+ );
+
if (isLoading) {
return (
@@ -2292,20 +2338,78 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
if (!agent || isExpired) return;
try {
- await agent.com.atproto.repo.createRecord({
- collection: `app.reddwarf.poll.vote.${option}`,
- repo: agent.assertDid,
- record: {
- $type: `app.reddwarf.poll.vote.${option}`,
- subject: {
- $type: "com.atproto.repo.strongRef",
- uri: pollUri,
- cid: pollRecord.cid,
+ // Get existing votes for this option
+ const existingVotes = (() => {
+ switch (option) {
+ case "a":
+ return userVotesA;
+ case "b":
+ return userVotesB;
+ case "c":
+ return userVotesC;
+ case "d":
+ return userVotesD;
+ default:
+ return [];
+ }
+ })();
+
+ // If user has already voted for this option, delete all votes (unvote)
+ if (existingVotes && existingVotes.length > 0) {
+ for (const voteUri of existingVotes) {
+ const match = voteUri.match(/at:\/\/(.+)\/(.+)\/(.+)/);
+ if (match) {
+ const [, did, collection, rkey] = match;
+ await agent.com.atproto.repo.deleteRecord({
+ repo: did,
+ collection,
+ rkey,
+ });
+ }
+ }
+ } else {
+ // If not voted for this option, create new vote
+ // First, delete votes from other options if poll doesn't allow multiple votes
+ if (!poll.multiple) {
+ const otherVotes = [
+ ...(userVotesA || []),
+ ...(userVotesB || []),
+ ...(userVotesC || []),
+ ...(userVotesD || []),
+ ].filter((vote) => {
+ // Filter out votes for the current option
+ return !vote.includes(`app.reddwarf.poll.vote.${option}`);
+ });
+
+ for (const voteUri of otherVotes) {
+ const match = voteUri.match(/at:\/\/(.+)\/(.+)\/(.+)/);
+ if (match) {
+ const [, did, collection, rkey] = match;
+ await agent.com.atproto.repo.deleteRecord({
+ repo: did,
+ collection,
+ rkey,
+ });
+ }
+ }
+ }
+
+ // Create new vote
+ await agent.com.atproto.repo.createRecord({
+ collection: `app.reddwarf.poll.vote.${option}`,
+ repo: agent.assertDid,
+ record: {
+ $type: `app.reddwarf.poll.vote.${option}`,
+ subject: {
+ $type: "com.atproto.repo.strongRef",
+ uri: pollUri,
+ cid: pollRecord.cid,
+ },
+ createdAt: new Date().toISOString(),
},
- createdAt: new Date().toISOString(),
- },
- // Let PDS generate rkey automatically
- });
+ // Let PDS generate rkey automatically
+ });
+ }
} catch (error) {
console.error("Failed to vote:", error);
}
@@ -2337,24 +2441,60 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
{options.map((optionText, index) => {
const optionKey = ["a", "b", "c", "d"][index];
+ // Check if user has voted for this option
+ const userVotesForOption = (() => {
+ switch (optionKey) {
+ case "a":
+ return userVotesA;
+ case "b":
+ return userVotesB;
+ case "c":
+ return userVotesC;
+ case "d":
+ return userVotesD;
+ default:
+ return [];
+ }
+ })();
+
+ const hasVotedForOption =
+ userVotesForOption && userVotesForOption.length > 0;
+ const voteCount =
+ voteData.find((v) => v.option === optionKey)?.count ?? 0;
+ const votePercentage =
+ totalVotes > 0 ? (voteCount / totalVotes) * 100 : 0;
+
return (
!isExpired && handleVote(optionKey)}
>
+ {/* Vote percentage bar - always show */}
+
+
{/* Option text */}
{optionText}
+ {hasVotedForOption && (
+
+ {poll.multiple ? "✓" : "✓ (click to remove)"}
+
+ )}
{/* Vote count */}
- {voteData.find(v => v.option === optionKey)?.count ?? 0}
+ {votePercentage.toFixed(0)}%
);