From 7c1453a0cf2dfa568ec0db00f8a54c0f912317d9 Mon Sep 17 00:00:00 2001 From: JoeGruff Date: Fri, 10 Apr 2026 11:02:06 +0900 Subject: [PATCH] politeia: Fix proposal vote eligibility. WalletProposalVoteDetails was passing only the already-voted ticket hashes to CommittedTickets, so it could never discover wallet tickets that had not yet voted. Fetch the full eligible ticket list from the vote snapshot via TicketVoteDetails and use that instead. Also restyle the vote modal to use standard form patterns (go/danger buttons, feature submit, consistent layout), hide the vote button after a successful vote, and log vote casts with ticket hashes. --- client/webserver/site/src/css/proposal.scss | 41 ++------------- client/webserver/site/src/html/proposal.tmpl | 40 +++++---------- client/webserver/site/src/js/proposal.ts | 1 + dex/politeia/politeia.go | 53 ++++++++++++++------ 4 files changed, 53 insertions(+), 82 deletions(-) diff --git a/client/webserver/site/src/css/proposal.scss b/client/webserver/site/src/css/proposal.scss index d621ed3f57..91f3f65093 100644 --- a/client/webserver/site/src/css/proposal.scss +++ b/client/webserver/site/src/css/proposal.scss @@ -1,8 +1,3 @@ -.vote-icon { - color: #fff; - display: block; -} - .proposal-content { h1, h2, h3 { font-size: 1.3rem; @@ -10,39 +5,9 @@ } .vote-btn { - display: flex; - align-items: center; - width: 100%; - max-width: 420px; - border: none; - font-weight: 600; - cursor: pointer; - color: #fff; -} - -.vote-btn .icon { - width: 20px; - height: 20px; - border-radius: 50%; - display: grid; - place-items: center; - font-size: 20px; - font-weight: 700; -} - -.vote-btn .label { flex: 1; - text-align: center; -} - -.vote-btn.vote-yes { - background: #6dff4f; -} -.vote-btn.vote-no { - background: #d81e14; -} - -.vote-btn.active { - border: 3px solid var(--btn-go-bg); + &.active { + outline: 3px solid var(--text-color); + } } diff --git a/client/webserver/site/src/html/proposal.tmpl b/client/webserver/site/src/html/proposal.tmpl index 2c22d4d95f..d25dc8034a 100644 --- a/client/webserver/site/src/html/proposal.tmpl +++ b/client/webserver/site/src/html/proposal.tmpl @@ -119,36 +119,20 @@
[[[vote]]]
-

+

[[[cast_vote_prompt]]] -

-
[[[voting_power]]]: {{$votingPower}}
-
- - -
- - -
+
+ [[[voting_power]]]: {{$votingPower}} +
+
+ + +
+
+ +
+
diff --git a/client/webserver/site/src/js/proposal.ts b/client/webserver/site/src/js/proposal.ts index 3d3f6764ca..e0ba9123b8 100644 --- a/client/webserver/site/src/js/proposal.ts +++ b/client/webserver/site/src/js/proposal.ts @@ -107,6 +107,7 @@ export default class ProposalPage extends BasePage { Doc.show(page.voteFormError) return } + Doc.hide(page.viewVoteFormBtn) this.closePopups() this.showSuccess(intl.prep(intl.ID_VOTE_CAST_MESSAGE)) } diff --git a/dex/politeia/politeia.go b/dex/politeia/politeia.go index eb67ad4b1e..53a587e52a 100644 --- a/dex/politeia/politeia.go +++ b/dex/politeia/politeia.go @@ -141,32 +141,45 @@ func New(ctx context.Context, politeiaURL string, dbPath string, log dex.Logger) // have voted along with their votes, and the total yes and no votes cast by // the wallet. func (p *Politeia) WalletProposalVoteDetails(wallet VotingWallet, token string) (*WalletProposalVoteDetails, error) { - req := tkv1.Results{ - Token: token, - } - votesResults, err := p.client.TicketVoteResults(req) + // Fetch the vote details to get the full list of eligible tickets + // from the snapshot. + voteDetails, err := p.client.TicketVoteDetails(tkv1.Details{Token: token}) if err != nil { - return nil, err + return nil, fmt.Errorf("TicketVoteDetails error: %w", err) + } + if voteDetails.Vote == nil { + return nil, fmt.Errorf("no vote details for proposal %s", token) } - hashes := make([]*chainhash.Hash, 0, len(votesResults.Votes)) - castVotes := make(map[string]string) - for _, v := range votesResults.Votes { - hash, err := chainhash.NewHashFromStr(v.Ticket) + // Build the list of all eligible ticket hashes from the vote snapshot. + eligibleHashes := make([]*chainhash.Hash, 0, len(voteDetails.Vote.EligibleTickets)) + for _, ticketStr := range voteDetails.Vote.EligibleTickets { + hash, err := chainhash.NewHashFromStr(ticketStr) if err != nil { return nil, err } - hashes = append(hashes, hash) + eligibleHashes = append(eligibleHashes, hash) + } + + // Fetch the votes already cast to know which tickets have voted. + votesResults, err := p.client.TicketVoteResults(tkv1.Results{Token: token}) + if err != nil { + return nil, fmt.Errorf("TicketVoteResults error: %w", err) + } + + castVotes := make(map[string]string, len(votesResults.Votes)) + for _, v := range votesResults.Votes { castVotes[v.Ticket] = v.VoteBit } - walletTicketHashes, addresses, err := wallet.CommittedTickets(hashes) + // Check which eligible tickets belong to this wallet. + walletTicketHashes, addresses, err := wallet.CommittedTickets(eligibleHashes) if err != nil { return nil, err } - eligibleWalletTickets := make([]*Ticket, 0) // eligibleWalletTickets are wallet tickets that have not yet voted. - walletVotedTickets := make([]*Ticket, 0) // walletVotedTickets are wallet tickets that have voted. + eligibleWalletTickets := make([]*Ticket, 0) + walletVotedTickets := make([]*Ticket, 0) for i := 0; i < len(walletTicketHashes); i++ { ticket := &Ticket{ Hash: walletTicketHashes[i].String(), @@ -176,17 +189,18 @@ func (p *Politeia) WalletProposalVoteDetails(wallet VotingWallet, token string) isMine, accountNumber, err := wallet.AddressAccount(ticket.Address) if err != nil { if strings.Contains(err.Error(), "address not found in wallet") { - continue // address not found in wallet, skip this ticket + continue } return nil, err } - // filter out tickets controlled by imported accounts or not owned by this wallet + // Filter out tickets controlled by imported accounts or not + // owned by this wallet. if !isMine || accountNumber == udb.ImportedAddrAccount { continue } - // filter out wallet tickets that have voted. + // Separate into already-voted and eligible. if _, ok := castVotes[ticket.Hash]; ok { walletVotedTickets = append(walletVotedTickets, ticket) continue @@ -262,6 +276,13 @@ func (p *Politeia) CastVotes(wallet VotingWallet, eligibleTickets []*Ticket, bit return fmt.Errorf("errors casting votes: %v", strings.Join(voteErrors, "; ")) } + ticketHashes := make([]string, len(eligibleTickets)) + for i, t := range eligibleTickets { + ticketHashes[i] = t.Hash + } + p.log.Infof("Successfully cast %d %s vote(s) on proposal %s with tickets: %s", + len(eligibleTickets), bit, token, strings.Join(ticketHashes, ", ")) + return nil }