| sessionId |
|---|
| session-261005-184808-15gg |
Provide immediate, clear visual feedback to users regarding their previous voting decisions on any visited proposal and its associated amendments. By leveraging ProposalService.getUserVotes, the application eliminates cognitive friction, prevents redundant vote attempts, and enhances user confidence by highlighting previously selected voting choices across proposal and amendment action controls.
ProposalService.getUserVotes(proposalId).ProposalVotingCard.vue to highlight the user’s previous proposal vote (Approve, Reject, or Abstain) with active colored styling and accessible state.AmendmentCarousel.vue to color the quick-action approval (Confirm) and disapproval (Cancel) icons according to the user’s vote on each amendment.ProposalDetail.vue must call ProposalService.getUserVotes(proposalId) to fetch all proposal and amendment votes cast by the current user.VoteChoice.YES (yes / Positive), the “Approve” button and icon must display active emerald styling.VoteChoice.NO (no / Negative), the “Reject” button and icon must display active rose styling.VoteChoice.ABSTAIN (abstain / Abstain), the “Abstain” button and icon must display active amber styling.inFavor === true), the Confirm button/icon is colored emerald.inFavor === false), the Cancel button/icon is colored rose.text-slate-400).aria-pressed="true" or descriptive aria-label / title attributes on active buttons to inform screen readers of the user’s recorded stance.getUserVotes) to avoid N+1 queries.ProposalService.getUserVotes(proposalId: number): CancelablePromise<UserProposalVotes> is available in src/generated/typescript/services/ProposalService.ts and returns:
proposalVote?: ProposalVote | null containing choice: VoteChoice ('yes' | 'no' | 'abstain').amendmentVotes: Array<UserAmendmentVote> containing amendmentId: number and vote?: AmendmentVote | null with inFavor: boolean.src/views/ProposalView.vue loads proposal and passes it to src/components/ProposalDetail.vue.ProposalDetail.vue renders:
<ProposalVotingCard :proposal-id="proposal.id" @vote="recordVote" /><AmendmentCarousel :proposal-id="proposal.id" />getUserVotes or accepts user vote props; buttons in ProposalVotingCard.vue and AmendmentCarousel.vue display static unselected styling regardless of user voting history.ProposalDetail.vue:
getUserVotes in ProposalDetail.vue and pass relevant slice data as props down to ProposalVotingCard.vue and AmendmentCarousel.vue.ProposalService.getUserVotes in a try/catch block; if 401 or network error occurs, silently default userVotes to null.emerald (positive/approve), rose (negative/reject), and amber (abstain) classes with subtle background and border tints for active states.Chip.vue, AmendmentCarousel.vue, and VotingControls.vue while delivering high-contrast clarity.src/components/ProposalDetail.vueUserProposalVotes, UserAmendmentVote, VoteChoice from generated typescript models.const userVotes = ref<UserProposalVotes | null>(null);.fetchUserVotes(proposalId: number):
ProposalService.getUserVotes(proposalId).userVotes.value.userVotes.value = null.fetchUserVotes in onMounted and within the watch(() => props.proposal?.id) handler.current-vote="userVotes?.proposalVote?.choice" to <ProposalVotingCard>.:amendment-votes="userVotes?.amendmentVotes" to <AmendmentCarousel>.recordVote to await voteProposal and immediately call fetchUserVotes(props.proposal.id).src/components/ProposalVotingCard.vuecurrentVote?: VoteChoice | 'yes' | 'no' | 'abstain' | null.isApproved = computed(() => props.currentVote === VoteChoice.YES || props.currentVote === 'yes')isRejected = computed(() => props.currentVote === VoteChoice.NO || props.currentVote === 'no')isAbstained = computed(() => props.currentVote === VoteChoice.ABSTAIN || props.currentVote === 'abstain'):class="isApproved ? 'border-emerald-600 bg-emerald-50 text-emerald-700 ring-1 ring-emerald-500' : 'border-slate-300 bg-white text-slate-700 hover:border-green-600 hover:bg-green-600 hover:text-white'":class="isRejected ? 'border-rose-600 bg-rose-50 text-rose-700 ring-1 ring-rose-500' : 'border-slate-300 bg-white text-slate-700 hover:border-red-600 hover:bg-red-600 hover:text-white'":class="isAbstained ? 'border-amber-500 bg-amber-50 text-amber-700 ring-1 ring-amber-500' : 'border-slate-300 bg-white text-slate-700 hover:border-amber-500 hover:bg-amber-500 hover:text-slate-950'"aria-pressed based on active vote status.src/components/AmendmentCarousel.vueamendmentVotes?: UserAmendmentVote[].getAmendmentVote(amendmentId: number): boolean | null | undefined:
true if vote?.inFavor === true, false if vote?.inFavor === false, or null/undefined if unvoted.text-emerald-600 bg-emerald-50 ring-1 ring-emerald-400 when getAmendmentVote(amendment.id) === true.text-rose-600 bg-rose-50 ring-1 ring-rose-400 when getAmendmentVote(amendment.id) === false.voted event when confirm or reject completes so parent ProposalDetail can refresh getUserVotes.graph TD
PV[ProposalView.vue] --> PD[ProposalDetail.vue]
PD -->|1. getUserVotes proposalId| PS[ProposalService API]
PS -->|UserProposalVotes| PD
PD -->|currentVote| PVC[ProposalVotingCard.vue]
PD -->|amendmentVotes| AC[AmendmentCarousel.vue]
PVC -->|@vote -> voteProposal + refresh| PD
AC -->|@voted -> voteAmendment + refresh| PD
src/components/ProposalDetail.vue (Modified): Adds user votes fetching, state management, and props dispatch.src/components/ProposalVotingCard.vue (Modified): Receives active proposal vote prop and renders highlight styles for Approve, Reject, and Abstain.src/components/AmendmentCarousel.vue (Modified): Receives amendment votes prop, applies active color styles on Confirm/Cancel icons, and notifies parent on vote.getUserVotes will return 401 when tokens are missing or invalid. Mitigation: Catch 401 errors gracefully and treat user votes as empty without throwing alerts.Verify the functionality by loading proposals in authenticated and unauthenticated states, testing all vote positions on both proposal and amendment components, and confirming real-time UI updates upon casting votes.
ProposalVotingCard is highlighted in emerald with active border/background.inFavor: true) -> Verify the checkmark (Confirm) icon is highlighted in emerald.inFavor: false) -> Verify the cross (Cancel) icon is highlighted in rose.text-slate-400).localStorage -> Verify the proposal details, comments, and amendment carousel render properly with default unselected action buttons and no console runtime errors.amendmentVotes handles empty arrays safely without runtime errors.ProposalDetail fetches and manages user vote records for the active proposal and its amendments, maintaining synchronized reactive state.
fetchUserVotes in src/components/ProposalDetail.vue using ProposalService.getUserVotes(props.proposal.id).userVotes typed as UserProposalVotes | null to track active proposal and amendment votes.fetchUserVotes on component mount and watch proposal.id changes to keep user vote state updated when navigating proposals.userVotes?.proposalVote?.choice) to ProposalVotingCard and the amendment vote mapping to AmendmentCarousel.ProposalVotingCard visually indicates the user’s previously cast vote with distinct color and container styling for Approve, Reject, and Abstain options.
currentVote prop to src/components/ProposalVotingCard.vue accepting VoteChoice | 'Positive' | 'Negative' | 'Abstain' | null | undefined.border-emerald-500 bg-emerald-50 text-emerald-700) and emerald Confirm icon.border-rose-500 bg-rose-50 text-rose-700) and rose Cancel icon.border-amber-500 bg-amber-50 text-amber-700) and amber Abstain icon.aria-pressed attributes to buttons for assistive technology feedback.recordVote in ProposalDetail.vue to refresh or optimistically update user vote state immediately after casting a vote.AmendmentCarousel displays colored approval and disapproval quick-action icons corresponding to the user’s recorded amendment votes.
src/components/AmendmentCarousel.vue to accept amendment vote data (e.g. userVotes?: UserAmendmentVote[] or a vote lookup map) from ProposalDetail.vue.inFavor: true, inFavor: false, or unvoted) for each amendment ID.text-emerald-600 bg-emerald-50 border border-emerald-200) on the Confirm icon button.text-rose-600 bg-rose-50 border border-rose-200) on the Cancel icon button.text-slate-400 hover:text-emerald-500 / hover:text-rose-500).confirm and reject voting handlers in AmendmentCarousel.vue to emit vote events or trigger vote refreshes to ensure immediate UI synchronization.