diff --git a/src/components/UniversalPostRenderer.tsx b/src/components/UniversalPostRenderer.tsx
index 10bb9e0..987588c 100644
--- a/src/components/UniversalPostRenderer.tsx
+++ b/src/components/UniversalPostRenderer.tsx
@@ -1,5 +1,5 @@
import * as ATPAPI from "@atproto/api";
-import { useQuery } from "@tanstack/react-query";
+import { useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import DOMPurify from "dompurify";
import { useAtom } from "jotai";
@@ -14,11 +14,11 @@ import {
enableBridgyTextAtom,
enableWafrnTextAtom,
imgCDNAtom,
+ slingshotURLAtom,
} from "~/utils/atoms";
import { useGetOneToOneState } from "~/utils/followState";
import { useHydratedEmbed } from "~/utils/useHydrated";
import {
- constructConstellationQuery,
useQueryArbitrary,
useQueryConstellation,
useQueryIdentity,
@@ -2152,6 +2152,9 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
// Query vote counts for each option
const [constellationurl] = useAtom(constellationURLAtom);
+ const [imgcdn] = useAtom(imgCDNAtom);
+ const [slingshoturl] = useAtom(slingshotURLAtom);
+ const queryClient = useQueryClient();
const { data: voteCountsA } = useQueryConstellation({
method: "/links/count/distinct-dids",
@@ -2182,45 +2185,33 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
});
// Query first page of voters for each option to get PFPs
- const { data: votersA } = useQuery(
- constructConstellationQuery({
- constellation: constellationurl,
- method: "/links",
- target: pollUri,
- collection: "app.reddwarf.poll.vote.a",
- path: ".subject.uri",
- }),
- );
+ const { data: votersA } = useQueryConstellation({
+ method: "/links",
+ target: pollUri,
+ collection: "app.reddwarf.poll.vote.a",
+ path: ".subject.uri",
+ });
- const { data: votersB } = useQuery(
- constructConstellationQuery({
- constellation: constellationurl,
- method: "/links",
- target: pollUri,
- collection: "app.reddwarf.poll.vote.b",
- path: ".subject.uri",
- }),
- );
+ const { data: votersB } = useQueryConstellation({
+ method: "/links",
+ target: pollUri,
+ collection: "app.reddwarf.poll.vote.b",
+ path: ".subject.uri",
+ });
- const { data: votersC } = useQuery(
- constructConstellationQuery({
- constellation: constellationurl,
- method: "/links",
- target: pollUri,
- collection: "app.reddwarf.poll.vote.c",
- path: ".subject.uri",
- }),
- );
+ const { data: votersC } = useQueryConstellation({
+ method: "/links",
+ target: pollUri,
+ collection: "app.reddwarf.poll.vote.c",
+ path: ".subject.uri",
+ });
- const { data: votersD } = useQuery(
- constructConstellationQuery({
- constellation: constellationurl,
- method: "/links",
- target: pollUri,
- collection: "app.reddwarf.poll.vote.d",
- path: ".subject.uri",
- }),
- );
+ const { data: votersD } = useQueryConstellation({
+ method: "/links",
+ target: pollUri,
+ collection: "app.reddwarf.poll.vote.d",
+ path: ".subject.uri",
+ });
// Check if user has already voted for each option in this poll
const userVotesA = useGetOneToOneState(
@@ -2267,26 +2258,9 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
: undefined,
);
- if (isLoading) {
- return (
-
- );
- }
- if (error || !pollRecord?.value) {
- return Failed to load poll
;
- }
- const poll = pollRecord.value as {
+ const poll = pollRecord?.value as {
a: string;
b: string;
c?: string;
@@ -2297,44 +2271,64 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
};
const options = [poll.a, poll.b, poll.c, poll.d].filter(Boolean);
- const isExpired = false //poll.expiry ? new Date(poll.expiry) < new Date() : false;
-
- // todo unused waiting for private polls
- // undefined for public polls which equals never expires
- const formattedDate = undefined;
- // const formattedDate = poll.expiry
- // ? new Date(poll.expiry).toLocaleDateString("en-US", {
- // month: "short",
- // day: "numeric",
- // hour: "numeric",
- // minute: "2-digit",
- // })
- // : null;
// Calculate vote counts
const voteData = [
{
option: "a",
count: parseInt((voteCountsA as any)?.total || "0"),
- voters: (votersA as any)?.linking_records || [],
+ voters: votersA?.linking_records || [],
},
{
option: "b",
count: parseInt((voteCountsB as any)?.total || "0"),
- voters: (votersB as any)?.linking_records || [],
+ voters: votersB?.linking_records || [],
},
{
option: "c",
count: parseInt((voteCountsC as any)?.total || "0"),
- voters: (votersC as any)?.linking_records || [],
+ voters: votersC?.linking_records || [],
},
{
option: "d",
count: parseInt((voteCountsD as any)?.total || "0"),
- voters: (votersD as any)?.linking_records || [],
+ voters: votersD?.linking_records || [],
},
].slice(0, options.length);
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (error || !pollRecord?.value) {
+ return Failed to load poll
;
+ }
+ const isExpired = false; //poll.expiry ? new Date(poll.expiry) < new Date() : false;
+
+ // todo unused waiting for private polls
+ // undefined for public polls which equals never expires
+ const formattedDate = undefined;
+ // const formattedDate = poll.expiry
+ // ? new Date(poll.expiry).toLocaleDateString("en-US", {
+ // month: "short",
+ // day: "numeric",
+ // hour: "numeric",
+ // minute: "2-digit",
+ // })
+ // : null;
+
+
const totalVotes = voteData.reduce((sum, item) => sum + item.count, 0);
const handleVote = async (option: string) => {
@@ -2460,6 +2454,7 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
}
})();
+ const rowData = voteData.find((v) => v.option === optionKey);
const hasVotedForOption =
userVotesForOption && userVotesForOption.length > 0;
const voteCount =
@@ -2467,6 +2462,11 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
const votePercentage =
totalVotes > 0 ? (voteCount / totalVotes) * 100 : 0;
+ // Extract just the DIDs we want to show (top 2)
+ const topVoters = rowData?.voters
+ .filter(v => !!v.did)
+ .slice(0, 2) || [];
+
return (
- {/* Vote count */}
-
- {votePercentage.toFixed(0)}%
-
+ {/* Avatar circles and vote count */}
+
+ {/* Avatar circles - semi overlapping */}
+
+ {topVoters.length > 0 && (
+
+ {topVoters.map((voter, idx) => (
+
+ {/* The Component handles the async fetch! */}
+
+
+ ))}
+
+ )}
+
+ {/* Vote count */}
+
+ {votePercentage.toFixed(0)}%
+
+
);
})}
@@ -2509,9 +2531,15 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
{/* Expires {formattedDate} */}
- {formattedDate ? !isExpired ? (
- Expires {formattedDate}
- ) : (Expired at {formattedDate}) : Never expires}
+ {formattedDate ? (
+ !isExpired ? (
+ Expires {formattedDate}
+ ) : (
+ Expired at {formattedDate}
+ )
+ ) : (
+ Never expires
+ )}
{/* Status */}
@@ -2531,6 +2559,38 @@ function PollEmbed({ did, rkey }: { did: string; rkey: string }) {
);
}
+function PollOptionAvatar({
+ did,
+}: {
+ did: string;
+}) {
+ const [imgcdn] = useAtom(imgCDNAtom);
+ // Each avatar handles its own data fetching
+ // If this specific DID is already in cache, it loads instantly
+ const { data: profileRecord } = useQueryProfile(`at://${did}/app.bsky.actor.profile/self`)
+
+ //const profile = profileRecord?.value as ATPAPI.AppBskyActorProfile.Record;
+ const avatarUrl = getAvatarUrl(profileRecord, did, imgcdn);
+
+ if (!avatarUrl) {
+ // Fallback grey circle
+ return ;
+ }
+
+ return (
+
{
+ const target = e.target as HTMLImageElement;
+ target.style.display = "none";
+ target.parentElement!.style.backgroundColor = "#6b7280";
+ }}
+ />
+ );
+}
+
function PostEmbeds({
embed,
moderation,