Skip to content

feat: filter for voted props #2173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 6, 2024
Merged
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
Field,
Label,
Listbox,
ListboxButton,
ListboxOptions,
ListboxOption,
Transition,
} from '@headlessui/react'
import type { ComponentProps } from 'react'
import Arrow from '@images/icons/down.inline.svg'
import { Fragment } from 'react'

type OwnProps<T> = {
label: string
options: readonly T[]
value: T
onChange: (newValue: T) => void
}

type Props<T> = Omit<ComponentProps<typeof Listbox>, keyof OwnProps<T>> &
OwnProps<T>

export const Select = <T extends string>({
options,
label,
...props
}: Props<T>) => (
<Field className="flex flex-col gap-1">
<Label>{label}</Label>
<Listbox as="div" className="relative block w-[180px] text-left" {...props}>
{({ open }) => (
<>
<ListboxButton className="inline-flex w-full items-center justify-between bg-darkGray2 py-3 px-6 text-sm outline-0">
<span className="mr-3">{props.value}</span>
<Arrow className={`${open && 'rotate-180'}`} />
</ListboxButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<ListboxOptions className="absolute right-0 mt-2 w-full origin-top-right z-10">
{options.map((option) => (
<ListboxOption
key={option}
value={option}
className="block w-full bg-darkGray py-3 px-6 text-left text-sm hover:bg-darkGray2 cursor-pointer"
>
{option}
</ListboxOption>
))}
</ListboxOptions>
</Transition>
</>
)}
</Listbox>
</Field>
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { StatusTag } from './StatusTag'
import { getInstructionsSummary, getProposalStatus } from './utils'

import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { useWallet } from '@solana/wallet-adapter-react'
import { AccountMeta, Keypair } from '@solana/web3.js'
import {
MultisigParser,
Expand All @@ -30,6 +31,7 @@ export const ProposalRow = ({
const { isLoading: isMultisigLoading, connection } = useMultisigContext()
const router = useRouter()
const elementRef = useRef(null)
const { publicKey: walletPublicKey } = useWallet()
const formattedTime = time?.toLocaleString(undefined, {
year: 'numeric',
month: 'short',
Expand Down Expand Up @@ -191,6 +193,24 @@ export const ProposalRow = ({
/>
</div>
)}
{walletPublicKey &&
proposal.approved.some((vote) => vote.equals(walletPublicKey)) && (
<div>
<StatusTag proposalStatus="executed" text="You approved" />
</div>
)}
{walletPublicKey &&
proposal.rejected.some((vote) => vote.equals(walletPublicKey)) && (
<div>
<StatusTag proposalStatus="rejected" text="You rejected" />
</div>
)}
{walletPublicKey &&
proposal.cancelled.some((vote) => vote.equals(walletPublicKey)) && (
<div>
<StatusTag proposalStatus="cancelled" text="You cancelled" />
</div>
)}
<div>
<StatusTag proposalStatus={status} />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
import { TransactionAccount } from '@sqds/mesh/lib/types'
import { useRouter } from 'next/router'
import { useCallback, useContext, useEffect, useState } from 'react'
import { useCallback, useContext, useEffect, useState, useMemo } from 'react'
import { ClusterContext } from '../../../contexts/ClusterContext'
import { useMultisigContext } from '../../../contexts/MultisigContext'
import { StatusFilterContext } from '../../../contexts/StatusFilterContext'
import { PROPOSAL_STATUSES } from './utils'
import ClusterSwitch from '../../ClusterSwitch'
import ProposalStatusFilter from '../../ProposalStatusFilter'
import Loadbar from '../../loaders/Loadbar'
import { Select } from '../../Select'
import { useQueryState, parseAsStringLiteral } from 'nuqs'

import { ProposalRow } from './ProposalRow'
import { getProposalStatus } from './utils'
import { Proposal } from './Proposal'
import { useWallet } from '@solana/wallet-adapter-react'

type ProposalType = 'priceFeed' | 'governance'

const VOTE_STATUSES = [
'any',
'voted',
'approved',
'rejected',
'cancelled',
'notVoted',
] as const
const DEFAULT_VOTE_STATUS = 'any'

const PROPOSAL_STATUS_FILTERS = ['all', ...PROPOSAL_STATUSES] as const
const DEFAULT_PROPOSAL_STATUS_FILTER = 'all'

const Proposals = () => {
const router = useRouter()
const [currentProposal, setCurrentProposal] = useState<TransactionAccount>()
const [currentProposalPubkey, setCurrentProposalPubkey] = useState<string>()
const [statusFilter, setStatusFilter] = useQueryState(
'status',
parseAsStringLiteral(PROPOSAL_STATUS_FILTERS).withDefault(
DEFAULT_PROPOSAL_STATUS_FILTER
)
)
const [voteStatus, setVoteStatus] = useQueryState(
'voteStatus',
parseAsStringLiteral(VOTE_STATUSES).withDefault(DEFAULT_VOTE_STATUS)
)
const { cluster } = useContext(ClusterContext)
const { statusFilter } = useContext(StatusFilterContext)
const { publicKey: walletPublicKey } = useWallet()

const {
upgradeMultisigAccount,
Expand All @@ -40,9 +65,6 @@ const Proposals = () => {
proposalType === 'priceFeed'
? priceFeedMultisigProposals
: upgradeMultisigProposals
const [filteredProposals, setFilteredProposals] = useState<
TransactionAccount[]
>([])

const handleClickBackToProposals = () => {
delete router.query.proposal
Expand Down Expand Up @@ -103,19 +125,60 @@ const Proposals = () => {
cluster,
])

useEffect(() => {
// filter price feed multisig proposals by status
if (statusFilter === 'all') {
setFilteredProposals(multisigProposals)
const proposalsFilteredByStatus = useMemo(
() =>
statusFilter === 'all'
? multisigProposals
: multisigProposals.filter(
(proposal) =>
getProposalStatus(proposal, multisigAccount) === statusFilter
),
[statusFilter, multisigAccount, multisigProposals]
)

const filteredProposals = useMemo(() => {
if (walletPublicKey) {
switch (voteStatus) {
case 'any':
return proposalsFilteredByStatus
case 'voted': {
return proposalsFilteredByStatus.filter((proposal) =>
[
...proposal.approved,
...proposal.rejected,
...proposal.cancelled,
].some((vote) => vote.equals(walletPublicKey))
)
}
case 'approved': {
return proposalsFilteredByStatus.filter((proposal) =>
proposal.approved.some((vote) => vote.equals(walletPublicKey))
)
}
case 'rejected': {
return proposalsFilteredByStatus.filter((proposal) =>
proposal.rejected.some((vote) => vote.equals(walletPublicKey))
)
}
case 'cancelled': {
return proposalsFilteredByStatus.filter((proposal) =>
proposal.cancelled.some((vote) => vote.equals(walletPublicKey))
)
}
case 'notVoted': {
return proposalsFilteredByStatus.filter((proposal) =>
[
...proposal.approved,
...proposal.rejected,
...proposal.cancelled,
].every((vote) => !vote.equals(walletPublicKey))
)
}
}
} else {
setFilteredProposals(
multisigProposals.filter(
(proposal) =>
getProposalStatus(proposal, multisigAccount) === statusFilter
)
)
return proposalsFilteredByStatus
}
}, [statusFilter, multisigAccount, multisigProposals])
}, [proposalsFilteredByStatus, walletPublicKey, voteStatus])

return (
<div className="relative">
Expand Down Expand Up @@ -167,11 +230,26 @@ const Proposals = () => {
</div>
) : (
<>
<div className="flex items-center justify-between pb-4">
<div className="flex items-end md:flex-row-reverse justify-between pb-4">
<div className="flex flex-col md:flex-row md:items-center gap-4 text-sm">
{walletPublicKey && (
<Select
label="Your Vote"
value={voteStatus}
options={VOTE_STATUSES}
onChange={setVoteStatus}
/>
)}
<Select
label="Proposal Status"
value={statusFilter}
options={PROPOSAL_STATUS_FILTERS}
onChange={setStatusFilter}
/>
</div>
<h4 className="h4">
Total Proposals: {filteredProposals.length}
</h4>
<ProposalStatusFilter />
</div>
{filteredProposals.length > 0 ? (
<div className="flex flex-col">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ import {
WormholeMultisigInstruction,
} from '@pythnetwork/xc-admin-common'

export type ProposalStatus =
| 'active'
| 'executed'
| 'cancelled'
| 'rejected'
| 'expired'
| 'executeReady'
| 'draft'
| 'unkwown'
export const PROPOSAL_STATUSES = [
'active',
'executed',
'cancelled',
'rejected',
'expired',
'executeReady',
'draft',
'unkwown',
] as const
export type ProposalStatus = typeof PROPOSAL_STATUSES[number]

export const getProposalStatus = (
proposal: TransactionAccount | undefined,
Expand Down
Loading
Loading