Skip to content

Commit bf46ee9

Browse files
committed
Improve stance defaults and refresh logic in vote pages: refine default stances for comments based on voting behavior, update stance tracking, and ensure vote casting refreshes badges and tallies.
1 parent 181a505 commit bf46ee9

2 files changed

Lines changed: 45 additions & 10 deletions

File tree

web/components/votes/vote-comments.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,12 @@ export function VoteCommentSection(props: {
152152
>
153153
<Col className="gap-3">
154154
<DiscussionGuidance />
155-
<VoteCommentInput voteId={voteId} askForStance onPosted={onPosted} />
155+
<VoteCommentInput
156+
voteId={voteId}
157+
askForStance
158+
userChoice={user ? choicesByUserId[user.id] : undefined}
159+
onPosted={onPosted}
160+
/>
156161
</Col>
157162
</ShowMore>
158163
) : (
@@ -279,6 +284,7 @@ function VoteCommentThread(props: {
279284
}) {
280285
const {voteId, thread, choicesByUserId, canComment, onPosted, idInUrl, className} = props
281286
const {parent: parentComment, replies} = thread
287+
const user = useUser()
282288
const [replyToUserInfo, setReplyToUserInfo] = useState<ReplyToUserInfo>()
283289

284290
const idInThisThread =
@@ -332,6 +338,7 @@ function VoteCommentThread(props: {
332338
replyToUserInfo={replyToUserInfo}
333339
clearReply={clearReply}
334340
parentStance={parentComment.stance}
341+
userChoice={user ? choicesByUserId[user.id] : undefined}
335342
askForStance
336343
onPosted={onPosted}
337344
className="w-full min-w-0 grow"
@@ -749,6 +756,8 @@ export function VoteCommentInput(props: {
749756
askForStance?: boolean
750757
/** Stance of the comment being replied to, used to pick a sensible default for this one. */
751758
parentStance?: Stance
759+
/** The viewer's own recorded choice (1 / 0 / -1), used as the other default. */
760+
userChoice?: number
752761
onPosted?: () => void
753762
className?: string
754763
}) {
@@ -759,16 +768,33 @@ export function VoteCommentInput(props: {
759768
clearReply,
760769
askForStance,
761770
parentStance,
771+
userChoice,
762772
onPosted,
763773
className,
764774
} = props
765775
const t = useT()
766776
const user = useUser()
767-
// Replying to a question is almost always answering it, so that chip starts selected — still one
768-
// click to clear or change, but the common case costs nothing.
769-
const [stance, setStance] = useState<Stance | undefined>(
770-
parentStance === 'question' ? 'answer' : undefined,
771-
)
777+
// Two defaults, both still one click to clear or change. Replying to a question is almost always
778+
// answering it, so that wins. Otherwise someone who has already voted is almost always writing for
779+
// the side they voted — and a stanceless argument is invisible to the highlighted-arguments
780+
// ranking, which reads `stance`, not the ballot. Abstain and not-yet-voted leave it blank rather
781+
// than guess a side.
782+
const defaultStance: Stance | undefined =
783+
parentStance === 'question'
784+
? 'answer'
785+
: userChoice === 1
786+
? 'for'
787+
: userChoice === -1
788+
? 'against'
789+
: undefined
790+
const [stance, setStance] = useState<Stance | undefined>(defaultStance)
791+
// The ballot is fetched with the page, so it can land after this mounts, and it can change while
792+
// the composer sits open. Track it until the writer picks a chip themselves — after that the
793+
// default never overrides a deliberate choice.
794+
const stanceTouched = useRef(false)
795+
useEffect(() => {
796+
if (!stanceTouched.current) setStance(defaultStance)
797+
}, [defaultStance])
772798
const [isSubmitting, setIsSubmitting] = useState(false)
773799

774800
const key = `vote-comment ${voteId} ${parentCommentId ?? ''}`
@@ -795,7 +821,8 @@ export function VoteCommentInput(props: {
795821
replyToCommentId: parentCommentId,
796822
stance,
797823
})
798-
setStance(undefined)
824+
setStance(defaultStance)
825+
stanceTouched.current = false
799826
clearReply?.()
800827
onPosted?.()
801828
track('vote comment', {voteId, stance})
@@ -836,7 +863,10 @@ export function VoteCommentInput(props: {
836863
<button
837864
key={s}
838865
type="button"
839-
onClick={() => setStance((prev) => (prev === s ? undefined : s))}
866+
onClick={() => {
867+
stanceTouched.current = true
868+
setStance((prev) => (prev === s ? undefined : s))
869+
}}
840870
className={clsx(
841871
'rounded-full px-2.5 py-1 text-xs font-semibold transition-opacity',
842872
STANCE_COLOR[s],

web/pages/vote/[id].tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default function VoteDetailPage() {
4242
validId ? {voteId: voteId!} : undefined,
4343
getVote,
4444
)
45-
const {data: choicesByUserId} = useGetter(
45+
const {data: choicesByUserId, refresh: refreshChoices} = useGetter(
4646
'vote-results',
4747
validId ? {voteId: voteId!} : undefined,
4848
getVoteResultsByUser,
@@ -150,7 +150,12 @@ export default function VoteDetailPage() {
150150
abstain: typedVote.votes_abstain,
151151
against: typedVote.votes_against,
152152
}}
153-
onVoted={refreshVote}
153+
onVoted={() => {
154+
refreshVote()
155+
// The ballots feed the "voted For" badges and the composer's default stance, so a
156+
// vote cast on this page has to invalidate them too — not just the tallies.
157+
refreshChoices()
158+
}}
154159
disabled={typedVote.status !== 'voting_open'}
155160
/>
156161
{user && <MuteToggle voteId={typedVote.id} />}

0 commit comments

Comments
 (0)