Description
The Milestone Detail page (/grants/[id]/milestones/[idx]) shows a vote tally in VotePanel. When a reviewer votes in another window, the tally on screen stays stale until manual refresh. With useContractEvents (issue #78) available, this issue wires live vote updates to the milestone detail page.
Technical Requirements
Files to update
app/grants/[id]/milestones/[idx]/page.tsx
components/milestones/VotePanel.tsx
Wire useContractEvents
In the milestone detail page:
const { events } = useContractEvents({ grantId })
const { refetch } = useMilestone(grantId, Number(idx)) // (issue #49)
useEffect(() => {
const latest = events[events.length - 1]
if (
latest?.type === 'MilestoneApproved' || latest?.type === 'MilestoneRejected'
) {
const eventMilestoneIdx = Number(latest.data.milestone_idx)
if (eventMilestoneIdx === Number(idx)) {
refetch() // refetch the milestone to get the new vote
}
}
}, [events])
Optimistic display for the current user's vote
When the connected wallet submits a vote (via useVoting — issue #35), update VotePanel optimistically before the refetch completes:
- Immediately add the wallet's vote to the local votes array
- The vote row shows with a "pending" indicator (amber dot) instead of the final approved/rejected icon
- When the confirmed data arrives (via refetch), the pending indicator disappears
Connection status in VotePanel
Add a "● LIVE" indicator to the VotePanel header (similar to the funding panel in issue #86):
REVIEWER VOTE ● LIVE 3 / 5 needed
connectionStatus from useContractEvents drives the live indicator.
Announce vote updates to screen readers
When the approval count changes, update an aria-live="polite" region:
<div aria-live="polite" className="sr-only">
{`Vote updated: ${approvalCount} of ${quorum} approvals`}
</div>
This is part of the accessibility audit from issue #97.
Quorum reached animation
When approvalCount >= quorum changes from false to true (milestone just reached quorum):
- Show a Framer Motion banner animation: a green
"✓ Quorum reached" banner slides down from the top of the VotePanel
- Fire a toast: "Milestone approved by quorum! Payout will be processed."
This event may come from SSE (MilestoneApproved event with quorum_reached: true in the payload) or be derived locally when approvalCount crosses the threshold.
No duplicate subscriptions
The milestone detail page opens a useContractEvents subscription filtered to grantId. The Grant Detail page (if open in another tab) has its own subscription. Each subscription is scoped and independent — verify with the Network tab that there is only one SSE connection per page.
Acceptance Criteria
Estimated Effort
Beginner: 5 hours
Intermediate: 2.5 hours
Expert: 1.5 hours
Description
The Milestone Detail page (
/grants/[id]/milestones/[idx]) shows a vote tally inVotePanel. When a reviewer votes in another window, the tally on screen stays stale until manual refresh. WithuseContractEvents(issue #78) available, this issue wires live vote updates to the milestone detail page.Technical Requirements
Files to update
app/grants/[id]/milestones/[idx]/page.tsxcomponents/milestones/VotePanel.tsxWire
useContractEventsIn the milestone detail page:
Optimistic display for the current user's vote
When the connected wallet submits a vote (via
useVoting— issue #35), updateVotePaneloptimistically before the refetch completes:Connection status in VotePanel
Add a "● LIVE" indicator to the
VotePanelheader (similar to the funding panel in issue #86):connectionStatusfromuseContractEventsdrives the live indicator.Announce vote updates to screen readers
When the approval count changes, update an
aria-live="polite"region:This is part of the accessibility audit from issue #97.
Quorum reached animation
When
approvalCount >= quorumchanges fromfalsetotrue(milestone just reached quorum):"✓ Quorum reached"banner slides down from the top of theVotePanelThis event may come from SSE (
MilestoneApprovedevent withquorum_reached: truein the payload) or be derived locally whenapprovalCountcrosses the threshold.No duplicate subscriptions
The milestone detail page opens a
useContractEventssubscription filtered tograntId. The Grant Detail page (if open in another tab) has its own subscription. Each subscription is scoped and independent — verify with the Network tab that there is only one SSE connection per page.Acceptance Criteria
● LIVEindicator appears in the VotePanel header.Estimated Effort
Beginner: 5 hours
Intermediate: 2.5 hours
Expert: 1.5 hours