tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
enable optimistic votes and prompt to signin
awarm.space
4 months ago
13198ba7
fb907fe3
+47
-11
1 changed file
expand all
collapse all
unified
split
app
lish
[did]
[publication]
[rkey]
PublishedPollBlock.tsx
+47
-11
app/lish/[did]/[publication]/[rkey]/PublishedPollBlock.tsx
···
20
20
const [selectedOption, setSelectedOption] = useState<string | null>(null);
21
21
const [isVoting, setIsVoting] = useState(false);
22
22
const [showResults, setShowResults] = useState(false);
23
23
+
const [optimisticVote, setOptimisticVote] = useState<{
24
24
+
option: string;
25
25
+
voter_did: string;
26
26
+
} | null>(null);
23
27
let pollRecord = props.pollData.record as PubLeafletPollDefinition.Record;
24
28
let [isClient, setIsClient] = useState(false);
25
29
useEffect(() => {
···
27
31
}, []);
28
32
29
33
const handleVote = async () => {
30
30
-
if (!selectedOption) return;
34
34
+
if (!selectedOption || !identity?.atp_did) return;
31
35
32
36
setIsVoting(true);
37
37
+
38
38
+
// Optimistically add the vote
39
39
+
setOptimisticVote({
40
40
+
option: selectedOption,
41
41
+
voter_did: identity.atp_did,
42
42
+
});
43
43
+
setShowResults(true);
44
44
+
33
45
try {
34
46
const result = await voteOnPublishedPoll(
35
47
props.block.pollRef.uri,
···
37
49
selectedOption,
38
50
);
39
51
40
40
-
if (result.success) {
41
41
-
setShowResults(true);
42
42
-
} else {
52
52
+
if (!result.success) {
43
53
console.error("Failed to vote:", result.error);
54
54
+
// Revert optimistic update on failure
55
55
+
setOptimisticVote(null);
56
56
+
setShowResults(false);
44
57
}
45
58
} catch (error) {
46
59
console.error("Failed to vote:", error);
60
60
+
// Revert optimistic update on failure
61
61
+
setOptimisticVote(null);
62
62
+
setShowResults(false);
47
63
} finally {
48
64
setIsVoting(false);
49
65
}
···
51
67
52
68
const hasVoted =
53
69
!!identity?.atp_did &&
54
54
-
!!props.pollData?.atp_poll_votes.find(
70
70
+
(!!props.pollData?.atp_poll_votes.find(
55
71
(v) => v.voter_did === identity?.atp_did,
56
56
-
);
72
72
+
) ||
73
73
+
!!optimisticVote);
57
74
const displayResults = showResults || hasVoted;
58
75
59
76
return (
···
69
86
pollData={props.pollData}
70
87
hasVoted={hasVoted}
71
88
setShowResults={setShowResults}
89
89
+
optimisticVote={optimisticVote}
72
90
/>
73
91
) : (
74
92
<>
···
79
97
optionIndex={index.toString()}
80
98
selected={selectedOption === index.toString()}
81
99
onSelect={() => setSelectedOption(index.toString())}
100
100
+
disabled={!identity?.atp_did}
82
101
/>
83
102
))}
84
103
<div className="flex justify-between items-center">
···
130
149
optionIndex: string;
131
150
selected: boolean;
132
151
onSelect: () => void;
152
152
+
disabled?: boolean;
133
153
}) => {
134
154
const ButtonComponent = props.selected ? ButtonPrimary : ButtonSecondary;
135
155
···
138
158
<ButtonComponent
139
159
className="pollOption grow max-w-full flex"
140
160
onClick={props.onSelect}
161
161
+
disabled={props.disabled}
141
162
>
142
163
{props.option.text}
143
164
</ButtonComponent>
···
149
170
pollData: PollData;
150
171
hasVoted: boolean;
151
172
setShowResults: (show: boolean) => void;
173
173
+
optimisticVote: { option: string; voter_did: string } | null;
152
174
}) => {
153
153
-
const totalVotes = props.pollData.atp_poll_votes.length || 0;
175
175
+
// Merge optimistic vote with actual votes
176
176
+
const allVotes = props.optimisticVote
177
177
+
? [
178
178
+
...props.pollData.atp_poll_votes,
179
179
+
{
180
180
+
option: props.optimisticVote.option,
181
181
+
voter_did: props.optimisticVote.voter_did,
182
182
+
poll_uri: "",
183
183
+
poll_cid: "",
184
184
+
uri: "",
185
185
+
record: {},
186
186
+
indexed_at: "",
187
187
+
},
188
188
+
]
189
189
+
: props.pollData.atp_poll_votes;
190
190
+
191
191
+
const totalVotes = allVotes.length || 0;
154
192
let pollRecord = props.pollData.record as PubLeafletPollDefinition.Record;
155
193
let optionsWithCount = pollRecord.options.map((o, index) => ({
156
194
...o,
157
157
-
votes: props.pollData.atp_poll_votes.filter(
158
158
-
(v) => v.option == index.toString(),
159
159
-
),
195
195
+
votes: allVotes.filter((v) => v.option == index.toString()),
160
196
}));
161
197
162
198
const highestVotes = Math.max(...optionsWithCount.map((o) => o.votes.length));
163
199
return (
164
200
<>
165
201
{pollRecord.options.map((option, index) => {
166
166
-
const votes = props.pollData?.atp_poll_votes.filter(
202
202
+
const votes = allVotes.filter(
167
203
(v) => v.option === index.toString(),
168
204
).length;
169
205
const isWinner = totalVotes > 0 && votes === highestVotes;