Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions components/VoteList/VoteListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Button,
Checkbox,
Dropdown,
Loader,
LoadingSkeleton,
Expand Down Expand Up @@ -47,6 +48,10 @@ export function VoteListItem(props: VoteListItemProps) {
isDirty,
origin,
moreDetailsAction,
phase,
isSelectedForReveal,
toggleRevealSelection,
canBeRevealed,
} = useVoteListItem(props);

const { isOptimisticGovernorVote, explanationText } =
Expand All @@ -57,6 +62,7 @@ export function VoteListItem(props: VoteListItemProps) {
: "";

const voteOrigin = isOptimisticGovernorVote ? "OSnap" : origin;
const showRevealCheckbox = phase === "reveal" && canBeRevealed;

return (
<div
Expand All @@ -65,6 +71,19 @@ export function VoteListItem(props: VoteListItemProps) {
>
<div className="w-full rounded-l">
<div className="align-center flex border-b-[--border-color] pb-1">
{showRevealCheckbox && (
<div
className="mr-3 flex items-center"
onClick={(e) => e.stopPropagation()}
>
<Checkbox
label=""
checked={isSelectedForReveal ?? false}
onChange={() => toggleRevealSelection?.()}
gap={0}
/>
</div>
)}
<div className="w-full">
<h3 className="mb-1 line-clamp-2 w-full break-words text-lg font-semibold">
{optimisticGovernorTitle || titleText}
Expand Down
28 changes: 27 additions & 1 deletion components/VoteList/VoteTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Checkbox,
Dropdown,
Loader,
LoadingSkeleton,
Expand Down Expand Up @@ -63,6 +64,10 @@ export function VoteTableRow(props: VoteListItemProps) {
multipleInputProps,
dropdownOptions,
selectedDropdownOption,
phase,
isSelectedForReveal,
toggleRevealSelection,
canBeRevealed,
} = useVoteListItem(props);

const { isOptimisticGovernorVote, explanationText } =
Expand All @@ -74,13 +79,34 @@ export function VoteTableRow(props: VoteListItemProps) {

const voteOrigin = isOptimisticGovernorVote ? "OSnap" : origin;

const showRevealCheckbox = phase === "reveal" && canBeRevealed;

return (
<tr
className="group min-h-[80px] cursor-pointer rounded bg-white"
style={style}
onClick={moreDetailsAction}
>
<td className="rounded-l p-[--cell-padding]">
{showRevealCheckbox && (
<td
className="w-[50px] cursor-default rounded-l pl-[--cell-padding]"
onClick={(e) => e.stopPropagation()}
>
<Checkbox
label=""
checked={isSelectedForReveal ?? false}
onChange={() => toggleRevealSelection?.()}
gap={0}
/>
</td>
)}
<td
className={
showRevealCheckbox
? "p-[--cell-padding]"
: "rounded-l p-[--cell-padding]"
}
>
<div className="flex items-center gap-[--cell-padding]">
<div className="min-w-[--title-icon-size]">
<div className="w-[--title-icon-size]">
Expand Down
4 changes: 4 additions & 0 deletions components/VoteList/shared.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ export interface VoteListItemProps {
moreDetailsAction: () => void;
setDirty?: (dirty: boolean) => void;
isDirty?: boolean;
// Reveal phase selection props
isSelectedForReveal?: boolean;
toggleRevealSelection?: () => void;
canBeRevealed?: boolean;
}
8 changes: 8 additions & 0 deletions components/VoteList/useVoteListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export function useVoteListItem({
setDirty,
moreDetailsAction,
isDirty = false,
isSelectedForReveal,
toggleRevealSelection,
canBeRevealed,
}: VoteListItemProps) {
const [isCustomInput, setIsCustomInput] = useState(false);
const multipleInputProps = useMultipleValuesVote({
Expand Down Expand Up @@ -368,5 +371,10 @@ export function useVoteListItem({
moreDetailsAction,
multipleInputProps,
selectedDropdownOption,
// Reveal selection props
phase,
isSelectedForReveal,
toggleRevealSelection,
canBeRevealed,
};
}
82 changes: 71 additions & 11 deletions components/Votes/ActiveVotes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
useVoteTimingContext,
useWalletContext,
} from "hooks";
import { useState } from "react";
import { useState, useEffect, useMemo } from "react";
import { VoteT } from "types";
import {
ButtonInnerWrapper,
Expand Down Expand Up @@ -64,6 +64,9 @@ export function ActiveVotes() {
const { revealVotesMutation, isRevealingVotes } = useRevealVotes(address);
const [selectedVotes, setSelectedVotes] = usePersistedVotes(roundId);
const [dirtyInputs, setDirtyInput] = useState<boolean[]>([]);
const [selectedRevealVotes, setSelectedRevealVotes] = useState<
Record<string, boolean>
>({});
const { showPagination, entriesToShow, ...paginationProps } = usePagination(
activeVoteList ?? []
);
Expand All @@ -72,6 +75,65 @@ export function ActiveVotes() {
return dirtyInputs.some((x) => x);
}

// Get all votes that are eligible for reveal
const revealableVotes = useMemo(() => {
return (
activeVoteList?.filter(
(vote) =>
vote.isCommitted &&
!!vote.decryptedVote &&
vote.isRevealed === false &&
vote.canReveal
) ?? []
);
}, [activeVoteList]);

// Initialize selected reveal votes when entering reveal phase
useEffect(() => {
if (phase === "reveal" && revealableVotes.length > 0) {
setSelectedRevealVotes((prev) => {
const updated = { ...prev };
// Add any new revealable votes that aren't in the selection yet
revealableVotes.forEach((vote) => {
if (updated[vote.uniqueKey] === undefined) {
updated[vote.uniqueKey] = true; // Select all by default
}
});
// Remove votes that are no longer revealable
Object.keys(updated).forEach((key) => {
if (!revealableVotes.find((v) => v.uniqueKey === key)) {
delete updated[key];
}
});
return updated;
});
}
}, [phase, revealableVotes]);

function toggleRevealVoteSelection(uniqueKey: string) {
setSelectedRevealVotes((prev) => ({
...prev,
[uniqueKey]: !(prev[uniqueKey] ?? true), // Toggle from default true
}));
}

function isVoteSelectedForReveal(uniqueKey: string): boolean {
return selectedRevealVotes[uniqueKey] ?? true; // Default to checked
}

function canVoteBeRevealed(vote: VoteT): boolean {
return (
vote.isCommitted === true &&
!!vote.decryptedVote &&
vote.isRevealed === false &&
vote.canReveal === true
);
}

const selectedRevealCount = revealableVotes.filter(
(vote) => selectedRevealVotes[vote.uniqueKey] ?? true
).length;

const actionStatus = calculateActionStatus();
type ActionStatus = {
tooltip?: string;
Expand Down Expand Up @@ -115,7 +177,7 @@ export function ActiveVotes() {
? isDirty()
: true
: false;
const hasVotesToReveal = getVotesToReveal().length > 0;
const hasVotesToReveal = selectedRevealCount > 0;
// the current account is editing a previously committed value from another account, either delegate or delegator
const isEditingUnknownVote = Boolean(
activeVoteList?.filter((vote) => {
Expand Down Expand Up @@ -217,7 +279,7 @@ export function ActiveVotes() {
}
if (isReveal) {
actionConfig.hidden = false;
actionConfig.label = "Reveal all votes";
actionConfig.label = `Reveal ${selectedRevealCount}/${revealableVotes.length} votes`;
if (!hasSigningKey) {
actionConfig.label = "Sign";
actionConfig.onClick = () => sign();
Expand Down Expand Up @@ -286,14 +348,8 @@ export function ActiveVotes() {
}

function getVotesToReveal() {
return (
activeVoteList?.filter(
(vote) =>
vote.isCommitted &&
!!vote.decryptedVote &&
vote.isRevealed === false &&
vote.canReveal
) ?? []
return revealableVotes.filter(
(vote) => selectedRevealVotes[vote.uniqueKey] ?? true
);
}

Expand All @@ -320,6 +376,10 @@ export function ActiveVotes() {
inputs[index] = dirty;
return [...inputs];
}),
// Reveal phase selection props
isSelectedForReveal: isVoteSelectedForReveal(vote.uniqueKey),
toggleRevealSelection: () => toggleRevealVoteSelection(vote.uniqueKey),
canBeRevealed: canVoteBeRevealed(vote),
}));

return (
Expand Down