mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-06-17 06:41:04 +00:00
d21bfb1320
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
// Copyright 2017-2025 @pezkuwi/app-council authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type { DeriveElectionsInfo } from '@pezkuwi/api-derive/types';
|
|
import type { BN } from '@pezkuwi/util';
|
|
|
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
|
|
import { Button, InputAddress, InputAddressMulti, InputBalance, Modal, TxButton, VoteValue } from '@pezkuwi/react-components';
|
|
import { useApi, useToggle } from '@pezkuwi/react-hooks';
|
|
import { BN_ZERO } from '@pezkuwi/util';
|
|
|
|
import { useTranslation } from '../translate.js';
|
|
import { useModuleElections } from '../useModuleElections.js';
|
|
|
|
interface Props {
|
|
className?: string;
|
|
electionsInfo?: DeriveElectionsInfo;
|
|
}
|
|
|
|
const MAX_VOTES = 16;
|
|
|
|
function Vote ({ electionsInfo }: Props): React.ReactElement<Props> | null {
|
|
const { t } = useTranslation();
|
|
const { api } = useApi();
|
|
const [isVisible, toggleVisible] = useToggle();
|
|
const [accountId, setAccountId] = useState<string | null>(null);
|
|
const [available, setAvailable] = useState<string[]>([]);
|
|
const [defaultVotes, setDefaultVotes] = useState<string[]>([]);
|
|
const [votes, setVotes] = useState<string[]>([]);
|
|
const [voteValue, setVoteValue] = useState(BN_ZERO);
|
|
const modLocation = useModuleElections();
|
|
|
|
useEffect((): void => {
|
|
if (electionsInfo) {
|
|
const { candidates, members, runnersUp } = electionsInfo;
|
|
|
|
setAvailable(
|
|
members
|
|
.map(([accountId]) => accountId.toString())
|
|
.concat(runnersUp.map(([accountId]) => accountId.toString()))
|
|
.concat(candidates.map((accountId) => accountId.toString()))
|
|
);
|
|
}
|
|
}, [electionsInfo]);
|
|
|
|
useEffect((): void => {
|
|
accountId && api.derive.council
|
|
.votesOf(accountId)
|
|
.then(({ votes }): void => {
|
|
setDefaultVotes(
|
|
votes
|
|
.map((a) => a.toString())
|
|
.filter((a) => available.includes(a))
|
|
);
|
|
})
|
|
.catch(console.error);
|
|
}, [api, accountId, available]);
|
|
|
|
const bondValue = useMemo(
|
|
(): BN | undefined => {
|
|
const location = api.consts.elections || api.consts.phragmenElection || api.consts.electionsPhragmen;
|
|
|
|
return location &&
|
|
location.votingBondBase &&
|
|
location.votingBondBase.add(location.votingBondFactor.muln(votes.length));
|
|
},
|
|
[api, votes]
|
|
);
|
|
|
|
if (!modLocation) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Button
|
|
icon='check-to-slot'
|
|
isDisabled={available.length === 0}
|
|
label={t('Vote')}
|
|
onClick={toggleVisible}
|
|
/>
|
|
{isVisible && (
|
|
<Modal
|
|
header={t('Vote for current candidates')}
|
|
onClose={toggleVisible}
|
|
size='large'
|
|
>
|
|
<Modal.Content>
|
|
<Modal.Columns hint={t('The vote will be recorded for the selected account.')}>
|
|
<InputAddress
|
|
label={t('voting account')}
|
|
onChange={setAccountId}
|
|
type='account'
|
|
/>
|
|
</Modal.Columns>
|
|
<Modal.Columns hint={t('The value associated with this vote. The amount will be locked (not available for transfer) and used in all subsequent elections.')}>
|
|
<VoteValue
|
|
accountId={accountId}
|
|
onChange={setVoteValue}
|
|
/>
|
|
</Modal.Columns>
|
|
<Modal.Columns
|
|
hint={
|
|
<>
|
|
<p>{t('The votes for the members, runner-ups and candidates. These should be ordered based on your priority.')}</p>
|
|
<p>{t('In calculating the election outcome, this prioritized vote ordering will be used to determine the final score for the candidates.')}</p>
|
|
</>
|
|
}
|
|
>
|
|
<InputAddressMulti
|
|
available={available}
|
|
availableLabel={t('council candidates')}
|
|
defaultValue={defaultVotes}
|
|
maxCount={MAX_VOTES}
|
|
onChange={setVotes}
|
|
valueLabel={t('my ordered votes')}
|
|
/>
|
|
</Modal.Columns>
|
|
{bondValue && (
|
|
<Modal.Columns hint={t('The amount will be reserved for the duration of your vote')}>
|
|
<InputBalance
|
|
defaultValue={bondValue}
|
|
isDisabled
|
|
label={t('voting bond')}
|
|
/>
|
|
</Modal.Columns>
|
|
)}
|
|
</Modal.Content>
|
|
<Modal.Actions>
|
|
<TxButton
|
|
accountId={accountId}
|
|
icon='trash-alt'
|
|
isDisabled={!defaultVotes.length}
|
|
label={t('Unvote all')}
|
|
onStart={toggleVisible}
|
|
tx={api.tx[modLocation].removeVoter}
|
|
/>
|
|
<TxButton
|
|
accountId={accountId}
|
|
isDisabled={!accountId || votes.length === 0 || voteValue.lten(0)}
|
|
label={t('Vote')}
|
|
onStart={toggleVisible}
|
|
params={[votes, voteValue]}
|
|
tx={api.tx[modLocation].vote}
|
|
/>
|
|
</Modal.Actions>
|
|
</Modal>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default React.memo(Vote);
|