diff --git a/package.json b/package.json index ee24adaf31..6b259649cc 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "pioneer/packages/joy-utils", "pioneer/packages/joy-members", "pioneer/packages/joy-pages", + "pioneer/packages/joy-election", "utils/api-examples" ], "resolutions": { diff --git a/pioneer/.eslintignore b/pioneer/.eslintignore index 42a2471efd..57bb4f9e3e 100644 --- a/pioneer/.eslintignore +++ b/pioneer/.eslintignore @@ -2,7 +2,6 @@ **/coverage/* **/node_modules/* packages/old-apps/* -packages/joy-election/* packages/joy-forum/* packages/joy-help/* packages/joy-media/* diff --git a/pioneer/packages/apps-routing/src/index.ts b/pioneer/packages/apps-routing/src/index.ts index 47899c3931..a2fe3c0ce1 100644 --- a/pioneer/packages/apps-routing/src/index.ts +++ b/pioneer/packages/apps-routing/src/index.ts @@ -21,11 +21,13 @@ import transfer from './transfer'; // Joy packages import members from './joy-members'; import { terms, privacyPolicy } from './joy-pages'; +import election from './joy-election'; export default function create (t: (key: string, text: string, options: { ns: string }) => T): Routes { return appSettings.uiMode === 'light' ? [ members(t), + election(t), staking(t), null, transfer(t), @@ -37,6 +39,7 @@ export default function create (t: (key: string, text: string, opti ] : [ members(t), + election(t), staking(t), null, transfer(t), diff --git a/pioneer/packages/apps-routing/src/joy-election.ts b/pioneer/packages/apps-routing/src/joy-election.ts new file mode 100644 index 0000000000..cbff554d99 --- /dev/null +++ b/pioneer/packages/apps-routing/src/joy-election.ts @@ -0,0 +1,17 @@ +import { Route } from './types'; + +import Election from '@polkadot/joy-election/index'; +import SidebarSubtitle from '@polkadot/joy-election/SidebarSubtitle'; + +export default function create (t: (key: string, text: string, options: { ns: string }) => T): Route { + return { + Component: Election, + display: { + needsApi: ['query.council.activeCouncil', 'query.councilElection.stage'] + }, + text: t('nav.election', 'Council', { ns: 'apps-routing' }), + icon: 'university', + name: 'council', + SubtitleComponent: SidebarSubtitle + }; +} diff --git a/pioneer/packages/apps-routing/src/types.ts b/pioneer/packages/apps-routing/src/types.ts index 6dce432d8c..4a6695fef2 100644 --- a/pioneer/packages/apps-routing/src/types.ts +++ b/pioneer/packages/apps-routing/src/types.ts @@ -24,6 +24,8 @@ export interface Route { name: string; text: string; useCounter?: () => number | string | null; + // Joystream-specific + SubtitleComponent?: React.ComponentType; } export type Routes = (Route | null)[]; diff --git a/pioneer/packages/apps/src/Content/index.tsx b/pioneer/packages/apps/src/Content/index.tsx index 63f0238535..cf2dc00224 100644 --- a/pioneer/packages/apps/src/Content/index.tsx +++ b/pioneer/packages/apps/src/Content/index.tsx @@ -16,6 +16,11 @@ import { useTranslation } from '../translate'; import NotFound from './NotFound'; import Status from './Status'; +// Joystream-specific +// We must use transport provider here instead of /apps/src/index +// to avoid "Cannot create Transport: The Substrate API is not ready yet." error +import { TransportProvider } from '@polkadot/joy-utils/react/context'; + interface Props { className?: string; } @@ -60,11 +65,25 @@ function Content ({ className }: Props): React.ReactElement { ? : ( - + { needsApi + // Add transport provider for routes that need the api + // (the above condition makes sure it's aleady initialized at this point) + ? ( + + + + ) + : ( + + ) } ) } diff --git a/pioneer/packages/apps/src/SideBar/Item.tsx b/pioneer/packages/apps/src/SideBar/Item.tsx index abcd801144..629f9c9c19 100644 --- a/pioneer/packages/apps/src/SideBar/Item.tsx +++ b/pioneer/packages/apps/src/SideBar/Item.tsx @@ -79,12 +79,15 @@ function Item ({ isCollapsed, onClick, route }: Props): React.ReactElement - {text} + + {text} + { SubtitleComponent && } + {!!count && ( { render () { - const { index, accountId, stake } = this.props; + const { index, accountId, stake, isVotingStage } = this.props; const voteUrl = `/council/votes?applicantId=${accountId.toString()}`; return ( @@ -33,9 +34,11 @@ class Applicant extends React.PureComponent { {formatBalance(calcTotalStake(stake))} - - Vote - + { isVotingStage && ( + + Vote + + ) } ); } diff --git a/pioneer/packages/joy-election/src/Applicants.tsx b/pioneer/packages/joy-election/src/Applicants.tsx index e84c85f3d5..3ee84db4d7 100644 --- a/pioneer/packages/joy-election/src/Applicants.tsx +++ b/pioneer/packages/joy-election/src/Applicants.tsx @@ -1,49 +1,68 @@ import React from 'react'; -import { Table } from 'semantic-ui-react'; +import { Table, Message } from 'semantic-ui-react'; import BN from 'bn.js'; import { I18nProps } from '@polkadot/react-components/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { withCalls } from '@polkadot/react-api/with'; +import { withCalls } from '@polkadot/react-api/hoc'; import { AccountId } from '@polkadot/types/interfaces'; +import { Option } from '@polkadot/types'; import { formatNumber } from '@polkadot/util'; import translate from './translate'; import Applicant from './Applicant'; import ApplyForm from './ApplyForm'; -import Section from '@polkadot/joy-utils/Section'; -import { queryToProp } from '@polkadot/joy-utils/index'; -import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount'; +import Section from '@polkadot/joy-utils/react/components/Section'; +import { queryToProp } from '@polkadot/joy-utils/functions/misc'; +import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts'; +import { ElectionStage } from '@joystream/types/src/council'; +import { RouteProps } from 'react-router-dom'; -type Props = ApiProps & I18nProps & MyAccountProps & { +type Props = RouteProps & ApiProps & I18nProps & MyAccountProps & { candidacyLimit?: BN; applicants?: Array; + stage?: Option; }; class Applicants extends React.PureComponent { - private renderTable = (applicants: Array) => ( - - - - # - Applicant - Total stake - Actions - - - {applicants.map((accountId, index) => ( - - ))} -
- ) + private renderTable = (applicants: Array) => { + const isVotingStage = this.props.stage?.unwrapOr(undefined)?.isOfType('Voting') || false; + + return ( + + + + # + Applicant + Total stake + { isVotingStage && ( + Actions + ) } + + + {applicants.map((accountId, index) => ( + + ))} +
+ ); + } render () { - const { myAddress, applicants = [], candidacyLimit = new BN(0) } = this.props; + const { myAddress, applicants = [], candidacyLimit = new BN(0), stage } = this.props; const title = Applicants {applicants.length}/{formatNumber(candidacyLimit)}; return <>
- + { stage?.unwrapOr(undefined)?.isOfType('Announcing') + ? ( + + ) + : ( + + Applying to council is only possible during Announcing stage. + + ) + }
{!applicants.length @@ -59,6 +78,7 @@ class Applicants extends React.PureComponent { export default translate( withCalls( queryToProp('query.councilElection.candidacyLimit'), - queryToProp('query.councilElection.applicants') + queryToProp('query.councilElection.applicants'), + queryToProp('query.councilElection.stage') )(withMyAccount(Applicants)) ); diff --git a/pioneer/packages/joy-election/src/ApplyForm.tsx b/pioneer/packages/joy-election/src/ApplyForm.tsx index f058fc992c..5685d3371b 100644 --- a/pioneer/packages/joy-election/src/ApplyForm.tsx +++ b/pioneer/packages/joy-election/src/ApplyForm.tsx @@ -3,21 +3,21 @@ import React from 'react'; import { I18nProps } from '@polkadot/react-components/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { withCalls, withMulti } from '@polkadot/react-api/with'; +import { withCalls, withMulti } from '@polkadot/react-api/hoc'; import { Labelled } from '@polkadot/react-components/index'; import { Balance } from '@polkadot/types/interfaces'; import translate from './translate'; -import TxButton from '@polkadot/joy-utils/TxButton'; -import InputStake from '@polkadot/joy-utils/InputStake'; +import TxButton from '@polkadot/joy-utils/react/components/TxButton'; +import InputStake from '@polkadot/joy-utils/react/components/InputStake'; import { ElectionStake } from '@joystream/types/council'; -import { calcTotalStake, ZERO } from '@polkadot/joy-utils/index'; -import { MyAddressProps, withOnlyMembers } from '@polkadot/joy-utils/MyAccount'; +import { calcTotalStake, ZERO } from '@polkadot/joy-utils/functions/misc'; +import { MyAddressProps } from '@polkadot/joy-utils/react/hocs/accounts'; +import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards'; type Props = ApiProps & I18nProps & MyAddressProps & { minStake?: Balance; alreadyStaked?: ElectionStake; - myBalance?: Balance; }; type State = { @@ -48,15 +48,17 @@ class ApplyForm extends React.PureComponent { isValid={isStakeValid} onChange={this.onChangeStake} /> - - - +
+ + + +
); } @@ -71,10 +73,9 @@ class ApplyForm extends React.PureComponent { private onChangeStake = (stake?: BN): void => { stake = stake || ZERO; - const { myBalance = ZERO } = this.props; - const isStakeLteBalance = stake.lte(myBalance); const isStakeGteMinStake = stake.add(this.alreadyStaked()).gte(this.minStake()); - const isStakeValid = !stake.isZero() && isStakeGteMinStake && isStakeLteBalance; + const isStakeValid = !stake.isZero() && isStakeGteMinStake; + this.setState({ stake, isStakeValid }); } } @@ -88,8 +89,6 @@ export default withMulti( ['query.councilElection.minCouncilStake', { propName: 'minStake' }], ['query.councilElection.applicantStakes', - { paramName: 'myAddress', propName: 'alreadyStaked' }], - ['query.balances.freeBalance', - { paramName: 'myAddress', propName: 'myBalance' }] + { paramName: 'myAddress', propName: 'alreadyStaked' }] ) ); diff --git a/pioneer/packages/joy-election/src/CandidatePreview.tsx b/pioneer/packages/joy-election/src/CandidatePreview.tsx index 9e6571efb6..bab07182e7 100644 --- a/pioneer/packages/joy-election/src/CandidatePreview.tsx +++ b/pioneer/packages/joy-election/src/CandidatePreview.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import AddressMini from '@polkadot/react-components/AddressMiniJoy'; -import MemberByAccount from '@polkadot/joy-utils/MemberByAccountPreview'; +import AddressMini from '@polkadot/react-components/AddressMini'; +import MemberByAccount from '@polkadot/joy-utils/react/components/MemberByAccountPreview'; import { AccountId } from '@polkadot/types/interfaces'; import styled from 'styled-components'; diff --git a/pioneer/packages/joy-election/src/Council.tsx b/pioneer/packages/joy-election/src/Council.tsx index b81c144883..f4e9e9597d 100644 --- a/pioneer/packages/joy-election/src/Council.tsx +++ b/pioneer/packages/joy-election/src/Council.tsx @@ -2,22 +2,22 @@ import React from 'react'; import { ApiProps } from '@polkadot/react-api/types'; import { I18nProps } from '@polkadot/react-components/types'; -import { withCalls } from '@polkadot/react-api/with'; +import { withCalls } from '@polkadot/react-api/hoc'; import { Table } from 'semantic-ui-react'; import { formatBalance } from '@polkadot/util'; import CouncilCandidate from './CandidatePreview'; -import { calcBackersStake } from '@polkadot/joy-utils/index'; +import { calcBackersStake } from '@polkadot/joy-utils/functions/misc'; import { Seat } from '@joystream/types/council'; import translate from './translate'; -import Section from '@polkadot/joy-utils/Section'; +import Section from '@polkadot/joy-utils/react/components/Section'; +import { RouteProps } from 'react-router-dom'; -type Props = ApiProps & -I18nProps & { +type Props = RouteProps & ApiProps & I18nProps & { council?: Seat[]; }; -type State = {}; +type State = Record; class Council extends React.PureComponent { state: State = {}; @@ -53,9 +53,10 @@ class Council extends React.PureComponent { render () { const { council = [] } = this.props; + // console.log({ council }); return ( -
+
{!council.length ? Council is not elected yet : this.renderTable(council)}
); diff --git a/pioneer/packages/joy-election/src/Dashboard.tsx b/pioneer/packages/joy-election/src/Dashboard.tsx index c7f64ef811..da6fffbd9c 100644 --- a/pioneer/packages/joy-election/src/Dashboard.tsx +++ b/pioneer/packages/joy-election/src/Dashboard.tsx @@ -3,18 +3,19 @@ import React from 'react'; import { ApiProps } from '@polkadot/react-api/types'; import { I18nProps } from '@polkadot/react-components/types'; -import { withCalls } from '@polkadot/react-api/with'; +import { withCalls } from '@polkadot/react-api/hoc'; import { Option } from '@polkadot/types'; import { BlockNumber, Balance } from '@polkadot/types/interfaces'; -import { Bubble } from '@polkadot/react-components/index'; +import { Label, Icon } from 'semantic-ui-react'; import { formatNumber, formatBalance } from '@polkadot/util'; -import Section from '@polkadot/joy-utils/Section'; -import { queryToProp } from '@polkadot/joy-utils/index'; +import Section from '@polkadot/joy-utils/react/components/Section'; +import { queryToProp } from '@polkadot/joy-utils/functions/misc'; import { ElectionStage, Seat } from '@joystream/types/council'; import translate from './translate'; +import { RouteProps } from 'react-router-dom'; -type Props = ApiProps & I18nProps & { +type Props = RouteProps & ApiProps & I18nProps & { bestNumber?: BN; activeCouncil?: Seat[]; @@ -34,7 +35,7 @@ type Props = ApiProps & I18nProps & { stage?: Option; }; -type State = {}; +type State = Record; class Dashboard extends React.PureComponent { state: State = {}; @@ -45,12 +46,17 @@ class Dashboard extends React.PureComponent { const title = `Council ${activeCouncil.length > 0 ? '' : '(not elected)'}`; return
- - {activeCouncil.length} - - - {formatNumber(p.termEndsAt)} - + + + +
; } @@ -59,13 +65,16 @@ class Dashboard extends React.PureComponent { let stageName: string | undefined; let stageEndsAt: BlockNumber | undefined; + if (stage && stage.isSome) { const stageValue = stage.value as ElectionStage; + stageEndsAt = stageValue.value as BlockNumber; // contained value stageName = stageValue.type; // name of Enum variant } let leftBlocks: BN | undefined; + if (stageEndsAt && bestNumber) { leftBlocks = stageEndsAt.sub(bestNumber); } @@ -76,20 +85,28 @@ class Dashboard extends React.PureComponent { const title = <>Election ({stateText}); return
- - {formatNumber(round)} - - {isRunning && <> - - {stageName} - - - {formatNumber(leftBlocks)} - - - {formatNumber(stageEndsAt)} - - } + + + {isRunning && <> + + + + } +
; } @@ -98,33 +115,46 @@ class Dashboard extends React.PureComponent { const isAutoStart = (p.autoStart || false).valueOf(); return
- - {isAutoStart ? 'Yes' : 'No'} - - - {formatNumber(p.newTermDuration)} - - - {formatNumber(p.candidacyLimit)} - - - {formatNumber(p.councilSize)} - - - {formatBalance(p.minCouncilStake)} - - - {formatBalance(p.minVotingStake)} - - - {formatNumber(p.announcingPeriod)} blocks - - - {formatNumber(p.votingPeriod)} blocks - - - {formatNumber(p.revealingPeriod)} blocks - + + + + + + + + + + + + +
; } diff --git a/pioneer/packages/joy-election/src/Reveals.tsx b/pioneer/packages/joy-election/src/Reveals.tsx index 6a20dad6f9..0c980ac7ff 100644 --- a/pioneer/packages/joy-election/src/Reveals.tsx +++ b/pioneer/packages/joy-election/src/Reveals.tsx @@ -1,23 +1,23 @@ import React from 'react'; -import { AppProps, I18nProps } from '@polkadot/react-components/types'; +import { I18nProps } from '@polkadot/react-components/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { withCalls, withMulti } from '@polkadot/react-api/with'; +import { withCalls, withMulti } from '@polkadot/react-api/hoc'; import { AccountId } from '@polkadot/types/interfaces'; import { Input, Labelled, InputAddress } from '@polkadot/react-components/index'; import translate from './translate'; -import { nonEmptyStr, queryToProp, getUrlParam } from '@polkadot/joy-utils/index'; +import { nonEmptyStr, queryToProp, getUrlParam } from '@polkadot/joy-utils/functions/misc'; import { accountIdsToOptions, hashVote } from './utils'; -import TxButton from '@polkadot/joy-utils/TxButton'; +import TxButton from '@polkadot/joy-utils/react/components/TxButton'; import { findVoteByHash } from './myVotesStore'; -import { withOnlyMembers } from '@polkadot/joy-utils/MyAccount'; +import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards'; +import { RouteProps } from 'react-router-dom'; // AppsProps is needed to get a location from the route. -type Props = AppProps & ApiProps & I18nProps & { +type Props = RouteProps & ApiProps & I18nProps & { applicantId?: string | null; applicants?: AccountId[]; - location: any; }; type State = { @@ -30,8 +30,9 @@ class RevealVoteForm extends React.PureComponent { constructor (props: Props) { super(props); let { applicantId, location } = this.props; - applicantId = applicantId || getUrlParam(location, 'applicantId'); - const hashedVote = getUrlParam(location, 'hashedVote'); + + applicantId = applicantId || (location && getUrlParam(location, 'applicantId')); + const hashedVote = location && getUrlParam(location, 'hashedVote'); this.state = { applicantId, @@ -45,6 +46,7 @@ class RevealVoteForm extends React.PureComponent { const applicantOpts = accountIdsToOptions(this.props.applicants || []); const myVote = hashedVote ? findVoteByHash(hashedVote) : undefined; + if (myVote) { // Try to substitue applicantId and salt from local sotre: if (!applicantId) applicantId = myVote.applicantId; @@ -81,15 +83,17 @@ class RevealVoteForm extends React.PureComponent { onChange={this.onChangeSalt} /> } - - - +
+ + + +
); } diff --git a/pioneer/packages/joy-election/src/SealedVote.tsx b/pioneer/packages/joy-election/src/SealedVote.tsx index 54650328c1..769a884e4a 100644 --- a/pioneer/packages/joy-election/src/SealedVote.tsx +++ b/pioneer/packages/joy-election/src/SealedVote.tsx @@ -1,38 +1,47 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import { Table } from 'semantic-ui-react'; +import { Table, Message } from 'semantic-ui-react'; import { I18nProps } from '@polkadot/react-components/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { withCalls } from '@polkadot/react-api/with'; +import { withCalls } from '@polkadot/react-api/hoc'; import { Hash } from '@polkadot/types/interfaces'; import { formatBalance } from '@polkadot/util'; import translate from './translate'; -import { calcTotalStake } from '@polkadot/joy-utils/index'; +import { calcTotalStake } from '@polkadot/joy-utils/functions/misc'; import { SealedVote } from '@joystream/types/council'; -import AddressMini from '@polkadot/react-components/AddressMiniJoy'; +import AddressMini from '@polkadot/react-components/AddressMini'; import CandidatePreview from './CandidatePreview'; import { findVoteByHash } from './myVotesStore'; type Props = ApiProps & I18nProps & { hash: Hash; sealedVote?: SealedVote; + isStageRevealing: boolean; + isMyVote: boolean; }; class Comp extends React.PureComponent { renderCandidateOrAction () { - const { hash, sealedVote } = this.props; + const { hash, sealedVote, isStageRevealing, isMyVote } = this.props; + if (!sealedVote) { return Unknown hashed vote: {hash.toHex()}; } if (sealedVote.vote.isSome) { const candidateId = sealedVote.vote.unwrap(); + return ; - } else { + } else if (isStageRevealing && isMyVote) { const revealUrl = `/council/reveals?hashedVote=${hash.toHex()}`; + return Reveal this vote; + } else if (isMyVote) { + return Wait until Revealing stage in order to reveal this vote.; + } else { + return This vote has not been revealed yet.; } } diff --git a/pioneer/packages/joy-election/src/SealedVotes.tsx b/pioneer/packages/joy-election/src/SealedVotes.tsx index 1f03b66ac0..5effd2b117 100644 --- a/pioneer/packages/joy-election/src/SealedVotes.tsx +++ b/pioneer/packages/joy-election/src/SealedVotes.tsx @@ -1,36 +1,43 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import { Button } from 'semantic-ui-react'; +import { Button, Message } from 'semantic-ui-react'; import { I18nProps } from '@polkadot/react-components/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { withCalls } from '@polkadot/react-api/with'; +import { withCalls } from '@polkadot/react-api/hoc'; import { Hash } from '@polkadot/types/interfaces'; import translate from './translate'; import SealedVote from './SealedVote'; -import { queryToProp } from '@polkadot/joy-utils/index'; -import { MyAddressProps } from '@polkadot/joy-utils/MyAccount'; +import { queryToProp } from '@polkadot/joy-utils/functions/misc'; +import { MyAddressProps } from '@polkadot/joy-utils/react/hocs/accounts'; import { SavedVote } from './myVotesStore'; -import Section from '@polkadot/joy-utils/Section'; +import Section from '@polkadot/joy-utils/react/components/Section'; type Props = ApiProps & I18nProps & MyAddressProps & { myVotes?: SavedVote[]; commitments?: Hash[]; + isStageRevealing: boolean; }; class Comp extends React.PureComponent { private filterVotes = (myVotesOnly: boolean): Hash[] => { const { myVotes = [], commitments = [] } = this.props; + const isMyVote = (hash: string): boolean => { - return myVotes.find(x => x.hash === hash) !== undefined; + return myVotes.find((x) => x.hash === hash) !== undefined; }; - return commitments.filter(x => myVotesOnly === isMyVote(x.toHex())); + + return commitments.filter((x) => myVotesOnly === isMyVote(x.toHex())); } - private renderVotes = (votes: Hash[]) => { + private renderVotes = (votes: Hash[], areVotesMine: boolean) => { return votes.map((hash, index) => - + ); } @@ -39,17 +46,19 @@ class Comp extends React.PureComponent { const otherVotes = this.filterVotes(false); return <> -
{ - !myVotes.length - ? No votes by the current account found on the current browser. - : this.renderVotes(myVotes) - }
+
+ { + !myVotes.length + ? No votes by the current account found on the current browser. + : this.renderVotes(myVotes, true) + } + { this.props.isStageRevealing && } +
- { !otherVotes.length ? No votes submitted by other accounts yet. - : this.renderVotes(otherVotes) + : this.renderVotes(otherVotes, false) }
; diff --git a/pioneer/packages/joy-election/src/SidebarSubtitle.tsx b/pioneer/packages/joy-election/src/SidebarSubtitle.tsx new file mode 100644 index 0000000000..82e63d39bf --- /dev/null +++ b/pioneer/packages/joy-election/src/SidebarSubtitle.tsx @@ -0,0 +1,35 @@ +/** Component providing election stage subtitle for SideBar menu **/ +import React from 'react'; +import { ElectionStage } from '@joystream/types/council'; +import { Option } from '@polkadot/types/codec'; +import { useApi, useCall } from '@polkadot/react-hooks'; +import styled from 'styled-components'; + +const colorByStage = { + Announcing: '#4caf50', + Voting: '#2196f3', + Revealing: '#ff5722' +} as const; + +type StyledSubtitleProps = { + stage?: keyof typeof colorByStage; +} +const StyledSubtitle = styled.div` + display: block; + font-size: 0.85rem; + color: ${(props: StyledSubtitleProps) => props.stage ? colorByStage[props.stage] : 'grey'}; +`; + +export default function SidebarSubtitle () { + const apiProps = useApi(); + const electionStage = useCall>(apiProps.isApiReady && apiProps.api.query.councilElection.stage, []); + + if (electionStage) { + const stageName = electionStage.unwrapOr(undefined)?.type; + const text = stageName ? `${stageName} stage` : 'No active election'; + + return {text}; + } + + return null; +} diff --git a/pioneer/packages/joy-election/src/VoteForm.tsx b/pioneer/packages/joy-election/src/VoteForm.tsx index 63fbb78b17..fdb74b95d8 100644 --- a/pioneer/packages/joy-election/src/VoteForm.tsx +++ b/pioneer/packages/joy-election/src/VoteForm.tsx @@ -4,9 +4,9 @@ import uuid from 'uuid/v4'; import React from 'react'; import { Message, Table } from 'semantic-ui-react'; -import { AppProps, I18nProps } from '@polkadot/react-components/types'; +import { I18nProps } from '@polkadot/react-components/types'; import { ApiProps } from '@polkadot/react-api/types'; -import { withCalls, withMulti } from '@polkadot/react-api/with'; +import { withCalls, withMulti } from '@polkadot/react-api/hoc'; import { AccountId, Balance } from '@polkadot/types/interfaces'; import { Button, Input, Labelled } from '@polkadot/react-components/index'; import { SubmittableResult } from '@polkadot/api'; @@ -14,14 +14,16 @@ import { formatBalance } from '@polkadot/util'; import translate from './translate'; import { hashVote } from './utils'; -import { queryToProp, ZERO, getUrlParam, nonEmptyStr } from '@polkadot/joy-utils/index'; -import TxButton from '@polkadot/joy-utils/TxButton'; -import InputStake from '@polkadot/joy-utils/InputStake'; +import { queryToProp, ZERO, getUrlParam, nonEmptyStr } from '@polkadot/joy-utils/functions/misc'; +import TxButton from '@polkadot/joy-utils/react/components/TxButton'; +import InputStake from '@polkadot/joy-utils/react/components/InputStake'; import CandidatePreview from './CandidatePreview'; -import { MyAccountProps, withOnlyMembers } from '@polkadot/joy-utils/MyAccount'; -import MembersDropdown from '@polkadot/joy-utils/MembersDropdown'; +import { MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts'; +import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards'; +import MembersDropdown from '@polkadot/joy-utils/react/components/MembersDropdown'; import { saveVote, NewVote } from './myVotesStore'; import { TxFailedCallback } from '@polkadot/react-components/Status/types'; +import { RouteProps } from 'react-router-dom'; // TODO use a crypto-prooven generator instead of UUID 4. function randomSalt () { @@ -29,11 +31,10 @@ function randomSalt () { } // AppsProps is needed to get a location from the route. -type Props = AppProps & ApiProps & I18nProps & MyAccountProps & { +type Props = RouteProps & ApiProps & I18nProps & MyAccountProps & { applicantId?: string | null; minVotingStake?: Balance; applicants?: AccountId[]; - location?: any; }; type State = { @@ -49,7 +50,8 @@ class Component extends React.PureComponent { super(props); let { applicantId, location } = this.props; - applicantId = applicantId || getUrlParam(location, 'applicantId'); + + applicantId = applicantId || (location && getUrlParam(location, 'applicantId')); this.state = { applicantId, @@ -103,14 +105,11 @@ class Component extends React.PureComponent { - - +