Files
pezkuwi-apps/packages/page-council/src/Overview/Vote.tsx
T
pezkuwichain d21bfb1320 feat: initial Pezkuwi Apps rebrand from polkadot-apps
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
2026-01-07 13:05:27 +03:00

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);