a tool for shared writing and social publishing

enable optimistic votes and prompt to signin

+47 -11
+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 + const [optimisticVote, setOptimisticVote] = useState<{ 24 + option: string; 25 + voter_did: string; 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 - if (!selectedOption) return; 34 + if (!selectedOption || !identity?.atp_did) return; 31 35 32 36 setIsVoting(true); 37 + 38 + // Optimistically add the vote 39 + setOptimisticVote({ 40 + option: selectedOption, 41 + voter_did: identity.atp_did, 42 + }); 43 + setShowResults(true); 44 + 33 45 try { 34 46 const result = await voteOnPublishedPoll( 35 47 props.block.pollRef.uri, ··· 37 49 selectedOption, 38 50 ); 39 51 40 - if (result.success) { 41 - setShowResults(true); 42 - } else { 52 + if (!result.success) { 43 53 console.error("Failed to vote:", result.error); 54 + // Revert optimistic update on failure 55 + setOptimisticVote(null); 56 + setShowResults(false); 44 57 } 45 58 } catch (error) { 46 59 console.error("Failed to vote:", error); 60 + // Revert optimistic update on failure 61 + setOptimisticVote(null); 62 + setShowResults(false); 47 63 } finally { 48 64 setIsVoting(false); 49 65 } ··· 51 67 52 68 const hasVoted = 53 69 !!identity?.atp_did && 54 - !!props.pollData?.atp_poll_votes.find( 70 + (!!props.pollData?.atp_poll_votes.find( 55 71 (v) => v.voter_did === identity?.atp_did, 56 - ); 72 + ) || 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 + 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 + 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 + 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 + 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 + optimisticVote: { option: string; voter_did: string } | null; 152 174 }) => { 153 - const totalVotes = props.pollData.atp_poll_votes.length || 0; 175 + // Merge optimistic vote with actual votes 176 + const allVotes = props.optimisticVote 177 + ? [ 178 + ...props.pollData.atp_poll_votes, 179 + { 180 + option: props.optimisticVote.option, 181 + voter_did: props.optimisticVote.voter_did, 182 + poll_uri: "", 183 + poll_cid: "", 184 + uri: "", 185 + record: {}, 186 + indexed_at: "", 187 + }, 188 + ] 189 + : props.pollData.atp_poll_votes; 190 + 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 - votes: props.pollData.atp_poll_votes.filter( 158 - (v) => v.option == index.toString(), 159 - ), 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 - const votes = props.pollData?.atp_poll_votes.filter( 202 + const votes = allVotes.filter( 167 203 (v) => v.option === index.toString(), 168 204 ).length; 169 205 const isWinner = totalVotes > 0 && votes === highestVotes;