From f13b63b0b288738ddc42ce1b869e6081b2ccadd7 Mon Sep 17 00:00:00 2001 From: "Sebastian.Bor" Date: Fri, 2 Apr 2021 10:46:28 +0100 Subject: [PATCH 01/12] feat: add quick vote --- .../src/actions/depositSourceTokens.ts | 18 ++- .../src/actions/depositSourceTokensAndVote.ts | 52 ++++++++ .../src/components/Proposal/QuickVote.tsx | 118 ++++++++++++++++++ packages/proposals/src/constants/labels.ts | 1 + .../proposals/src/views/proposal/index.tsx | 6 + 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 packages/proposals/src/actions/depositSourceTokensAndVote.ts create mode 100644 packages/proposals/src/components/Proposal/QuickVote.tsx diff --git a/packages/proposals/src/actions/depositSourceTokens.ts b/packages/proposals/src/actions/depositSourceTokens.ts index 7bda08b..09a8fe8 100644 --- a/packages/proposals/src/actions/depositSourceTokens.ts +++ b/packages/proposals/src/actions/depositSourceTokens.ts @@ -13,10 +13,12 @@ import { } from '@oyster/common'; import { TimelockSet } from '../models/timelock'; + import { AccountLayout } from '@solana/spl-token'; import { depositSourceTokensInstruction } from '../models/depositSourceTokens'; import { LABELS } from '../constants'; import { createEmptyGovernanceVotingRecordInstruction } from '../models/createEmptyGovernanceVotingRecord'; + const { createTokenAccount } = actions; const { sendTransaction } = contexts.Connection; const { notify } = utils; @@ -31,7 +33,11 @@ export const depositSourceTokens = async ( existingNoVoteAccount: PublicKey | undefined, sourceAccount: PublicKey, votingTokenAmount: number, -) => { +): Promise<{ + voteAccount: PublicKey; + yesVoteAccount: PublicKey; + noVoteAccount: PublicKey; +}> => { const PROGRAM_IDS = utils.programIds(); let signers: Account[] = []; @@ -74,7 +80,7 @@ export const depositSourceTokens = async ( } if (!existingYesVoteAccount) { - createTokenAccount( + existingYesVoteAccount = createTokenAccount( instructions, wallet.publicKey, accountRentExempt, @@ -85,7 +91,7 @@ export const depositSourceTokens = async ( } if (!existingNoVoteAccount) { - createTokenAccount( + existingNoVoteAccount = createTokenAccount( instructions, wallet.publicKey, accountRentExempt, @@ -148,4 +154,10 @@ export const depositSourceTokens = async ( console.error(ex); throw new Error(); } + + return { + voteAccount: existingVoteAccount, + yesVoteAccount: existingYesVoteAccount, + noVoteAccount: existingNoVoteAccount, + }; }; diff --git a/packages/proposals/src/actions/depositSourceTokensAndVote.ts b/packages/proposals/src/actions/depositSourceTokensAndVote.ts new file mode 100644 index 0000000..b675b49 --- /dev/null +++ b/packages/proposals/src/actions/depositSourceTokensAndVote.ts @@ -0,0 +1,52 @@ +import { Connection, PublicKey } from '@solana/web3.js'; +import { ParsedAccount } from '@oyster/common'; + +import { TimelockConfig, TimelockSet, TimelockState } from '../models/timelock'; + +import { vote } from './vote'; +import { depositSourceTokens } from './depositSourceTokens'; + +export const depositSourceTokensAndVote = async ( + connection: Connection, + wallet: any, + proposal: ParsedAccount, + existingVoteAccount: PublicKey | undefined, + existingYesVoteAccount: PublicKey | undefined, + existingNoVoteAccount: PublicKey | undefined, + sourceAccount: PublicKey, + timelockConfig: ParsedAccount, + state: ParsedAccount, + yesVotingTokenAmount: number, + noVotingTokenAmount: number, +) => { + const votingTokenAmount = + yesVotingTokenAmount > 0 ? yesVotingTokenAmount : noVotingTokenAmount; + + const { + voteAccount, + yesVoteAccount, + noVoteAccount, + } = await depositSourceTokens( + connection, + wallet, + proposal, + existingVoteAccount, + existingYesVoteAccount, + existingNoVoteAccount, + sourceAccount, + votingTokenAmount, + ); + + await vote( + connection, + wallet, + proposal, + timelockConfig, + state, + voteAccount, + yesVoteAccount, + noVoteAccount, + yesVotingTokenAmount, + noVotingTokenAmount, + ); +}; diff --git a/packages/proposals/src/components/Proposal/QuickVote.tsx b/packages/proposals/src/components/Proposal/QuickVote.tsx new file mode 100644 index 0000000..16c1e74 --- /dev/null +++ b/packages/proposals/src/components/Proposal/QuickVote.tsx @@ -0,0 +1,118 @@ +import { ParsedAccount } from '@oyster/common'; +import { Button, Col, Modal, Row, Switch } from 'antd'; +import React, { useState } from 'react'; +import { + TimelockConfig, + TimelockSet, + TimelockState, + TimelockStateStatus, + VotingEntryRule, +} from '../../models/timelock'; +import { LABELS } from '../../constants'; +import { depositSourceTokensAndVote } from '../../actions/depositSourceTokensAndVote'; +import { contexts, hooks } from '@oyster/common'; +import { + CheckOutlined, + CloseOutlined, + ExclamationCircleOutlined, +} from '@ant-design/icons'; + +const { useWallet } = contexts.Wallet; +const { useConnection } = contexts.Connection; +const { useAccountByMint } = hooks; + +const { cache } = contexts.Accounts; + +const { confirm } = Modal; +export function QuickVote({ + proposal, + state, + timelockConfig, +}: { + proposal: ParsedAccount; + state: ParsedAccount; + timelockConfig: ParsedAccount; +}) { + const wallet = useWallet(); + const connection = useConnection(); + + const voteAccount = useAccountByMint(proposal.info.votingMint); + const yesVoteAccount = useAccountByMint(proposal.info.yesVotingMint); + const noVoteAccount = useAccountByMint(proposal.info.noVotingMint); + + const userTokenAccount = useAccountByMint(proposal.info.sourceMint); + const alreadyHaveTokens = + (voteAccount && voteAccount.info.amount.toNumber() > 0) || + (yesVoteAccount && yesVoteAccount.info.amount.toNumber() > 0) || + (noVoteAccount && noVoteAccount.info.amount.toNumber() > 0); + + const [mode, setMode] = useState(true); + + const eligibleToView = + timelockConfig.info.votingEntryRule == VotingEntryRule.Anytime && + [TimelockStateStatus.Draft, TimelockStateStatus.Voting].includes( + state.info.status, + ); + + return eligibleToView ? ( + + ) : null; +} diff --git a/packages/proposals/src/constants/labels.ts b/packages/proposals/src/constants/labels.ts index b87ffd8..e03fe79 100644 --- a/packages/proposals/src/constants/labels.ts +++ b/packages/proposals/src/constants/labels.ts @@ -69,6 +69,7 @@ export const LABELS = { WITHDRAWING_VOTING_TOKENS: 'Refunding voting tokens as Source Tokens', TOKENS_WITHDRAWN: 'Voting tokens refunded as Source Tokens', REGISTER_TO_VOTE: 'Register to Vote', + QUICK_VOTE: 'Quick Vote', CONFIRM: 'Confirm', CANCEL: 'Cancel', ADD_MORE_VOTES: 'Add More Votes', diff --git a/packages/proposals/src/views/proposal/index.tsx b/packages/proposals/src/views/proposal/index.tsx index b79d379..4906e99 100644 --- a/packages/proposals/src/views/proposal/index.tsx +++ b/packages/proposals/src/views/proposal/index.tsx @@ -25,6 +25,7 @@ import AddSigners from '../../components/Proposal/AddSigners'; import MintSourceTokens from '../../components/Proposal/MintSourceTokens'; import { Vote } from '../../components/Proposal/Vote'; import { RegisterToVote } from '../../components/Proposal/RegisterToVote'; +import { QuickVote } from '../../components/Proposal/QuickVote'; import { WithdrawTokens } from '../../components/Proposal/WithdrawTokens'; import './style.less'; import { getGovernanceVotingRecords } from '../../utils/lookups'; @@ -306,6 +307,11 @@ function InnerProposalView({ proposal={proposal} state={timelockState} /> + Date: Fri, 2 Apr 2021 16:59:51 +0100 Subject: [PATCH 02/12] feat: remove register/vote workflow --- .../components/Proposal/RegisterToVote.tsx | 106 ----------------- .../src/components/Proposal/Vote.tsx | 111 ------------------ packages/proposals/src/constants/labels.ts | 2 - .../proposals/src/views/proposal/index.tsx | 12 -- 4 files changed, 231 deletions(-) delete mode 100644 packages/proposals/src/components/Proposal/RegisterToVote.tsx delete mode 100644 packages/proposals/src/components/Proposal/Vote.tsx diff --git a/packages/proposals/src/components/Proposal/RegisterToVote.tsx b/packages/proposals/src/components/Proposal/RegisterToVote.tsx deleted file mode 100644 index 9f104a7..0000000 --- a/packages/proposals/src/components/Proposal/RegisterToVote.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { ParsedAccount } from '@oyster/common'; -import { Button, Col, Modal, Row, Slider } from 'antd'; -import React, { useState } from 'react'; -import { - TimelockConfig, - TimelockSet, - TimelockState, - TimelockStateStatus, - VotingEntryRule, -} from '../../models/timelock'; -import { LABELS } from '../../constants'; -import { depositSourceTokens } from '../../actions/depositSourceTokens'; -import { contexts, hooks } from '@oyster/common'; -import { ExclamationCircleOutlined } from '@ant-design/icons'; - -const { useWallet } = contexts.Wallet; -const { useConnection } = contexts.Connection; -const { useAccountByMint } = hooks; - -const { confirm } = Modal; -export function RegisterToVote({ - proposal, - state, - timelockConfig, -}: { - proposal: ParsedAccount; - state: ParsedAccount; - timelockConfig: ParsedAccount; -}) { - const wallet = useWallet(); - const connection = useConnection(); - const voteAccount = useAccountByMint(proposal.info.votingMint); - const yesVoteAccount = useAccountByMint(proposal.info.yesVotingMint); - const noVoteAccount = useAccountByMint(proposal.info.noVotingMint); - - const userTokenAccount = useAccountByMint(proposal.info.sourceMint); - const alreadyHaveTokens = - (voteAccount && voteAccount.info.amount.toNumber() > 0) || - (yesVoteAccount && yesVoteAccount.info.amount.toNumber() > 0) || - (noVoteAccount && noVoteAccount.info.amount.toNumber() > 0); - - const eligibleToView = - timelockConfig.info.votingEntryRule == VotingEntryRule.Anytime && - [TimelockStateStatus.Draft, TimelockStateStatus.Voting].includes( - state.info.status, - ); - - const [_, setTokenAmount] = useState(1); - return eligibleToView ? ( - - ) : null; -} diff --git a/packages/proposals/src/components/Proposal/Vote.tsx b/packages/proposals/src/components/Proposal/Vote.tsx deleted file mode 100644 index 24cbe0c..0000000 --- a/packages/proposals/src/components/Proposal/Vote.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { ParsedAccount } from '@oyster/common'; -import { Button, Col, Modal, Row, Slider, Switch } from 'antd'; -import React, { useState } from 'react'; -import { - TimelockConfig, - TimelockSet, - TimelockState, - TimelockStateStatus, -} from '../../models/timelock'; -import { LABELS } from '../../constants'; -import { vote } from '../../actions/vote'; -import { contexts, hooks } from '@oyster/common'; -import { - CheckOutlined, - CloseOutlined, - ExclamationCircleOutlined, -} from '@ant-design/icons'; - -const { useWallet } = contexts.Wallet; -const { useConnection } = contexts.Connection; -const { useAccountByMint } = hooks; - -const { confirm } = Modal; -export function Vote({ - proposal, - state, - timelockConfig, -}: { - proposal: ParsedAccount; - state: ParsedAccount; - timelockConfig: ParsedAccount; -}) { - const wallet = useWallet(); - const connection = useConnection(); - const voteAccount = useAccountByMint(proposal.info.votingMint); - const yesVoteAccount = useAccountByMint(proposal.info.yesVotingMint); - const noVoteAccount = useAccountByMint(proposal.info.noVotingMint); - const [mode, setMode] = useState(true); - const [_, setTokenAmount] = useState(1); - const eligibleToView = - voteAccount && - voteAccount.info.amount.toNumber() > 0 && - state.info.status === TimelockStateStatus.Voting; - return eligibleToView ? ( - - ) : null; -} diff --git a/packages/proposals/src/constants/labels.ts b/packages/proposals/src/constants/labels.ts index e03fe79..d05e8ee 100644 --- a/packages/proposals/src/constants/labels.ts +++ b/packages/proposals/src/constants/labels.ts @@ -68,11 +68,9 @@ export const LABELS = { EXECUTED: 'Executed.', WITHDRAWING_VOTING_TOKENS: 'Refunding voting tokens as Source Tokens', TOKENS_WITHDRAWN: 'Voting tokens refunded as Source Tokens', - REGISTER_TO_VOTE: 'Register to Vote', QUICK_VOTE: 'Quick Vote', CONFIRM: 'Confirm', CANCEL: 'Cancel', - ADD_MORE_VOTES: 'Add More Votes', REFUND_TOKENS: 'Refund My Tokens', REGISTER_GOVERNANCE: 'Register', PROGRAM: 'Program ID', diff --git a/packages/proposals/src/views/proposal/index.tsx b/packages/proposals/src/views/proposal/index.tsx index 4906e99..982058e 100644 --- a/packages/proposals/src/views/proposal/index.tsx +++ b/packages/proposals/src/views/proposal/index.tsx @@ -23,8 +23,6 @@ import { NewInstructionCard } from '../../components/Proposal/NewInstructionCard import SignButton from '../../components/Proposal/SignButton'; import AddSigners from '../../components/Proposal/AddSigners'; import MintSourceTokens from '../../components/Proposal/MintSourceTokens'; -import { Vote } from '../../components/Proposal/Vote'; -import { RegisterToVote } from '../../components/Proposal/RegisterToVote'; import { QuickVote } from '../../components/Proposal/QuickVote'; import { WithdrawTokens } from '../../components/Proposal/WithdrawTokens'; import './style.less'; @@ -297,11 +295,6 @@ function InnerProposalView({ timelockConfig.info.governanceMint.toBase58() } /> - - From a332d6a69bf21ff27a873722d6b6803f27233679 Mon Sep 17 00:00:00 2001 From: "Sebastian.Bor" Date: Fri, 2 Apr 2021 17:45:43 +0100 Subject: [PATCH 03/12] choore: rename QuickVote to Vote --- .../components/Proposal/{QuickVote.tsx => Vote.tsx} | 10 ++-------- packages/proposals/src/constants/labels.ts | 1 - packages/proposals/src/views/proposal/index.tsx | 4 ++-- 3 files changed, 4 insertions(+), 11 deletions(-) rename packages/proposals/src/components/Proposal/{QuickVote.tsx => Vote.tsx} (91%) diff --git a/packages/proposals/src/components/Proposal/QuickVote.tsx b/packages/proposals/src/components/Proposal/Vote.tsx similarity index 91% rename from packages/proposals/src/components/Proposal/QuickVote.tsx rename to packages/proposals/src/components/Proposal/Vote.tsx index 16c1e74..4ab147d 100644 --- a/packages/proposals/src/components/Proposal/QuickVote.tsx +++ b/packages/proposals/src/components/Proposal/Vote.tsx @@ -21,10 +21,8 @@ const { useWallet } = contexts.Wallet; const { useConnection } = contexts.Connection; const { useAccountByMint } = hooks; -const { cache } = contexts.Accounts; - const { confirm } = Modal; -export function QuickVote({ +export function Vote({ proposal, state, timelockConfig, @@ -41,10 +39,6 @@ export function QuickVote({ const noVoteAccount = useAccountByMint(proposal.info.noVotingMint); const userTokenAccount = useAccountByMint(proposal.info.sourceMint); - const alreadyHaveTokens = - (voteAccount && voteAccount.info.amount.toNumber() > 0) || - (yesVoteAccount && yesVoteAccount.info.amount.toNumber() > 0) || - (noVoteAccount && noVoteAccount.info.amount.toNumber() > 0); const [mode, setMode] = useState(true); @@ -112,7 +106,7 @@ export function QuickVote({ }) } > - {LABELS.QUICK_VOTE} + {LABELS.VOTE} ) : null; } diff --git a/packages/proposals/src/constants/labels.ts b/packages/proposals/src/constants/labels.ts index d05e8ee..9e33f27 100644 --- a/packages/proposals/src/constants/labels.ts +++ b/packages/proposals/src/constants/labels.ts @@ -68,7 +68,6 @@ export const LABELS = { EXECUTED: 'Executed.', WITHDRAWING_VOTING_TOKENS: 'Refunding voting tokens as Source Tokens', TOKENS_WITHDRAWN: 'Voting tokens refunded as Source Tokens', - QUICK_VOTE: 'Quick Vote', CONFIRM: 'Confirm', CANCEL: 'Cancel', REFUND_TOKENS: 'Refund My Tokens', diff --git a/packages/proposals/src/views/proposal/index.tsx b/packages/proposals/src/views/proposal/index.tsx index 982058e..d858064 100644 --- a/packages/proposals/src/views/proposal/index.tsx +++ b/packages/proposals/src/views/proposal/index.tsx @@ -23,7 +23,7 @@ import { NewInstructionCard } from '../../components/Proposal/NewInstructionCard import SignButton from '../../components/Proposal/SignButton'; import AddSigners from '../../components/Proposal/AddSigners'; import MintSourceTokens from '../../components/Proposal/MintSourceTokens'; -import { QuickVote } from '../../components/Proposal/QuickVote'; +import { Vote } from '../../components/Proposal/Vote'; import { WithdrawTokens } from '../../components/Proposal/WithdrawTokens'; import './style.less'; import { getGovernanceVotingRecords } from '../../utils/lookups'; @@ -300,7 +300,7 @@ function InnerProposalView({ proposal={proposal} state={timelockState} /> - Date: Fri, 2 Apr 2021 18:49:12 +0100 Subject: [PATCH 04/12] fix: create and use useLatestState hook to get up to date useState hook state --- .../proposals/src/components/Proposal/Vote.tsx | 15 +++++---------- packages/proposals/src/hooks/useLatestState.ts | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 packages/proposals/src/hooks/useLatestState.ts diff --git a/packages/proposals/src/components/Proposal/Vote.tsx b/packages/proposals/src/components/Proposal/Vote.tsx index 4ab147d..739c5cb 100644 --- a/packages/proposals/src/components/Proposal/Vote.tsx +++ b/packages/proposals/src/components/Proposal/Vote.tsx @@ -16,6 +16,7 @@ import { CloseOutlined, ExclamationCircleOutlined, } from '@ant-design/icons'; +import { useLatestState } from '../../hooks/useLatestState'; const { useWallet } = contexts.Wallet; const { useConnection } = contexts.Connection; @@ -40,7 +41,7 @@ export function Vote({ const userTokenAccount = useAccountByMint(proposal.info.sourceMint); - const [mode, setMode] = useState(true); + const [_, setMode, getLatestMode] = useLatestState(true); const eligibleToView = timelockConfig.info.votingEntryRule == VotingEntryRule.Anytime && @@ -76,17 +77,11 @@ export function Vote({ cancelText: LABELS.CANCEL, onOk: async () => { if (userTokenAccount) { - // mode is out of date in this scope, so we use a trick to get it here. - const valueHolder = { mode: true }; - await setMode(mode => { - valueHolder.mode = mode; - return mode; - }); - + const modeValue = await getLatestMode(); const voteAmount = userTokenAccount.info.amount.toNumber(); - const yesTokenAmount = valueHolder.mode ? voteAmount : 0; - const noTokenAmount = !valueHolder.mode ? voteAmount : 0; + const yesTokenAmount = modeValue ? voteAmount : 0; + const noTokenAmount = !modeValue ? voteAmount : 0; await depositSourceTokensAndVote( connection, diff --git a/packages/proposals/src/hooks/useLatestState.ts b/packages/proposals/src/hooks/useLatestState.ts new file mode 100644 index 0000000..cb7ae91 --- /dev/null +++ b/packages/proposals/src/hooks/useLatestState.ts @@ -0,0 +1,15 @@ +import { useState } from 'react'; + +// Extends useState() hook with async getLatestState which can be used in async callbacks where state property would be stale +export function useLatestState(initialState: T) { + const [state, setState] = useState(initialState); + const getLatestState = () => + new Promise(resolve => { + setState(s => { + resolve(s); + return s; + }); + }); + + return [state, setState, getLatestState] as const; +} From 5816cbda5e6e80cd93e281d480c477f2cad0f089 Mon Sep 17 00:00:00 2001 From: "Sebastian.Bor" Date: Fri, 2 Apr 2021 19:08:13 +0100 Subject: [PATCH 05/12] fix: restrict voting to Voting state and governance token avaliability --- packages/proposals/src/components/Proposal/Vote.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/proposals/src/components/Proposal/Vote.tsx b/packages/proposals/src/components/Proposal/Vote.tsx index 739c5cb..46ce16b 100644 --- a/packages/proposals/src/components/Proposal/Vote.tsx +++ b/packages/proposals/src/components/Proposal/Vote.tsx @@ -44,10 +44,9 @@ export function Vote({ const [_, setMode, getLatestMode] = useLatestState(true); const eligibleToView = - timelockConfig.info.votingEntryRule == VotingEntryRule.Anytime && - [TimelockStateStatus.Draft, TimelockStateStatus.Voting].includes( - state.info.status, - ); + userTokenAccount && + userTokenAccount.info.amount.toNumber() > 0 && + state.info.status === TimelockStateStatus.Voting; return eligibleToView ? ( ) : null; } diff --git a/packages/proposals/src/constants/labels.ts b/packages/proposals/src/constants/labels.ts index 90f4c3d..25f5c86 100644 --- a/packages/proposals/src/constants/labels.ts +++ b/packages/proposals/src/constants/labels.ts @@ -72,7 +72,14 @@ export const LABELS = { TOKENS_WITHDRAWN: 'Voting tokens refunded as Source Tokens', CONFIRM: 'Confirm', CANCEL: 'Cancel', - REFUND_TOKENS: 'Refund My Tokens', + + WITHDRAW_VOTE: 'Withdraw My Vote', + WITHDRAW: 'Withdraw', + + WITHDRAW_YOUR_VOTE_QUESTION: 'Withdraw your vote?', + WITHDRAW_YOUR_VOTE_MSG: + 'Once you withdraw your vote it won’t count towards the proposal voting outcome.', + REGISTER_GOVERNANCE: 'Register', PROGRAM: 'Program ID', GOVERNANCE: 'Governance Token Holders', diff --git a/packages/proposals/src/views/proposal/index.tsx b/packages/proposals/src/views/proposal/index.tsx index d858064..7eaec8a 100644 --- a/packages/proposals/src/views/proposal/index.tsx +++ b/packages/proposals/src/views/proposal/index.tsx @@ -24,7 +24,7 @@ import SignButton from '../../components/Proposal/SignButton'; import AddSigners from '../../components/Proposal/AddSigners'; import MintSourceTokens from '../../components/Proposal/MintSourceTokens'; import { Vote } from '../../components/Proposal/Vote'; -import { WithdrawTokens } from '../../components/Proposal/WithdrawTokens'; +import { WithdrawVote } from '../../components/Proposal/WithdrawVote'; import './style.less'; import { getGovernanceVotingRecords } from '../../utils/lookups'; import BN from 'bn.js'; @@ -295,7 +295,7 @@ function InnerProposalView({ timelockConfig.info.governanceMint.toBase58() } /> - Date: Mon, 5 Apr 2021 20:50:17 +0100 Subject: [PATCH 09/12] choore: move useThatState hook to commom package --- packages/common/src/hooks/index.ts | 1 + packages/common/src/hooks/useThatState.ts | 16 ++++++++++++++++ .../proposals/src/components/Proposal/Vote.tsx | 6 +++--- packages/proposals/src/hooks/useLatestState.ts | 15 --------------- 4 files changed, 20 insertions(+), 18 deletions(-) create mode 100644 packages/common/src/hooks/useThatState.ts delete mode 100644 packages/proposals/src/hooks/useLatestState.ts diff --git a/packages/common/src/hooks/index.ts b/packages/common/src/hooks/index.ts index 15ecc00..24aa229 100644 --- a/packages/common/src/hooks/index.ts +++ b/packages/common/src/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useUserAccounts'; export * from './useAccountByMint'; export * from './useTokenName'; +export * from './useThatState'; diff --git a/packages/common/src/hooks/useThatState.ts b/packages/common/src/hooks/useThatState.ts new file mode 100644 index 0000000..86ecf10 --- /dev/null +++ b/packages/common/src/hooks/useThatState.ts @@ -0,0 +1,16 @@ +import { useState } from 'react'; + +// Extends useState() hook with async getThatState getter which can be used to get state value in contexts (ex. async callbacks) where up to date state is not available +export function useThatState(initialState: T) { + const [state, setState] = useState(initialState); + const getThatState = () => + new Promise(resolve => { + // Use NOP setState call to retrieve current state value + setState(s => { + resolve(s); + return s; + }); + }); + + return [state, setState, getThatState] as const; +} diff --git a/packages/proposals/src/components/Proposal/Vote.tsx b/packages/proposals/src/components/Proposal/Vote.tsx index 868725e..cc18b0b 100644 --- a/packages/proposals/src/components/Proposal/Vote.tsx +++ b/packages/proposals/src/components/Proposal/Vote.tsx @@ -11,12 +11,12 @@ import { LABELS } from '../../constants'; import { depositSourceTokensAndVote } from '../../actions/depositSourceTokensAndVote'; import { contexts, hooks } from '@oyster/common'; import { ExclamationCircleOutlined } from '@ant-design/icons'; -import { useLatestState } from '../../hooks/useLatestState'; + import './style.less'; const { useWallet } = contexts.Wallet; const { useConnection } = contexts.Connection; -const { useAccountByMint } = hooks; +const { useAccountByMint, useThatState } = hooks; const { confirm } = Modal; export function Vote({ @@ -37,7 +37,7 @@ export function Vote({ const userTokenAccount = useAccountByMint(proposal.info.sourceMint); - const [vote, setVote, getLatestVote] = useLatestState(0); + const [vote, setVote, getLatestVote] = useThatState(0); const eligibleToView = userTokenAccount && diff --git a/packages/proposals/src/hooks/useLatestState.ts b/packages/proposals/src/hooks/useLatestState.ts deleted file mode 100644 index cb7ae91..0000000 --- a/packages/proposals/src/hooks/useLatestState.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useState } from 'react'; - -// Extends useState() hook with async getLatestState which can be used in async callbacks where state property would be stale -export function useLatestState(initialState: T) { - const [state, setState] = useState(initialState); - const getLatestState = () => - new Promise(resolve => { - setState(s => { - resolve(s); - return s; - }); - }); - - return [state, setState, getLatestState] as const; -} From a3129aaab7055a634ae5391a591833325ada330b Mon Sep 17 00:00:00 2001 From: "Sebastian.Bor" Date: Mon, 5 Apr 2021 22:11:22 +0100 Subject: [PATCH 10/12] feat: split vote button into yeah/nay buttons on proposal page --- .../src/components/Proposal/Vote.tsx | 39 +++++++------------ packages/proposals/src/constants/labels.ts | 9 ++++- .../proposals/src/views/proposal/index.tsx | 7 ++++ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/proposals/src/components/Proposal/Vote.tsx b/packages/proposals/src/components/Proposal/Vote.tsx index cc18b0b..69892da 100644 --- a/packages/proposals/src/components/Proposal/Vote.tsx +++ b/packages/proposals/src/components/Proposal/Vote.tsx @@ -1,5 +1,5 @@ import { ParsedAccount } from '@oyster/common'; -import { Button, Col, Modal, Row, Switch, Radio } from 'antd'; +import { Button, Col, Modal, Row } from 'antd'; import React from 'react'; import { TimelockConfig, @@ -16,17 +16,19 @@ import './style.less'; const { useWallet } = contexts.Wallet; const { useConnection } = contexts.Connection; -const { useAccountByMint, useThatState } = hooks; +const { useAccountByMint } = hooks; const { confirm } = Modal; export function Vote({ proposal, state, timelockConfig, + yeahVote, }: { proposal: ParsedAccount; state: ParsedAccount; timelockConfig: ParsedAccount; + yeahVote: boolean; }) { const wallet = useWallet(); const connection = useConnection(); @@ -37,50 +39,37 @@ export function Vote({ const userTokenAccount = useAccountByMint(proposal.info.sourceMint); - const [vote, setVote, getLatestVote] = useThatState(0); - const eligibleToView = userTokenAccount && userTokenAccount.info.amount.toNumber() > 0 && state.info.status === TimelockStateStatus.Voting; + const btnLabel = yeahVote ? LABELS.VOTE_YEAH : LABELS.VOTE_NAY; + const title = yeahVote ? LABELS.VOTE_YEAH_QUESTION : LABELS.VOTE_NAY_QUESTION; + const msg = yeahVote ? LABELS.VOTE_YEAH_MSG : LABELS.VOTE_NAY_MSG; + return eligibleToView ? ( ) : null; } diff --git a/packages/proposals/src/constants/labels.ts b/packages/proposals/src/constants/labels.ts index 25f5c86..f2a6f3e 100644 --- a/packages/proposals/src/constants/labels.ts +++ b/packages/proposals/src/constants/labels.ts @@ -65,7 +65,14 @@ export const LABELS = { ADD_GOVERNANCE_TOKENS: 'Add Governance Tokens', ADD_COUNCIL_TOKENS: 'Add Council Tokens', ACTIONS: 'Actions', - VOTE: 'Vote', + + VOTE_YEAH: 'Yeah', + VOTE_NAY: 'Nay', + VOTE_YEAH_QUESTION: 'Vote Yeah?', + VOTE_YEAH_MSG: 'Vote in favour of the proposal.', + VOTE_NAY_QUESTION: 'Vote Nay?', + VOTE_NAY_MSG: 'Vote against the proposal.', + EXECUTING: 'Executing...', EXECUTED: 'Executed.', WITHDRAWING_VOTING_TOKENS: 'Refunding voting tokens as Source Tokens', diff --git a/packages/proposals/src/views/proposal/index.tsx b/packages/proposals/src/views/proposal/index.tsx index 7eaec8a..d2ebbfb 100644 --- a/packages/proposals/src/views/proposal/index.tsx +++ b/packages/proposals/src/views/proposal/index.tsx @@ -304,6 +304,13 @@ function InnerProposalView({ timelockConfig={timelockConfig} proposal={proposal} state={timelockState} + yeahVote={true} + /> + From af2969ea35499d5333fd243055fdd743fce6052a Mon Sep 17 00:00:00 2001 From: "Sebastian.Bor" Date: Tue, 6 Apr 2021 10:19:58 +0100 Subject: [PATCH 11/12] fix: update voting messages and icons --- .../src/actions/depositSourceTokensAndVote.ts | 22 +++++++++++----- .../src/components/Proposal/Vote.tsx | 25 +++++++++++++++---- packages/proposals/src/constants/labels.ts | 15 ++++++----- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/proposals/src/actions/depositSourceTokensAndVote.ts b/packages/proposals/src/actions/depositSourceTokensAndVote.ts index 41309ab..6246bae 100644 --- a/packages/proposals/src/actions/depositSourceTokensAndVote.ts +++ b/packages/proposals/src/actions/depositSourceTokensAndVote.ts @@ -168,8 +168,21 @@ export const depositSourceTokensAndVote = async ( ), ); + const [votingMsg, votedMsg, voteTokensMsg] = + yesVotingTokenAmount > 0 + ? [ + LABELS.VOTING_YEAH, + LABELS.VOTED_YEAH, + `${yesVotingTokenAmount} ${LABELS.TOKENS_VOTED_FOR_THE_PROPOSAL}.`, + ] + : [ + LABELS.VOTING_NAY, + LABELS.VOTED_NAY, + `${noVotingTokenAmount} ${LABELS.TOKENS_VOTED_AGAINST_THE_PROPOSAL}.`, + ]; + notify({ - message: LABELS.VOTING_FOR_PROPOSAL, + message: votingMsg, description: LABELS.PLEASE_WAIT, type: 'warn', }); @@ -184,12 +197,9 @@ export const depositSourceTokensAndVote = async ( ); notify({ - message: LABELS.PROPOSAL_VOTED, + message: votedMsg, type: 'success', - description: - yesVotingTokenAmount > 0 - ? `${yesVotingTokenAmount} ${LABELS.TOKENS_VOTED_FOR_THE_PROPOSAL}.` - : `${noVotingTokenAmount} ${LABELS.TOKENS_VOTED_AGAINST_THE_PROPOSAL}.`, + description: voteTokensMsg, }); } catch (ex) { console.error(ex); diff --git a/packages/proposals/src/components/Proposal/Vote.tsx b/packages/proposals/src/components/Proposal/Vote.tsx index 69892da..c528b04 100644 --- a/packages/proposals/src/components/Proposal/Vote.tsx +++ b/packages/proposals/src/components/Proposal/Vote.tsx @@ -10,7 +10,11 @@ import { import { LABELS } from '../../constants'; import { depositSourceTokensAndVote } from '../../actions/depositSourceTokensAndVote'; import { contexts, hooks } from '@oyster/common'; -import { ExclamationCircleOutlined } from '@ant-design/icons'; +import { + ExclamationCircleOutlined, + CheckOutlined, + CloseOutlined, +} from '@ant-design/icons'; import './style.less'; @@ -44,17 +48,28 @@ export function Vote({ userTokenAccount.info.amount.toNumber() > 0 && state.info.status === TimelockStateStatus.Voting; - const btnLabel = yeahVote ? LABELS.VOTE_YEAH : LABELS.VOTE_NAY; - const title = yeahVote ? LABELS.VOTE_YEAH_QUESTION : LABELS.VOTE_NAY_QUESTION; - const msg = yeahVote ? LABELS.VOTE_YEAH_MSG : LABELS.VOTE_NAY_MSG; + const [btnLabel, title, msg, icon] = yeahVote + ? [ + LABELS.VOTE_YEAH, + LABELS.VOTE_YEAH_QUESTION, + LABELS.VOTE_YEAH_MSG, + , + ] + : [ + LABELS.VOTE_NAY, + LABELS.VOTE_NAY_QUESTION, + LABELS.VOTE_NAY_MSG, + , + ]; return eligibleToView ? ( ) : null; } diff --git a/packages/proposals/src/constants/labels.ts b/packages/proposals/src/constants/labels.ts index 3727cf0..f0e3d71 100644 --- a/packages/proposals/src/constants/labels.ts +++ b/packages/proposals/src/constants/labels.ts @@ -64,31 +64,39 @@ export const LABELS = { VOTE_YEAH: 'Yeah', VOTE_YEAH_QUESTION: 'Vote Yeah?', VOTE_YEAH_MSG: 'Vote in favour of the proposal.', - VOTING_YEAH: 'Voting for the proposal.', - VOTED_YEAH: 'Voted for the proposal.', + VOTING_YEAH: 'Voting for the proposal', + VOTED_YEAH: 'Voted for the proposal', VOTE_NAY: 'Nay', VOTE_NAY_QUESTION: 'Vote Nay?', VOTE_NAY_MSG: 'Vote against the proposal.', - VOTING_NAY: 'Voting against the proposal.', - VOTED_NAY: 'Voted against the proposal.', + VOTING_NAY: 'Voting against the proposal', + VOTED_NAY: 'Voted against the proposal', TOKENS_VOTED_FOR_THE_PROPOSAL: 'tokens voted for the proposal', TOKENS_VOTED_AGAINST_THE_PROPOSAL: 'tokens voted against the proposal', EXECUTING: 'Executing...', EXECUTED: 'Executed.', - WITHDRAWING_VOTING_TOKENS: 'Refunding voting tokens as Source Tokens', - TOKENS_WITHDRAWN: 'Voting tokens refunded as Source Tokens', + CONFIRM: 'Confirm', CANCEL: 'Cancel', WITHDRAW_VOTE: 'Withdraw My Vote', - WITHDRAW: 'Withdraw', - WITHDRAW_YOUR_VOTE_QUESTION: 'Withdraw your vote?', WITHDRAW_YOUR_VOTE_MSG: 'Once you withdraw your vote it won’t count towards the proposal voting outcome.', + WITHDRAW: 'Withdraw', + WITHDRAWING_YOUR_VOTE: 'Withdrawing your vote', + VOTE_WITHDRAWN: 'Your vote has been withdrawn', + + REFUND_TOKENS: 'Refund My Tokens', + REFUND_YOUR_TOKENS_QUESTION: 'Refund your tokens?', + REFUND_YOUR_TOKENS_MSG: + 'The proposal has been voted. Refunding your tokens won’t change the outcome.', + REFUND: 'Refund', + REFUNDING_YOUR_TOKENS: 'Refunding your tokens', + TOKENS_REFUNDED: 'Your voting tokens have been refunded', REGISTER_GOVERNANCE: 'Register', PROGRAM: 'Program ID',