From 5d4a1949599cb2f04f2b8951c7aff10d0fc39a37 Mon Sep 17 00:00:00 2001 From: Leszek Wiesner Date: Fri, 14 Aug 2020 10:23:35 +0200 Subject: [PATCH 01/17] Upgrade joy-election --- package.json | 3 +- pioneer/.eslintignore | 1 - pioneer/packages/apps-routing/src/index.ts | 3 + .../packages/apps-routing/src/joy-election.ts | 17 +++ pioneer/packages/apps-routing/src/types.ts | 2 + pioneer/packages/apps/src/Content/index.tsx | 29 +++- pioneer/packages/apps/src/SideBar/Item.tsx | 7 +- pioneer/packages/joy-election/.skip-build | 0 pioneer/packages/joy-election/package.json | 6 +- .../packages/joy-election/src/Applicant.tsx | 4 +- .../packages/joy-election/src/Applicants.tsx | 29 ++-- .../packages/joy-election/src/ApplyForm.tsx | 40 +++--- .../joy-election/src/CandidatePreview.tsx | 4 +- pioneer/packages/joy-election/src/Council.tsx | 6 +- .../packages/joy-election/src/Dashboard.tsx | 128 +++++++++++------- pioneer/packages/joy-election/src/Reveals.tsx | 28 ++-- .../packages/joy-election/src/SealedVote.tsx | 20 ++- .../packages/joy-election/src/SealedVotes.tsx | 29 ++-- .../joy-election/src/SidebarSubtitle.tsx | 34 +++++ .../packages/joy-election/src/VoteForm.tsx | 54 ++++---- pioneer/packages/joy-election/src/Votes.tsx | 35 ++++- pioneer/packages/joy-election/src/index.css | 28 ---- pioneer/packages/joy-election/src/index.tsx | 22 +-- .../packages/joy-election/src/myVotesStore.ts | 2 +- pioneer/packages/joy-election/src/style.ts | 21 +++ pioneer/packages/joy-election/src/utils.tsx | 3 +- .../packages/joy-utils-old/src/InputStake.tsx | 35 ----- .../src/MemberByAccountPreview.tsx | 48 ------- .../joy-utils-old/src/transport/base.ts | 107 --------------- .../joy-utils-old/src/transport/index.ts | 34 ----- .../src/react/components/InputStake.tsx | 46 +++++++ .../components/MemberByAccountPreview.tsx | 43 ++++++ .../components}/MemberProfilePreview.tsx | 0 .../src/react/components}/MembersDropdown.tsx | 14 +- .../src/react/components/PromiseComponent.tsx | 0 .../joy-utils/src/react/context/index.tsx | 1 + .../src/react/context/transport.tsx | 0 .../joy-utils/src/react/hooks/index.ts | 2 + .../src/react/hooks/usePromise.tsx | 0 .../src/react/hooks/useTransport.tsx | 0 .../src/transport}/APIQueryCache.ts | 0 .../packages/joy-utils/src/transport/base.ts | 66 +++++++++ .../packages/joy-utils/src/transport/index.ts | 34 +++++ .../src/transport/members.ts | 16 +++ .../src/types/members.ts | 4 + .../old-apps/apps-routing/src/joy-election.ts | 19 --- .../react-components/src/styles/joystream.ts | 93 +++++-------- pioneer/tsconfig.json | 5 +- 48 files changed, 609 insertions(+), 513 deletions(-) create mode 100644 pioneer/packages/apps-routing/src/joy-election.ts delete mode 100644 pioneer/packages/joy-election/.skip-build create mode 100644 pioneer/packages/joy-election/src/SidebarSubtitle.tsx delete mode 100644 pioneer/packages/joy-election/src/index.css create mode 100644 pioneer/packages/joy-election/src/style.ts delete mode 100644 pioneer/packages/joy-utils-old/src/InputStake.tsx delete mode 100644 pioneer/packages/joy-utils-old/src/MemberByAccountPreview.tsx delete mode 100644 pioneer/packages/joy-utils-old/src/transport/base.ts delete mode 100644 pioneer/packages/joy-utils-old/src/transport/index.ts create mode 100644 pioneer/packages/joy-utils/src/react/components/InputStake.tsx create mode 100644 pioneer/packages/joy-utils/src/react/components/MemberByAccountPreview.tsx rename pioneer/packages/{joy-utils-old/src => joy-utils/src/react/components}/MemberProfilePreview.tsx (100%) rename pioneer/packages/{joy-utils-old/src => joy-utils/src/react/components}/MembersDropdown.tsx (82%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/react/components/PromiseComponent.tsx (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/react/context/transport.tsx (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/react/hooks/usePromise.tsx (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/react/hooks/useTransport.tsx (100%) rename pioneer/packages/{joy-utils-old/src => joy-utils/src/transport}/APIQueryCache.ts (100%) create mode 100644 pioneer/packages/joy-utils/src/transport/base.ts create mode 100644 pioneer/packages/joy-utils/src/transport/index.ts rename pioneer/packages/{joy-utils-old => joy-utils}/src/transport/members.ts (53%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/types/members.ts (64%) delete mode 100644 pioneer/packages/old-apps/apps-routing/src/joy-election.ts diff --git a/package.json b/package.json index 2928330f15..ad47384381 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "pioneer/packages/react*", "pioneer/packages/joy-utils", "pioneer/packages/joy-members", - "pioneer/packages/joy-pages" + "pioneer/packages/joy-pages", + "pioneer/packages/joy-election" ], "resolutions": { "@polkadot/api": "1.26.1", 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 f4ba69e507..36f9671d31 100644 --- a/pioneer/packages/apps-routing/src/index.ts +++ b/pioneer/packages/apps-routing/src/index.ts @@ -20,11 +20,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), null, transfer(t), accounts(t), @@ -32,6 +34,7 @@ export default function create (t: (key: string, text: string, opti ] : [ members(t), + election(t), null, transfer(t), accounts(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..ebf857ad81 --- /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 && ( ; + stage?: Option; }; class Applicants extends React.PureComponent { @@ -38,12 +41,21 @@ class Applicants extends React.PureComponent { ) 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 +71,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..f0f753f37c 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,8 @@ 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 +88,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..72f4ff5ea8 100644 --- a/pioneer/packages/joy-election/src/Council.tsx +++ b/pioneer/packages/joy-election/src/Council.tsx @@ -2,15 +2,15 @@ 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'; type Props = ApiProps & I18nProps & { diff --git a/pioneer/packages/joy-election/src/Dashboard.tsx b/pioneer/packages/joy-election/src/Dashboard.tsx index c7f64ef811..b962c1e5aa 100644 --- a/pioneer/packages/joy-election/src/Dashboard.tsx +++ b/pioneer/packages/joy-election/src/Dashboard.tsx @@ -3,14 +3,14 @@ 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'; @@ -45,12 +45,17 @@ class Dashboard extends React.PureComponent { const title = `Council ${activeCouncil.length > 0 ? '' : '(not elected)'}`; return
- - {activeCouncil.length} - - - {formatNumber(p.termEndsAt)} - + + + +
; } @@ -76,20 +81,28 @@ class Dashboard extends React.PureComponent { const title = <>Election ({stateText}); return
- - {formatNumber(round)} - - {isRunning && <> - - {stageName} - - - {formatNumber(leftBlocks)} - - - {formatNumber(stageEndsAt)} - - } + + + {isRunning && <> + + + + } +
; } @@ -98,33 +111,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..b54330d5f7 100644 --- a/pioneer/packages/joy-election/src/Reveals.tsx +++ b/pioneer/packages/joy-election/src/Reveals.tsx @@ -2,16 +2,16 @@ import React from 'react'; import { AppProps, 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'; // AppsProps is needed to get a location from the route. type Props = AppProps & ApiProps & I18nProps & { @@ -81,15 +81,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..b935d04e11 100644 --- a/pioneer/packages/joy-election/src/SealedVote.tsx +++ b/pioneer/packages/joy-election/src/SealedVote.tsx @@ -1,28 +1,30 @@ 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()}; } @@ -30,10 +32,16 @@ class Comp extends React.PureComponent { 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. + } } render () { diff --git a/pioneer/packages/joy-election/src/SealedVotes.tsx b/pioneer/packages/joy-election/src/SealedVotes.tsx index 1f03b66ac0..8dd5abf374 100644 --- a/pioneer/packages/joy-election/src/SealedVotes.tsx +++ b/pioneer/packages/joy-election/src/SealedVotes.tsx @@ -4,19 +4,20 @@ import { Button } 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 { @@ -28,9 +29,13 @@ class Comp extends React.PureComponent { 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 +44,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) - }
+ : 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..bbd59e6c6b --- /dev/null +++ b/pioneer/packages/joy-election/src/SidebarSubtitle.tsx @@ -0,0 +1,34 @@ +/** 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..b567669d7f 100644 --- a/pioneer/packages/joy-election/src/VoteForm.tsx +++ b/pioneer/packages/joy-election/src/VoteForm.tsx @@ -6,7 +6,7 @@ import { Message, Table } from 'semantic-ui-react'; import { AppProps, 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,12 +14,13 @@ 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'; @@ -103,14 +104,11 @@ class Component extends React.PureComponent { - - + } + { + !myVotes.length + ? No votes by the current account found on the current browser. + : this.renderVotes(myVotes, true) + } + { this.props.isStageRevealing && }
{ diff --git a/pioneer/packages/joy-election/src/SidebarSubtitle.tsx b/pioneer/packages/joy-election/src/SidebarSubtitle.tsx index bbd59e6c6b..82e63d39bf 100644 --- a/pioneer/packages/joy-election/src/SidebarSubtitle.tsx +++ b/pioneer/packages/joy-election/src/SidebarSubtitle.tsx @@ -17,7 +17,7 @@ type StyledSubtitleProps = { const StyledSubtitle = styled.div` display: block; font-size: 0.85rem; - color: ${ (props: StyledSubtitleProps) => props.stage ? colorByStage[props.stage] : 'grey' }; + color: ${(props: StyledSubtitleProps) => props.stage ? colorByStage[props.stage] : 'grey'}; `; export default function SidebarSubtitle () { @@ -27,6 +27,7 @@ export default function SidebarSubtitle () { if (electionStage) { const stageName = electionStage.unwrapOr(undefined)?.type; const text = stageName ? `${stageName} stage` : 'No active election'; + return {text}; } diff --git a/pioneer/packages/joy-election/src/VoteForm.tsx b/pioneer/packages/joy-election/src/VoteForm.tsx index b567669d7f..1a7e1232ff 100644 --- a/pioneer/packages/joy-election/src/VoteForm.tsx +++ b/pioneer/packages/joy-election/src/VoteForm.tsx @@ -19,7 +19,7 @@ import TxButton from '@polkadot/joy-utils/react/components/TxButton'; import InputStake from '@polkadot/joy-utils/react/components/InputStake'; import CandidatePreview from './CandidatePreview'; import { MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts'; -import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards' +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'; @@ -50,6 +50,7 @@ class Component extends React.PureComponent { super(props); let { applicantId, location } = this.props; + applicantId = applicantId || getUrlParam(location, 'applicantId'); this.state = { @@ -118,7 +119,7 @@ class Component extends React.PureComponent { onChange={ (event, data) => this.onChangeApplicant(data.value as string) } accounts={this.props.applicants || []} value={applicantId || ''} - placeholder="Select an applicant you support" + placeholder='Select an applicant you support' /> { private onTxSuccess = (vote: NewVote, txResult: SubmittableResult): void => { let hasVotedEvent = false; + txResult.events.forEach((event, i) => { const { section, method } = event.event; + if (section === 'councilElection' && method === 'Voted') { hasVotedEvent = true; } }); + if (hasVotedEvent) { saveVote(vote); this.setState({ isFormSubmitted: true }); @@ -203,6 +207,7 @@ class Component extends React.PureComponent { private onChangeStake = (stake?: BN) => { const isStakeValid = stake && stake.gte(this.minStake()); + this.setState({ stake, isStakeValid }); } diff --git a/pioneer/packages/joy-election/src/Votes.tsx b/pioneer/packages/joy-election/src/Votes.tsx index d238dcd6c6..1d1baadea3 100644 --- a/pioneer/packages/joy-election/src/Votes.tsx +++ b/pioneer/packages/joy-election/src/Votes.tsx @@ -26,7 +26,7 @@ class Component extends React.PureComponent { return <>
- { stage?.unwrapOr(undefined)?.isOfType('Voting') + { stage?.unwrapOr(undefined)?.isOfType('Voting') ? ( ) @@ -35,7 +35,7 @@ class Component extends React.PureComponent { Voting is only possible during Voting stage. ) - } + }
{ private buildTabs (): TabItem[] { const { t, activeCouncil = [], applicants = [], commitments = [] } = this.props; + return [ { isRoot: true, @@ -62,6 +63,7 @@ class App extends React.PureComponent { render () { const { basePath } = this.props; const tabs = this.buildTabs(); + return (
diff --git a/pioneer/packages/joy-election/src/myVotesStore.ts b/pioneer/packages/joy-election/src/myVotesStore.ts index cfc3bc3d96..62b0006e40 100644 --- a/pioneer/packages/joy-election/src/myVotesStore.ts +++ b/pioneer/packages/joy-election/src/myVotesStore.ts @@ -20,22 +20,25 @@ export type SavedVote = NewVote & { /** Get all votes that are stored in a local sotrage. */ export const getAllVotes = (): SavedVote[] => { const votes = store.get(MY_VOTES); + return nonEmptyArr(votes) ? votes as SavedVote[] : []; }; export const getVotesByVoter = (voterId: string): SavedVote[] => { - return getAllVotes().filter(v => v.voterId === voterId); + return getAllVotes().filter((v) => v.voterId === voterId); }; export const findVoteByHash = (hash: string): SavedVote | undefined => { - return getAllVotes().find(v => v.hash === hash); + return getAllVotes().find((v) => v.hash === hash); }; export const saveVote = (vote: NewVote): void => { const votes = getAllVotes(); - const similarVote = votes.find(v => v.hash === vote.hash); + const similarVote = votes.find((v) => v.hash === vote.hash); + if (similarVote) { console.log('There is a vote with the same hash in a storage:', similarVote); + return; } @@ -45,7 +48,8 @@ export const saveVote = (vote: NewVote): void => { export const revealVote = (hash: string): void => { const votes = getAllVotes(); - const savedVote = votes.find(v => v.hash === hash); + const savedVote = votes.find((v) => v.hash === hash); + if (savedVote && !savedVote.isRevealed) { savedVote.isRevealed = true; savedVote.revealedOnTime = Date.now(); diff --git a/pioneer/packages/joy-election/src/utils.tsx b/pioneer/packages/joy-election/src/utils.tsx index 5a4cafb55a..812fa652ad 100644 --- a/pioneer/packages/joy-election/src/utils.tsx +++ b/pioneer/packages/joy-election/src/utils.tsx @@ -22,16 +22,19 @@ export type HashedVote = { const createAddressOption = (address: string) => { const name = findNameByAddress(address); + return createItem(address, name); }; -export const accountIdsToOptions = (applicants: Array): any => { +export const accountIdsToOptions = (applicants: Array) => { if (applicants && applicants.length) { - return applicants.map(a => { + return applicants.map((a) => { const addr = a.toString(); + return createAddressOption(addr); }); } + return []; }; @@ -45,10 +48,12 @@ export const hashVote = (accountId?: string | null, salt?: string): string | nul const accountU8a = decodeAddress(accountId); const saltU8a = stringToU8a(salt); const voteU8a = new Uint8Array(accountU8a.length + saltU8a.length); + voteU8a.set(accountU8a); voteU8a.set(saltU8a, accountU8a.length); const hash = blake2AsHex(voteU8a, 256); + // console.log('Vote hash:', hash, 'for', { accountId, salt }); return hash; }; diff --git a/pioneer/packages/joy-utils/src/react/components/InputStake.tsx b/pioneer/packages/joy-utils/src/react/components/InputStake.tsx index 8e012fe42f..5c18e525ee 100644 --- a/pioneer/packages/joy-utils/src/react/components/InputStake.tsx +++ b/pioneer/packages/joy-utils/src/react/components/InputStake.tsx @@ -14,6 +14,7 @@ type Props = { export default class Component extends React.PureComponent { render () { const { min, label, isValid, onChange } = this.props; + return (
{ color={ isValid ? 'green' : 'red' } icon={isValid ? 'check' : 'warning sign'} label='Minimum stake' - pointing="left" + pointing='left' > Minimum stake {formatBalance(min)} diff --git a/pioneer/packages/joy-utils/src/react/components/MemberByAccountPreview.tsx b/pioneer/packages/joy-utils/src/react/components/MemberByAccountPreview.tsx index 2fc8e28d83..bcae5779f7 100644 --- a/pioneer/packages/joy-utils/src/react/components/MemberByAccountPreview.tsx +++ b/pioneer/packages/joy-utils/src/react/components/MemberByAccountPreview.tsx @@ -23,7 +23,7 @@ const MemberByAccountPreview: React.FunctionComponent = ({ accountId }) = ); return ( - + { member && ( member.profile diff --git a/pioneer/packages/joy-utils/src/react/components/MemberProfilePreview.tsx b/pioneer/packages/joy-utils/src/react/components/MemberProfilePreview.tsx index af503bc575..69ea8b7cab 100644 --- a/pioneer/packages/joy-utils/src/react/components/MemberProfilePreview.tsx +++ b/pioneer/packages/joy-utils/src/react/components/MemberProfilePreview.tsx @@ -48,9 +48,9 @@ export default function ProfilePreview ( const Preview = ( {avatar_uri.toString() ? ( - + ) : ( - + )}
{handle.toString()} @@ -77,6 +77,7 @@ export function ProfilePreviewFromStruct ( { profile, link, id, children }: React.PropsWithChildren ) { const { avatar_uri, root_account, handle } = profile; + return ( {children} diff --git a/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx b/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx index accada8040..6777c33944 100644 --- a/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx +++ b/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx @@ -14,7 +14,8 @@ const StyledMembersDropdown = styled(Dropdown)` `; function membersToOptions (members: MemberFromAccount[]) { - const validMembers = members.filter(m => m.profile !== undefined) as (MemberFromAccount & { profile: Membership })[]; + const validMembers = members.filter((m) => m.profile !== undefined) as (MemberFromAccount & { profile: Membership })[]; + return validMembers .map(({ memberId, profile, account }) => ({ key: profile.handle, @@ -37,17 +38,20 @@ const MembersDropdown: React.FunctionComponent = ({ accounts, ...passedPr // State const [loading, setLoading] = useState(true); const [membersOptions, setMembersOptions] = useState([] as DropdownItemProps[]); + // Generate members options array on load useEffect(() => { let isSubscribed = true; + Promise - .all(accounts.map(acc => transport.members.membershipFromAccount(acc))) - .then(members => { + .all(accounts.map((acc) => transport.members.membershipFromAccount(acc))) + .then((members) => { if (isSubscribed) { setMembersOptions(membersToOptions(members)); setLoading(false); } }); + return () => { isSubscribed = false; }; }, [accounts]); diff --git a/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx b/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx index 18395aa101..b91c54ef75 100644 --- a/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx +++ b/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx @@ -7,6 +7,7 @@ type ErrorProps = { export function Error ({ error }: ErrorProps) { console.error(error); + return ( @@ -34,6 +35,7 @@ type PromiseComponentProps = { error: any; message: string; } + const PromiseComponent: React.FunctionComponent = ({ loading, error, message, children }) => { if (loading && !error) { return ; diff --git a/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx b/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx index 67f4d679ff..7dcc60475c 100644 --- a/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx +++ b/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx @@ -17,18 +17,21 @@ export default function usePromise ( let isSubscribed = true; const execute = useCallback(() => { setState({ value: state.value, error: null, isPending: true }); + return promise() - .then(value => { + .then((value) => { if (isSubscribed) { setState({ value, error: null, isPending: false }); + if (onUpdate) { onUpdate(value); } } }) - .catch(error => { + .catch((error) => { if (isSubscribed) { setState({ value: defaultValue, error: error, isPending: false }); + if (onUpdate) { onUpdate(defaultValue); // This should represent an empty value in most cases } @@ -38,11 +41,13 @@ export default function usePromise ( useEffect(() => { execute(); + return () => { isSubscribed = false; }; }, dependsOn); const { value, error, isPending } = state; + return [value, error, isPending, execute]; } diff --git a/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts b/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts index 8643a7b7e1..07bd9d1fd1 100644 --- a/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts +++ b/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts @@ -40,22 +40,28 @@ export class APIQueryCache { } protected buildQuery () { - const modules = Object.keys(this.api.query).map(key => ({ name: key, storage: this.api.query[key] })); + const modules = Object.keys(this.api.query).map((key) => ({ name: key, storage: this.api.query[key] })); + modules.map((module) => { this.query[module.name] = {}; - const funcs = Object.keys(module.storage).map(key => ({ name: key, storage: module.storage[key] })); + const funcs = Object.keys(module.storage).map((key) => ({ name: key, storage: module.storage[key] })); + funcs.map((func) => { this.query[module.name][func.name] = async (...args: any): Promise => { const cacheKey = module.name + func.name + JSON.stringify(args); const cacheValue = this.cache.get(cacheKey); + if (cacheValue) { this.cacheHits++; + return cacheValue; } const toCache = await this.api.query[module.name][func.name](...args); + this.cache.set(cacheKey, toCache); + return toCache; }; }); diff --git a/pioneer/packages/joy-utils/src/transport/base.ts b/pioneer/packages/joy-utils/src/transport/base.ts index b8a1433d1b..102a9d2083 100644 --- a/pioneer/packages/joy-utils/src/transport/base.ts +++ b/pioneer/packages/joy-utils/src/transport/base.ts @@ -61,6 +61,7 @@ export default abstract class BaseTransport { protected queryMethodByName (name: string) { const [module, method] = name.split('.'); + return this.api.query[module][method]; } } diff --git a/pioneer/packages/joy-utils/src/transport/members.ts b/pioneer/packages/joy-utils/src/transport/members.ts index 5a90bf74df..059b1a2f61 100644 --- a/pioneer/packages/joy-utils/src/transport/members.ts +++ b/pioneer/packages/joy-utils/src/transport/members.ts @@ -7,6 +7,7 @@ import { MemberFromAccount } from '../types/members'; export default class MembersTransport extends BaseTransport { async membershipById (id: MemberId | number): Promise { const member = (await this.members.membershipById(id)) as Membership; + // Can't just use member.isEmpty because member.suspended is Bool (which isEmpty method always returns false) return member.handle.isEmpty ? null : member; } @@ -14,6 +15,7 @@ export default class MembersTransport extends BaseTransport { // Throws if profile not found async expectedMembership (id: MemberId | number): Promise { const member = await this.membershipById(id); + if (!member) { throw new Error(`Expected member profile not found! ID: ${id.toString()}`); } @@ -34,7 +36,7 @@ export default class MembersTransport extends BaseTransport { return { account: accountId.toString(), memberId: memberId && memberId.toNumber(), - profile, + profile }; } } From 2e655f7f8f3dca6500145641487e12c40e8a4c7b Mon Sep 17 00:00:00 2001 From: Leszek Wiesner Date: Fri, 14 Aug 2020 14:14:39 +0200 Subject: [PATCH 03/17] Linting: Manual fixes (required refactoring usePromise) --- .../packages/joy-election/src/Applicants.tsx | 3 +- pioneer/packages/joy-election/src/Council.tsx | 6 +-- .../packages/joy-election/src/Dashboard.tsx | 5 ++- pioneer/packages/joy-election/src/Reveals.tsx | 10 ++--- .../packages/joy-election/src/VoteForm.tsx | 8 ++-- pioneer/packages/joy-election/src/Votes.tsx | 5 ++- pioneer/packages/joy-election/src/index.tsx | 7 +-- .../packages/joy-election/src/myVotesStore.ts | 2 +- .../packages/joy-utils/src/functions/misc.ts | 14 ++++++ .../src/react/components/MembersDropdown.tsx | 15 ++++--- .../src/react/components/PromiseComponent.tsx | 6 +-- .../joy-utils/src/react/context/transport.tsx | 2 +- .../joy-utils/src/react/hooks/usePromise.tsx | 44 +++++++++++++------ .../joy-utils/src/transport/APIQueryCache.ts | 8 ++-- .../packages/joy-utils/src/transport/base.ts | 1 - 15 files changed, 87 insertions(+), 49 deletions(-) diff --git a/pioneer/packages/joy-election/src/Applicants.tsx b/pioneer/packages/joy-election/src/Applicants.tsx index e999dc675c..68a8c916fb 100644 --- a/pioneer/packages/joy-election/src/Applicants.tsx +++ b/pioneer/packages/joy-election/src/Applicants.tsx @@ -16,8 +16,9 @@ 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; diff --git a/pioneer/packages/joy-election/src/Council.tsx b/pioneer/packages/joy-election/src/Council.tsx index 6ad36df3f5..f4e9e9597d 100644 --- a/pioneer/packages/joy-election/src/Council.tsx +++ b/pioneer/packages/joy-election/src/Council.tsx @@ -11,13 +11,13 @@ import { calcBackersStake } from '@polkadot/joy-utils/functions/misc'; import { Seat } from '@joystream/types/council'; import translate from './translate'; 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 = {}; diff --git a/pioneer/packages/joy-election/src/Dashboard.tsx b/pioneer/packages/joy-election/src/Dashboard.tsx index f115cc1917..da6fffbd9c 100644 --- a/pioneer/packages/joy-election/src/Dashboard.tsx +++ b/pioneer/packages/joy-election/src/Dashboard.tsx @@ -13,8 +13,9 @@ 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 = {}; diff --git a/pioneer/packages/joy-election/src/Reveals.tsx b/pioneer/packages/joy-election/src/Reveals.tsx index ae556d042d..0c980ac7ff 100644 --- a/pioneer/packages/joy-election/src/Reveals.tsx +++ b/pioneer/packages/joy-election/src/Reveals.tsx @@ -1,6 +1,6 @@ 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/hoc'; import { AccountId } from '@polkadot/types/interfaces'; @@ -12,12 +12,12 @@ import { accountIdsToOptions, hashVote } from './utils'; import TxButton from '@polkadot/joy-utils/react/components/TxButton'; import { findVoteByHash } from './myVotesStore'; 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 = { @@ -31,8 +31,8 @@ class RevealVoteForm extends React.PureComponent { 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, diff --git a/pioneer/packages/joy-election/src/VoteForm.tsx b/pioneer/packages/joy-election/src/VoteForm.tsx index 1a7e1232ff..fdb74b95d8 100644 --- a/pioneer/packages/joy-election/src/VoteForm.tsx +++ b/pioneer/packages/joy-election/src/VoteForm.tsx @@ -4,7 +4,7 @@ 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/hoc'; import { AccountId, Balance } from '@polkadot/types/interfaces'; @@ -23,6 +23,7 @@ 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 () { @@ -30,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 = { @@ -51,7 +51,7 @@ class Component extends React.PureComponent { let { applicantId, location } = this.props; - applicantId = applicantId || getUrlParam(location, 'applicantId'); + applicantId = applicantId || (location && getUrlParam(location, 'applicantId')); this.state = { applicantId, diff --git a/pioneer/packages/joy-election/src/Votes.tsx b/pioneer/packages/joy-election/src/Votes.tsx index 1d1baadea3..7890d30781 100644 --- a/pioneer/packages/joy-election/src/Votes.tsx +++ b/pioneer/packages/joy-election/src/Votes.tsx @@ -1,6 +1,6 @@ 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 } from '@polkadot/react-api/hoc'; import { Message } from 'semantic-ui-react'; @@ -14,8 +14,9 @@ import { getVotesByVoter } from './myVotesStore'; import VoteForm from './VoteForm'; import { queryToProp } from '@polkadot/joy-utils/functions/misc'; import { ElectionStage } from '@joystream/types/src/council'; +import { RouteProps } from 'react-router-dom'; -type Props = AppProps & ApiProps & I18nProps & MyAccountProps & { +type Props = RouteProps & ApiProps & I18nProps & MyAccountProps & { stage?: Option; }; diff --git a/pioneer/packages/joy-election/src/index.tsx b/pioneer/packages/joy-election/src/index.tsx index 918178c188..7e6be023af 100644 --- a/pioneer/packages/joy-election/src/index.tsx +++ b/pioneer/packages/joy-election/src/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Route, Switch } from 'react-router'; import { I18nProps } from '@polkadot/react-components/types'; -import { RouteProps } from '@polkadot/apps-routing/types'; +import { RouteProps as AppMainRouteProps } from '@polkadot/apps-routing/types'; import { withCalls } from '@polkadot/react-api/hoc'; import { AccountId, Hash } from '@polkadot/types/interfaces'; import Tabs from '@polkadot/react-components/Tabs'; @@ -21,17 +21,18 @@ import Votes from './Votes'; import Reveals from './Reveals'; import { queryToProp } from '@polkadot/joy-utils/functions/misc'; import { Seat } from '@joystream/types/council'; +import { ApiProps } from '@polkadot/react-api/types'; const ElectionMain = styled.main`${style}`; // define out internal types -type Props = RouteProps & I18nProps & { +type Props = AppMainRouteProps & ApiProps & I18nProps & { activeCouncil?: Seat[]; applicants?: AccountId[]; commitments?: Hash[]; }; -type State = {}; +type State = Record; class App extends React.PureComponent { state: State = {}; diff --git a/pioneer/packages/joy-election/src/myVotesStore.ts b/pioneer/packages/joy-election/src/myVotesStore.ts index 62b0006e40..16921a8b72 100644 --- a/pioneer/packages/joy-election/src/myVotesStore.ts +++ b/pioneer/packages/joy-election/src/myVotesStore.ts @@ -19,7 +19,7 @@ export type SavedVote = NewVote & { /** Get all votes that are stored in a local sotrage. */ export const getAllVotes = (): SavedVote[] => { - const votes = store.get(MY_VOTES); + const votes = store.get(MY_VOTES) as unknown; return nonEmptyArr(votes) ? votes as SavedVote[] : []; }; diff --git a/pioneer/packages/joy-utils/src/functions/misc.ts b/pioneer/packages/joy-utils/src/functions/misc.ts index 5f9deaf90e..5f96730c33 100644 --- a/pioneer/packages/joy-utils/src/functions/misc.ts +++ b/pioneer/packages/joy-utils/src/functions/misc.ts @@ -6,6 +6,7 @@ import { Options as QueryOptions } from '@polkadot/react-api/hoc/types'; import queryString from 'query-string'; import { SubmittableResult } from '@polkadot/api'; import { Codec } from '@polkadot/types/types'; +import { Location } from 'history'; export const ZERO = new BN(0); @@ -165,3 +166,16 @@ export function includeKeys (obj: T, ...allowedK export function bytesToString (bytes: Bytes) { return Buffer.from(bytes.toString().substr(2), 'hex').toString(); } + +export function normalizeError (e: any): string { + let message: string; + + if (e instanceof Error) { + message = e.message; + } else { + message = JSON.stringify(e); + } + + // Prevent returning falsely value + return message || 'Unexpected error'; +} diff --git a/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx b/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx index 6777c33944..d53a4ba490 100644 --- a/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx +++ b/pioneer/packages/joy-utils/src/react/components/MembersDropdown.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { Dropdown, DropdownItemProps, DropdownProps } from 'semantic-ui-react'; -import { Membership } from '@joystream/types/members'; import { MemberFromAccount } from '../../types/members'; import { useTransport } from '../hooks'; import { AccountId } from '@polkadot/types/interfaces'; @@ -14,14 +13,15 @@ const StyledMembersDropdown = styled(Dropdown)` `; function membersToOptions (members: MemberFromAccount[]) { - const validMembers = members.filter((m) => m.profile !== undefined) as (MemberFromAccount & { profile: Membership })[]; + const validMembers = members.filter((m) => m.profile !== undefined); + // Here we can assert "profile!" and "memberId!", because we filtered out those that don't have it. return validMembers .map(({ memberId, profile, account }) => ({ - key: profile.handle, - text: `${profile.handle} (id:${memberId})`, + key: profile!.handle.toString(), + text: `${profile!.handle.toString()} (id:${memberId!})`, value: account, - image: profile.avatar_uri.toString() ? { avatar: true, src: profile.avatar_uri } : null + image: profile!.avatar_uri.toString() ? { avatar: true, src: profile!.avatar_uri } : null })); } @@ -50,9 +50,12 @@ const MembersDropdown: React.FunctionComponent = ({ accounts, ...passedPr setMembersOptions(membersToOptions(members)); setLoading(false); } - }); + }) + .catch((e) => { throw e; }); return () => { isSubscribed = false; }; + // We don't need transport.members as dependency here, because we assume it's always the same, so: + // eslint-disable-next-line react-hooks/exhaustive-deps }, [accounts]); return ( diff --git a/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx b/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx index b91c54ef75..bc46d329e8 100644 --- a/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx +++ b/pioneer/packages/joy-utils/src/react/components/PromiseComponent.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Container, Message, Loader } from 'semantic-ui-react'; type ErrorProps = { - error: any; + error: string | null; }; export function Error ({ error }: ErrorProps) { @@ -12,7 +12,7 @@ export function Error ({ error }: ErrorProps) { Oops! We got an error! -

{error.message}

+

{error}

); @@ -32,7 +32,7 @@ export function Loading ({ text }: LoadingProps) { type PromiseComponentProps = { loading: boolean; - error: any; + error: string | null; message: string; } diff --git a/pioneer/packages/joy-utils/src/react/context/transport.tsx b/pioneer/packages/joy-utils/src/react/context/transport.tsx index 29acbdae87..91a599572a 100644 --- a/pioneer/packages/joy-utils/src/react/context/transport.tsx +++ b/pioneer/packages/joy-utils/src/react/context/transport.tsx @@ -6,7 +6,7 @@ import Transport from '../../transport/index'; export const TransportContext = createContext((null as unknown) as Transport); -export function TransportProvider ({ children }: { children: React.PropsWithChildren<{}> }) { +export function TransportProvider ({ children }: { children: React.PropsWithChildren }) { const api: ApiProps = useContext(ApiContext); if (!api) { diff --git a/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx b/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx index 7dcc60475c..cb4f8f8190 100644 --- a/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx +++ b/pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx @@ -1,6 +1,8 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useRef } from 'react'; +import { normalizeError } from '../../functions/misc'; +import { randomBytes } from 'crypto'; -export type UsePromiseReturnValues = [T, any, boolean, () => Promise]; +export type UsePromiseReturnValues = [T, string | null, boolean]; export default function usePromise ( promise: () => Promise, @@ -10,44 +12,58 @@ export default function usePromise ( ): UsePromiseReturnValues { const [state, setState] = useState<{ value: T; - error: any; + error: string | null; isPending: boolean; }>({ value: defaultValue, error: null, isPending: true }); - let isSubscribed = true; - const execute = useCallback(() => { - setState({ value: state.value, error: null, isPending: true }); + const subscribedPromiseToken = useRef(null); - return promise() + const executeAndSubscribePromise = () => { + setState((state) => ({ ...state, error: null, isPending: true })); + + const thisPromiseToken = randomBytes(32).toString('hex'); + + subscribedPromiseToken.current = thisPromiseToken; + + promise() .then((value) => { - if (isSubscribed) { + if (subscribedPromiseToken.current === thisPromiseToken) { setState({ value, error: null, isPending: false }); if (onUpdate) { onUpdate(value); } + } else { + console.warn('usePromise: Token didn\'t match on .then()'); + console.warn(`Subscribed promise: ${subscribedPromiseToken.current || 'NONE'}. Resolved promise: ${thisPromiseToken}.`); } }) .catch((error) => { - if (isSubscribed) { - setState({ value: defaultValue, error: error, isPending: false }); + if (subscribedPromiseToken.current === thisPromiseToken) { + setState({ value: defaultValue, error: normalizeError(error), isPending: false }); if (onUpdate) { onUpdate(defaultValue); // This should represent an empty value in most cases } + } else { + console.warn('usePromise: Token didn\'t match on .catch()'); + console.warn(`Subscribed promise: ${subscribedPromiseToken.current || 'NONE'}. Rejected promise: ${thisPromiseToken}.`); } }); - }, [promise]); + }; useEffect(() => { - execute(); + executeAndSubscribePromise(); return () => { - isSubscribed = false; + subscribedPromiseToken.current = null; }; + // Silence "React Hook useEffect was passed a dependency list that is not an array literal", + // since we want to preserve the ability to pass custom "depnendencies". + // eslint-disable-next-line react-hooks/exhaustive-deps }, dependsOn); const { value, error, isPending } = state; - return [value, error, isPending, execute]; + return [value, error, isPending]; } diff --git a/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts b/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts index 07bd9d1fd1..d04b9579f2 100644 --- a/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts +++ b/pioneer/packages/joy-utils/src/transport/APIQueryCache.ts @@ -24,15 +24,17 @@ export class APIQueryCache { this.api = api; this.buildQuery(); this.cache = new Map(); - this.breakCacheOnNewBlocks(); + this.breakCacheOnNewBlocks() + .then((unsub) => { this.unsubscribeFn = unsub; }) + .catch((e) => { throw e; }); } unsubscribe () { this.unsubscribeFn(); } - protected async breakCacheOnNewBlocks () { - this.unsubscribeFn = await this.api.rpc.chain.subscribeNewHeads((header) => { + protected breakCacheOnNewBlocks () { + return this.api.rpc.chain.subscribeNewHeads((header) => { this.cache = new Map(); // console.log("cache hits in this block", this.cacheHits) this.cacheHits = 0; diff --git a/pioneer/packages/joy-utils/src/transport/base.ts b/pioneer/packages/joy-utils/src/transport/base.ts index 102a9d2083..0e37496ee2 100644 --- a/pioneer/packages/joy-utils/src/transport/base.ts +++ b/pioneer/packages/joy-utils/src/transport/base.ts @@ -1,5 +1,4 @@ import { ApiPromise } from '@polkadot/api'; -import { Codec } from '@polkadot/types/types'; import { APIQueryCache } from './APIQueryCache'; export default abstract class BaseTransport { From 9fadcaf0be61f876522d2fb244635370f3f16caf Mon Sep 17 00:00:00 2001 From: Leszek Wiesner Date: Fri, 14 Aug 2020 16:17:12 +0200 Subject: [PATCH 04/17] Minor UI improvements --- .../packages/joy-election/src/Applicant.tsx | 11 +++--- .../packages/joy-election/src/Applicants.tsx | 36 +++++++++++-------- .../packages/joy-election/src/SealedVotes.tsx | 4 +-- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/pioneer/packages/joy-election/src/Applicant.tsx b/pioneer/packages/joy-election/src/Applicant.tsx index c8435a3f7f..0626c53e1e 100644 --- a/pioneer/packages/joy-election/src/Applicant.tsx +++ b/pioneer/packages/joy-election/src/Applicant.tsx @@ -17,11 +17,12 @@ type Props = ApiProps & I18nProps & { index: number; accountId: AccountId; stake?: ElectionStake; + isVotingStage: boolean; }; class Applicant extends React.PureComponent { 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 68a8c916fb..3ee84db4d7 100644 --- a/pioneer/packages/joy-election/src/Applicants.tsx +++ b/pioneer/packages/joy-election/src/Applicants.tsx @@ -25,21 +25,27 @@ type Props = RouteProps & ApiProps & I18nProps & MyAccountProps & { }; 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), stage } = this.props; diff --git a/pioneer/packages/joy-election/src/SealedVotes.tsx b/pioneer/packages/joy-election/src/SealedVotes.tsx index f7908e4c48..5effd2b117 100644 --- a/pioneer/packages/joy-election/src/SealedVotes.tsx +++ b/pioneer/packages/joy-election/src/SealedVotes.tsx @@ -1,6 +1,6 @@ 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'; @@ -49,7 +49,7 @@ class Comp extends React.PureComponent {
{ !myVotes.length - ? No votes by the current account found on the current browser. + ? No votes by the current account found on the current browser. : this.renderVotes(myVotes, true) } { this.props.isStageRevealing && } From f7d87da7fa803169254d005616c947cf0a4cc228 Mon Sep 17 00:00:00 2001 From: Leszek Wiesner Date: Tue, 18 Aug 2020 13:14:19 +0200 Subject: [PATCH 05/17] joy-proposals upgrade --- pioneer/.eslintignore | 1 - pioneer/packages/apps-routing/src/index.ts | 3 + .../apps-routing/src/joy-proposals.ts | 16 ++ .../packages/joy-election/src/ApplyForm.tsx | 1 - pioneer/packages/joy-election/src/Reveals.tsx | 1 - .../packages/joy-election/src/VoteForm.tsx | 1 - pioneer/packages/joy-members/src/EditForm.tsx | 1 - pioneer/packages/joy-proposals/.skip-build | 0 .../joy-proposals/src/Proposal/Body.tsx | 253 ++++++++++-------- .../src/Proposal/ChooseProposalType.tsx | 2 +- .../joy-proposals/src/Proposal/Details.tsx | 2 +- .../src/Proposal/ProposalDetails.tsx | 8 +- .../src/Proposal/ProposalFromId.tsx | 7 +- .../src/Proposal/ProposalPreview.tsx | 3 - .../src/Proposal/ProposalPreviewList.tsx | 2 +- .../joy-proposals/src/Proposal/Votes.tsx | 2 +- .../src/Proposal/VotingSection.tsx | 39 ++- .../Proposal/discussion/DiscussionPost.tsx | 2 +- .../discussion/DiscussionPostForm.tsx | 5 +- .../discussion/ProposalDiscussion.tsx | 2 +- .../src/forms/AddWorkingGroupOpeningForm.tsx | 3 +- .../BeginReviewLeaderApplicationsForm.tsx | 3 +- .../DecreaseWorkingGroupLeadStakeForm.tsx | 1 - .../FillWorkingGroupLeaderOpeningForm.tsx | 40 ++- .../src/forms/GenericProposalForm.tsx | 41 ++- .../forms/GenericWorkingGroupProposalForm.tsx | 5 +- .../src/forms/MintCapacityForm.tsx | 1 - .../src/forms/RuntimeUpgradeForm.tsx | 1 - .../forms/SetContentWorkingGroupLeadForm.tsx | 3 +- .../SetContentWorkingGroupMintCapForm.tsx | 2 +- .../src/forms/SetCouncilParamsForm.tsx | 31 ++- .../src/forms/SetMaxValidatorCountForm.tsx | 1 - .../forms/SetWorkingGroupLeadRewardForm.tsx | 1 - .../forms/SetWorkingGroupMintCapacityForm.tsx | 1 - .../joy-proposals/src/forms/SignalForm.tsx | 1 - .../forms/SlashWorkingGroupLeadStakeForm.tsx | 1 - .../src/forms/SpendingProposalForm.tsx | 1 - .../forms/TerminateWorkingGroupLeaderForm.tsx | 25 +- .../joy-proposals/src/forms/forms.css | 23 -- pioneer/packages/joy-proposals/src/index.css | 0 pioneer/packages/joy-proposals/src/index.tsx | 111 ++++---- .../src/stories/data/ProposalDetails.mock.ts | 6 +- .../src/{Proposal/Proposal.css => style.ts} | 8 +- .../joy-proposals/src/validationSchema.ts | 35 ++- .../src/consts/members.ts | 0 .../src/consts/proposals.ts | 0 .../src/consts/workingGroups.ts | 0 .../src/react/components/TxButton.tsx | 141 +++++----- .../working-groups/ApplicationDetails.tsx | 2 +- .../components/working-groups/LeadInfo.tsx | 2 +- .../joy-utils/src/react/hooks/index.ts | 1 + .../proposals/useProposalSubscription.tsx | 0 .../joy-utils/src/react/hooks/usePromise.tsx | 4 +- .../packages/joy-utils/src/transport/base.ts | 17 ++ .../src/transport/chain.ts | 0 .../src/transport/contentWorkingGroup.ts | 13 +- .../src/transport/council.ts | 38 ++- .../packages/joy-utils/src/transport/index.ts | 36 +-- .../joy-utils/src/transport/members.ts | 7 +- .../src/transport/proposals.ts | 69 ++--- .../src/transport/validators.ts | 0 .../src/transport/workingGroups.ts | 104 +++---- .../src/types/common.ts | 0 .../src/types/proposals.ts | 15 +- .../src/types/storageProviders.ts | 0 .../src/types/workingGroups.ts | 0 .../apps-routing/src/joy-proposals.ts | 17 -- pioneer/tsconfig.json | 5 +- types/src/index.ts | 10 + 69 files changed, 598 insertions(+), 579 deletions(-) create mode 100644 pioneer/packages/apps-routing/src/joy-proposals.ts delete mode 100644 pioneer/packages/joy-proposals/.skip-build delete mode 100644 pioneer/packages/joy-proposals/src/forms/forms.css delete mode 100644 pioneer/packages/joy-proposals/src/index.css rename pioneer/packages/joy-proposals/src/{Proposal/Proposal.css => style.ts} (80%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/consts/members.ts (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/consts/proposals.ts (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/consts/workingGroups.ts (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/react/components/working-groups/ApplicationDetails.tsx (97%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/react/components/working-groups/LeadInfo.tsx (95%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/react/hooks/proposals/useProposalSubscription.tsx (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/transport/chain.ts (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/transport/contentWorkingGroup.ts (72%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/transport/council.ts (75%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/transport/proposals.ts (77%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/transport/validators.ts (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/transport/workingGroups.ts (61%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/types/common.ts (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/types/proposals.ts (83%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/types/storageProviders.ts (100%) rename pioneer/packages/{joy-utils-old => joy-utils}/src/types/workingGroups.ts (100%) delete mode 100644 pioneer/packages/old-apps/apps-routing/src/joy-proposals.ts diff --git a/pioneer/.eslintignore b/pioneer/.eslintignore index 57bb4f9e3e..6abdb1d141 100644 --- a/pioneer/.eslintignore +++ b/pioneer/.eslintignore @@ -5,7 +5,6 @@ packages/old-apps/* packages/joy-forum/* packages/joy-help/* packages/joy-media/* -packages/joy-proposals/* packages/joy-roles/* packages/joy-settings/* packages/joy-utils-old/* diff --git a/pioneer/packages/apps-routing/src/index.ts b/pioneer/packages/apps-routing/src/index.ts index 36f9671d31..597d45b1a3 100644 --- a/pioneer/packages/apps-routing/src/index.ts +++ b/pioneer/packages/apps-routing/src/index.ts @@ -21,12 +21,14 @@ import transfer from './transfer'; import members from './joy-members'; import { terms, privacyPolicy } from './joy-pages'; import election from './joy-election'; +import proposals from './joy-proposals'; export default function create (t: (key: string, text: string, options: { ns: string }) => T): Routes { return appSettings.uiMode === 'light' ? [ members(t), election(t), + proposals(t), null, transfer(t), accounts(t), @@ -35,6 +37,7 @@ export default function create (t: (key: string, text: string, opti : [ members(t), election(t), + proposals(t), null, transfer(t), accounts(t), diff --git a/pioneer/packages/apps-routing/src/joy-proposals.ts b/pioneer/packages/apps-routing/src/joy-proposals.ts new file mode 100644 index 0000000000..0399968997 --- /dev/null +++ b/pioneer/packages/apps-routing/src/joy-proposals.ts @@ -0,0 +1,16 @@ +import { Route } from './types'; + +import Proposals from '@polkadot/joy-proposals/index'; + +export default function create (t: (key: string, text: string, options: { ns: string }) => T): Route { + return { + Component: Proposals, + display: { + needsApi: ['query.proposalsEngine.proposalCount'] + }, + text: t('nav.proposals', 'Proposals', { ns: 'apps-routing' }), + icon: 'tasks', + name: 'proposals', + // TODO: useCounter with active proposals count? (could be a nice addition) + }; +} diff --git a/pioneer/packages/joy-election/src/ApplyForm.tsx b/pioneer/packages/joy-election/src/ApplyForm.tsx index 5685d3371b..518d95ce51 100644 --- a/pioneer/packages/joy-election/src/ApplyForm.tsx +++ b/pioneer/packages/joy-election/src/ApplyForm.tsx @@ -51,7 +51,6 @@ class ApplyForm extends React.PureComponent {
{
{
{
NONE; } return ( - + ); } @@ -62,7 +64,7 @@ function ProposedMember (props: { memberId?: MemberId | number | null }) { return ( - { (member && !member.handle.isEmpty) ? ( + { (member && !member.isEmpty) ? ( ParsedParam[]} = { - Text: ([content]) => [ +const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails) => ParsedParam[]} = { + Text: content => [ new ParsedParam( 'Content', - , + , true ) ], @@ -111,63 +113,65 @@ const paramParsers: { [x in ProposalType]: (params: any[]) => ParsedParam[]} = { new ParsedParam('Blake2b256 hash of WASM code', hash, true), new ParsedParam('File size', filesize + ' bytes') ], - SetElectionParameters: ([params]) => [ - new ParsedParam('Announcing period', params.announcing_period + ' blocks'), - new ParsedParam('Voting period', params.voting_period + ' blocks'), - new ParsedParam('Revealing period', params.revealing_period + ' blocks'), - new ParsedParam('Council size', params.council_size + ' members'), - new ParsedParam('Candidacy limit', params.candidacy_limit + ' members'), - new ParsedParam('New term duration', params.new_term_duration + ' blocks'), + SetElectionParameters: params => [ + new ParsedParam('Announcing period', params.announcing_period.toString() + ' blocks'), + new ParsedParam('Voting period', params.voting_period.toString() + ' blocks'), + new ParsedParam('Revealing period', params.revealing_period.toString() + ' blocks'), + new ParsedParam('Council size', params.council_size.toString() + ' members'), + new ParsedParam('Candidacy limit', params.candidacy_limit.toString() + ' members'), + new ParsedParam('New term duration', params.new_term_duration.toString() + ' blocks'), new ParsedParam('Min. council stake', formatBalance(params.min_council_stake)), new ParsedParam('Min. voting stake', formatBalance(params.min_voting_stake)) ], Spending: ([amount, account]) => [ - new ParsedParam('Amount', formatBalance(amount)), - new ParsedParam('Account', ) + new ParsedParam('Amount', formatBalance(amount as Balance)), + new ParsedParam('Account', ) ], - SetLead: ([memberId, accountId]) => [ - new ParsedParam('Member', ), - new ParsedParam('Account id', ) + SetLead: params => [ + new ParsedParam('Member', ), + new ParsedParam('Account id', ) ], - SetContentWorkingGroupMintCapacity: ([capacity]) => [ + SetContentWorkingGroupMintCapacity: capacity => [ new ParsedParam('Mint capacity', formatBalance(capacity)) ], - EvictStorageProvider: ([accountId]) => [ - new ParsedParam('Storage provider account', ) + EvictStorageProvider: accountId => [ + new ParsedParam('Storage provider account', ) ], - SetValidatorCount: ([count]) => [ - new ParsedParam('Validator count', count) + SetValidatorCount: count => [ + new ParsedParam('Validator count', count.toString()) ], - SetStorageRoleParameters: ([params]) => [ + SetStorageRoleParameters: params => [ new ParsedParam('Min. stake', formatBalance(params.min_stake)), // "Min. actors": params.min_actors, - new ParsedParam('Max. actors', params.max_actors), + new ParsedParam('Max. actors', params.max_actors.toString()), new ParsedParam('Reward', formatBalance(params.reward)), - new ParsedParam('Reward period', params.reward_period + ' blocks'), + new ParsedParam('Reward period', `${params.reward_period.toString()} blocks`), // "Bonding period": params.bonding_period + " blocks", - new ParsedParam('Unbonding period', params.unbonding_period + ' blocks'), + new ParsedParam('Unbonding period', `${params.unbonding_period.toString()} blocks`), // "Min. service period": params.min_service_period + " blocks", // "Startup grace period": params.startup_grace_period + " blocks", new ParsedParam('Entry request fee', formatBalance(params.entry_request_fee)) ], - AddWorkingGroupLeaderOpening: ([{ activate_at, commitment, human_readable_text, working_group }]) => { - const workingGroup = new WorkingGroup(working_group); - const activateAt = new ActivateOpeningAt(activate_at); - const activateAtBlock = activateAt.type === ActivateOpeningAtKeys.ExactBlock ? activateAt.value : null; - const OPCommitment = new WorkingGroupOpeningPolicyCommitment(commitment); + AddWorkingGroupLeaderOpening: ({ + activate_at: activateAt, + commitment, + human_readable_text: humanReadableText, + working_group: workingGroup + }) => { + const activateAtBlock = activateAt.isOfType('ExactBlock') ? activateAt.asType('ExactBlock') : null; const { application_staking_policy: applicationSP, role_staking_policy: roleSP, application_rationing_policy: rationingPolicy - } = OPCommitment; - let HRT = bytesToString(new Bytes(human_readable_text)); + } = commitment; + let HRT = bytesToString(humanReadableText); try { HRT = JSON.stringify(JSON.parse(HRT), undefined, 4); } catch (e) { /* Do nothing */ } const formatStake = (stake: Option) => ( - stake.isSome ? stake.unwrap().amount_mode.type + `(${stake.unwrap().amount})` : 'NONE' + stake.isSome ? stake.unwrap().amount_mode.type + `(${stake.unwrap().amount.toString()})` : 'NONE' ); const formatPeriod = (unstakingPeriod: Option) => ( - unstakingPeriod.unwrapOr(0) + ' blocks' - ); + `${unstakingPeriod.unwrapOr(new BN(0)).toString()} blocks` + ) return [ new ParsedParam('Working group', workingGroup.type), new ParsedParam('Activate at', `${activateAt.type}${activateAtBlock ? `(${activateAtBlock.toString()})` : ''}`), @@ -177,34 +181,35 @@ const paramParsers: { [x in ProposalType]: (params: any[]) => ParsedParam[]} = { 'Max. applications', rationingPolicy.isSome ? rationingPolicy.unwrap().max_active_applicants.toNumber() : 'UNLIMITED' ), + new ParsedParam('Max. review period length', `${commitment.max_review_period_length.toString()} blocks`), new ParsedParam( 'Terminate unstaking period (role stake)', - formatPeriod(OPCommitment.terminate_role_stake_unstaking_period) + formatPeriod(commitment.terminate_role_stake_unstaking_period) ), new ParsedParam( 'Exit unstaking period (role stake)', - formatPeriod(OPCommitment.exit_role_stake_unstaking_period) + formatPeriod(commitment.exit_role_stake_unstaking_period) ), // new ParsedParam( 'Terminate unstaking period (appl. stake)', - formatPeriod(OPCommitment.terminate_application_stake_unstaking_period) + formatPeriod(commitment.terminate_application_stake_unstaking_period) ), new ParsedParam( 'Exit unstaking period (appl. stake)', - formatPeriod(OPCommitment.exit_role_application_stake_unstaking_period) + formatPeriod(commitment.exit_role_application_stake_unstaking_period) ), new ParsedParam( 'Appl. accepted unstaking period (appl. stake)', - formatPeriod(OPCommitment.fill_opening_successful_applicant_application_stake_unstaking_period) + formatPeriod(commitment.fill_opening_successful_applicant_application_stake_unstaking_period) ), new ParsedParam( 'Appl. failed unstaking period (role stake)', - formatPeriod(OPCommitment.fill_opening_failed_applicant_role_stake_unstaking_period) + formatPeriod(commitment.fill_opening_failed_applicant_role_stake_unstaking_period) ), new ParsedParam( 'Appl. failed unstaking period (appl. stake)', - formatPeriod(OPCommitment.fill_opening_failed_applicant_application_stake_unstaking_period) + formatPeriod(commitment.fill_opening_failed_applicant_application_stake_unstaking_period) ), new ParsedParam( 'Crowded out unstaking period (role stake)', @@ -227,55 +232,76 @@ const paramParsers: { [x in ProposalType]: (params: any[]) => ParsedParam[]} = { ]; }, SetWorkingGroupMintCapacity: ([capacity, group]) => [ - new ParsedParam('Working group', (new WorkingGroup(group)).type), - new ParsedParam('Mint capacity', formatBalance(capacity)) + new ParsedParam('Working group', (group as WorkingGroup).type), + new ParsedParam('Mint capacity', formatBalance((capacity as Balance))) ], BeginReviewWorkingGroupLeaderApplication: ([id, group]) => [ - new ParsedParam('Working group', (new WorkingGroup(group)).type), + new ParsedParam('Working group', (group as WorkingGroup).type), // TODO: Adjust the link to work with multiple groups after working-groups are normalized! - new ParsedParam('Opening id', #{id}) + new ParsedParam( + 'Opening id', + #{id.toString()} + ) + ], + FillWorkingGroupLeaderOpening: ({ + opening_id: openingId, + successful_application_id: succesfulApplicationId, + reward_policy: rewardPolicy, + working_group: workingGroup + }) => [ + new ParsedParam('Working group', workingGroup.type), + // TODO: Adjust the link to work with multiple groups after working-groups are normalized! + new ParsedParam( + 'Opening id', + #{openingId.toString()}), + new ParsedParam('Reward policy', rewardPolicy.isSome ? formatReward(rewardPolicy.unwrap(), true) : 'NONE'), + new ParsedParam( + 'Result', + , + true + ) ], - FillWorkingGroupLeaderOpening: ([params]) => { - const { opening_id, successful_application_id, reward_policy, working_group } = params; - const rewardPolicy = reward_policy && new RewardPolicy(reward_policy); - return [ - new ParsedParam('Working group', (new WorkingGroup(working_group)).type), - // TODO: Adjust the link to work with multiple groups after working-groups are normalized! - new ParsedParam('Opening id', #{opening_id}), - new ParsedParam('Reward policy', rewardPolicy ? formatReward(rewardPolicy, true) : 'NONE'), - new ParsedParam( - 'Result', - , - true - ) - ]; - }, SlashWorkingGroupLeaderStake: ([leadId, amount, group]) => [ - new ParsedParam('Working group', (new WorkingGroup(group)).type), - new ParsedParam('Slash amount', formatBalance(amount)), - new ParsedParam('Lead', , true) + new ParsedParam('Working group', (group as WorkingGroup).type), + new ParsedParam('Slash amount', formatBalance(amount as Balance)), + new ParsedParam( + 'Lead', + , + true + ) ], DecreaseWorkingGroupLeaderStake: ([leadId, amount, group]) => [ - new ParsedParam('Working group', (new WorkingGroup(group)).type), - new ParsedParam('Decrease amount', formatBalance(amount)), - new ParsedParam('Lead', , true) + new ParsedParam('Working group', (group as WorkingGroup).type), + new ParsedParam('Decrease amount', formatBalance(amount as Balance)), + new ParsedParam( + 'Lead', + , + true + ) ], SetWorkingGroupLeaderReward: ([leadId, amount, group]) => [ - new ParsedParam('Working group', (new WorkingGroup(group)).type), - new ParsedParam('New reward amount', formatBalance(amount)), - new ParsedParam('Lead', , true) + new ParsedParam('Working group', (group as WorkingGroup).type), + new ParsedParam('New reward amount', formatBalance(amount as Balance)), + new ParsedParam( + 'Lead', + , + true + ) ], - TerminateWorkingGroupLeaderRole: ([params]) => { - const paramsObj = new TerminateRoleParameters(params); - const { working_group: workingGroup, rationale, worker_id: leadId, slash } = paramsObj; + TerminateWorkingGroupLeaderRole: ({ + working_group: workingGroup, + rationale, + worker_id: leadId, + slash + }) => { return [ new ParsedParam('Working group', workingGroup.type), new ParsedParam('Rationale', bytesToString(rationale), true), new ParsedParam('Slash stake', slash.isTrue ? 'YES' : 'NO'), - new ParsedParam('Lead', , true) + new ParsedParam('Lead', , true) ]; } }; @@ -330,15 +356,20 @@ export default function Body ({ type, title, description, - params = [], + params, iAmProposer, proposalId, proposerId, isCancellable, cancellationFee }: BodyProps) { - const parseParams = paramParsers[type]; - const parsedParams = parseParams(params); + // Assert more generic type (since TypeScript cannot possibly know the value of "type" here yet) + const parseParams = paramParsers[type] as (params: SpecificProposalDetails) => ParsedParam[]; + const parsedParams = parseParams( + type === 'RuntimeUpgrade' + ? params as RuntimeUpgradeProposalDetails + : (params as ProposalDetails).asType(type) + ); return ( @@ -368,17 +399,17 @@ export default function Body ({ The cancellation fee for this type of proposal is:  { cancellationFee ? formatBalance(cancellationFee) : 'NONE' }

- - { sendTx(); } } - className={'icon left labeled'} - > - - Withdraw proposal - - + { sendTx(); } } + icon + color={ 'red' } + labelPosition={ 'left' } + > + + Withdraw proposal + ) } diff --git a/pioneer/packages/joy-proposals/src/Proposal/ChooseProposalType.tsx b/pioneer/packages/joy-proposals/src/Proposal/ChooseProposalType.tsx index fbd8194ab6..a207d37153 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/ChooseProposalType.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/ChooseProposalType.tsx @@ -4,7 +4,7 @@ import { Item, Dropdown } from 'semantic-ui-react'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; import { Categories } from '@polkadot/joy-utils/types/proposals'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; import './ChooseProposalType.css'; import { RouteComponentProps } from 'react-router-dom'; diff --git a/pioneer/packages/joy-proposals/src/Proposal/Details.tsx b/pioneer/packages/joy-proposals/src/Proposal/Details.tsx index 3289f0037b..4e0ce73193 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/Details.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/Details.tsx @@ -5,7 +5,7 @@ import { metadata as proposalConsts } from '@polkadot/joy-utils/consts/proposals import { ExtendedProposalStatus } from './ProposalDetails'; import styled from 'styled-components'; -import ProfilePreview from '@polkadot/joy-utils/MemberProfilePreview'; +import ProfilePreview from '@polkadot/joy-utils/react/components/MemberProfilePreview'; const DetailsContainer = styled(Item.Group)` display: grid; diff --git a/pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx b/pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx index f3f85b5fe7..cd15d109e7 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx @@ -4,17 +4,15 @@ import Details from './Details'; import Body from './Body'; import VotingSection from './VotingSection'; import Votes from './Votes'; -import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount'; +import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/react/hocs/accounts'; import { ParsedProposal, ProposalVotes } from '@polkadot/joy-utils/types/proposals'; import { withCalls } from '@polkadot/react-api'; -import { withMulti } from '@polkadot/react-api/with'; - -import './Proposal.css'; +import { withMulti } from '@polkadot/react-api/hoc'; import { ProposalId, ProposalDecisionStatuses, ApprovedProposalStatuses, ExecutionFailedStatus } from '@joystream/types/proposals'; import { BlockNumber } from '@polkadot/types/interfaces'; import { MemberId } from '@joystream/types/members'; import { Seat } from '@joystream/types/council'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; import ProposalDiscussion from './discussion/ProposalDiscussion'; import styled from 'styled-components'; diff --git a/pioneer/packages/joy-proposals/src/Proposal/ProposalFromId.tsx b/pioneer/packages/joy-proposals/src/Proposal/ProposalFromId.tsx index 9750685060..dfab2be622 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/ProposalFromId.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/ProposalFromId.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import ProposalDetails from './ProposalDetails'; import { useProposalSubscription } from '@polkadot/joy-utils/react/hooks'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; -import { ProposalId } from '@joystream/types/proposals'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; +import { useApi } from '@polkadot/react-hooks'; export default function ProposalFromId (props: RouteComponentProps) { const { @@ -11,8 +11,9 @@ export default function ProposalFromId (props: RouteComponentProps) { params: { id } } } = props; + const { api } = useApi(); - const { proposal: proposalState, votes: votesState } = useProposalSubscription(new ProposalId(id)); + const { proposal: proposalState, votes: votesState } = useProposalSubscription(api.createType('ProposalId', id)); return ( { `#${proposal.id.toString()}` } diff --git a/pioneer/packages/joy-proposals/src/Proposal/ProposalPreviewList.tsx b/pioneer/packages/joy-proposals/src/Proposal/ProposalPreviewList.tsx index 22df3b8803..ad031539db 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/ProposalPreviewList.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/ProposalPreviewList.tsx @@ -6,7 +6,7 @@ import { Link, useLocation } from 'react-router-dom'; import ProposalPreview from './ProposalPreview'; import { ParsedProposal, proposalStatusFilters, ProposalStatusFilter, ProposalsBatch } from '@polkadot/joy-utils/types/proposals'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; import { withCalls } from '@polkadot/react-api'; import { BlockNumber } from '@polkadot/types/interfaces'; import { Dropdown } from '@polkadot/react-components'; diff --git a/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx b/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx index 576e10e1c9..dbe2815d8c 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx @@ -4,7 +4,7 @@ import useVoteStyles from './useVoteStyles'; import { ProposalVotes } from '@polkadot/joy-utils/types/proposals'; import { VoteKind } from '@joystream/types/proposals'; import { VoteKindStr } from './VotingSection'; -import ProfilePreview from '@polkadot/joy-utils/MemberProfilePreview'; +import ProfilePreview from '@polkadot/joy-utils/react/components/MemberProfilePreview'; type VotesProps = { votes: ProposalVotes; diff --git a/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx b/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx index 7e10d4b1fb..086dcd5d53 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx @@ -1,8 +1,8 @@ import React, { useState } from 'react'; -import { Icon, Button, Message, Divider, Header } from 'semantic-ui-react'; +import { Icon, Message, Divider, Header } from 'semantic-ui-react'; import useVoteStyles from './useVoteStyles'; -import TxButton from '@polkadot/joy-utils/TxButton'; +import { SemanticTxButton } from '@polkadot/joy-utils/react/components/TxButton'; import { MemberId } from '@joystream/types/members'; import { ProposalId, VoteKind, VoteKinds } from '@joystream/types/proposals'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; @@ -35,24 +35,23 @@ type VoteButtonProps = { function VoteButton ({ voteKind, proposalId, memberId, onSuccess }: VoteButtonProps) { const { icon, color } = useVoteStyles(voteKind); return ( - // Button.Group "cheat" to force TxButton color - - sendTx() } - txFailedCb={ () => null } - txSuccessCb={ onSuccess } - className={'icon left labeled'}> - - { voteKind } - - + sendTx() } + txFailedCb={ () => null } + txSuccessCb={ onSuccess } + color={color} + style={{ marginRight: '5px' }} + icon + labelPosition={ 'left' }> + + { voteKind } + ); } diff --git a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx index 40d3125e22..4869c5dac0 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Button, Icon } from 'semantic-ui-react'; import { ParsedPost } from '@polkadot/joy-utils/types/proposals'; -import MemberProfilePreview from '@polkadot/joy-utils/MemberProfilePreview'; +import MemberProfilePreview from '@polkadot/joy-utils/react/components/MemberProfilePreview'; import DiscussionPostForm from './DiscussionPostForm'; import { MemberId } from '@joystream/types/members'; import { useTransport } from '@polkadot/joy-utils/react/hooks'; diff --git a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx index 5b7ff204fd..c19a25a4ee 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { Form, Field, withFormik, FormikProps } from 'formik'; import * as Yup from 'yup'; -import TxButton from '@polkadot/joy-utils/TxButton'; -import * as JoyForms from '@polkadot/joy-utils/forms'; +import TxButton from '@polkadot/joy-utils/react/components/TxButton'; +import * as JoyForms from '@polkadot/joy-utils/react/components/forms'; import { SubmittableResult } from '@polkadot/api'; import { Button } from 'semantic-ui-react'; import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types'; @@ -89,7 +89,6 @@ const DiscussionPostFormInner = (props: InnerProps) => { ({ ...genericFormDefaultOptions.validationSchema, ...Validation.AddWorkingGroupLeaderOpening( props.currentBlock?.toNumber() || 0, - props.HRTConstraint || InputValidationLengthConstraint.createWithMaxAllowed() + props.HRTConstraint ) }), handleSubmit: genericFormDefaultOptions.handleSubmit, diff --git a/pioneer/packages/joy-proposals/src/forms/BeginReviewLeaderApplicationsForm.tsx b/pioneer/packages/joy-proposals/src/forms/BeginReviewLeaderApplicationsForm.tsx index 22431c13a1..2e2b30b2fc 100644 --- a/pioneer/packages/joy-proposals/src/forms/BeginReviewLeaderApplicationsForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/BeginReviewLeaderApplicationsForm.tsx @@ -14,13 +14,12 @@ import { } from './GenericWorkingGroupProposalForm'; import FormField from './FormFields'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import { Dropdown, Message } from 'semantic-ui-react'; import _ from 'lodash'; import Validation from '../validationSchema'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; import { OpeningData } from '@polkadot/joy-utils/types/workingGroups'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; import { getFormErrorLabelsProps } from './errorHandling'; export type FormValues = WGFormValues & { diff --git a/pioneer/packages/joy-proposals/src/forms/DecreaseWorkingGroupLeadStakeForm.tsx b/pioneer/packages/joy-proposals/src/forms/DecreaseWorkingGroupLeadStakeForm.tsx index 735a57275f..7809b1c37b 100644 --- a/pioneer/packages/joy-proposals/src/forms/DecreaseWorkingGroupLeadStakeForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/DecreaseWorkingGroupLeadStakeForm.tsx @@ -15,7 +15,6 @@ import { } from './GenericWorkingGroupProposalForm'; import { InputFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import { Grid } from 'semantic-ui-react'; import { formatBalance } from '@polkadot/util'; import _ from 'lodash'; diff --git a/pioneer/packages/joy-proposals/src/forms/FillWorkingGroupLeaderOpeningForm.tsx b/pioneer/packages/joy-proposals/src/forms/FillWorkingGroupLeaderOpeningForm.tsx index 527ace5488..38b8bb5800 100644 --- a/pioneer/packages/joy-proposals/src/forms/FillWorkingGroupLeaderOpeningForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/FillWorkingGroupLeaderOpeningForm.tsx @@ -14,24 +14,19 @@ import { } from './GenericWorkingGroupProposalForm'; import { FormField, RewardPolicyFields } from './FormFields'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import { Dropdown, DropdownItemProps, Header, Checkbox, Message } from 'semantic-ui-react'; import _ from 'lodash'; import Validation from '../validationSchema'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; import { OpeningData, ParsedApplication } from '@polkadot/joy-utils/types/workingGroups'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; import { formatBalance } from '@polkadot/util'; import { withCalls } from '@polkadot/react-api'; -import { Option } from '@polkadot/types'; import { BlockNumber } from '@polkadot/types/interfaces'; -import { u32 as U32, u128 as U128 } from '@polkadot/types/primitive'; import { getFormErrorLabelsProps } from './errorHandling'; -import { RewardPolicy } from '@joystream/types/working-group'; -import { FillOpeningParameters } from '@joystream/types/proposals'; -import { WorkingGroup } from '@joystream/types/common'; -import { OpeningId, ApplicationId } from '@joystream/types/hiring'; import { ApplicationsDetails } from '@polkadot/joy-utils/react/components/working-groups/ApplicationDetails'; +import { SimplifiedTypeInterface } from '@polkadot/joy-utils/types/common'; +import { IFillOpeningParameters } from '@joystream/types/src/proposals'; export type FormValues = WGFormValues & { openingId: string; @@ -61,23 +56,20 @@ type FormContainerProps = ProposalFormContainerProps & { }; type FormInnerProps = ProposalFormInnerProps; -const valuesToFillOpeningParams = (values: FormValues): FillOpeningParameters => ( - new FillOpeningParameters({ - working_group: new WorkingGroup(values.workingGroup), - successful_application_id: new ApplicationId(values.successfulApplicant), - opening_id: new OpeningId(values.openingId), - reward_policy: new (Option.with(RewardPolicy))( +const valuesToFillOpeningParams = (values: FormValues): SimplifiedTypeInterface => ( + { + working_group: values.workingGroup, + successful_application_id: values.successfulApplicant, + opening_id: values.openingId, + reward_policy: values.includeReward - ? new RewardPolicy({ - amount_per_payout: new U128(values.rewardAmount), - next_payment_at_block: new U32(values.rewardNextBlock), - payout_interval: new (Option.with('BlockNumber'))( - values.rewardRecurring ? values.rewardInterval : null - ) as Option - }) - : null - ) as Option - }) + ? { + amount_per_payout: values.rewardAmount, + next_payment_at_block: values.rewardNextBlock, + payout_interval: values.rewardRecurring ? values.rewardInterval : undefined + } + : undefined + } ); const FillWorkingGroupLeaderOpeningForm: React.FunctionComponent = props => { diff --git a/pioneer/packages/joy-proposals/src/forms/GenericProposalForm.tsx b/pioneer/packages/joy-proposals/src/forms/GenericProposalForm.tsx index 3c0aca1c8a..f5f86a4557 100644 --- a/pioneer/packages/joy-proposals/src/forms/GenericProposalForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/GenericProposalForm.tsx @@ -4,11 +4,12 @@ import { Form, Icon, Button, Message } from 'semantic-ui-react'; import { getFormErrorLabelsProps } from './errorHandling'; import Validation from '../validationSchema'; import { InputFormField, TextareaFormField } from './FormFields'; -import TxButton from '@polkadot/joy-utils/TxButton'; +import TxButton from '@polkadot/joy-utils/react/components/TxButton'; import { SubmittableResult } from '@polkadot/api'; import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types'; -import { MyAccountProps, withOnlyMembers } from '@polkadot/joy-utils/MyAccount'; -import { withMulti } from '@polkadot/react-api/with'; +import { MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts'; +import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards'; +import { withMulti } from '@polkadot/react-api/hoc'; import { withCalls } from '@polkadot/react-api'; import { CallProps } from '@polkadot/react-api/types'; import { Balance, Event } from '@polkadot/types/interfaces'; @@ -16,8 +17,8 @@ import { RouteComponentProps } from 'react-router'; import { ProposalType } from '@polkadot/joy-utils/types/proposals'; import proposalsConsts from '@polkadot/joy-utils/consts/proposals'; import { formatBalance } from '@polkadot/util'; -import './forms.css'; import { ProposalId } from '@joystream/types/proposals'; +import styled from 'styled-components'; // Generic form values export type GenericFormValues = { @@ -74,6 +75,33 @@ export const genericFormDefaultOptions: GenericFormDefaultOptions = { } }; +const StyledGenericProposalForm = styled.div` + .proposal-form { + margin: 0 auto; + } + + .ui.form.proposal-form { + & label { + font-size: 1rem; + } + + & input[name="tokens"] { + max-width: 16rem; + } + } + + .form-buttons { + display: flex; + button { + margin-right: 0.5em; + } + } + + .ui.dropdown .ui.avatar.image { + width: 2em !important; + } +`; + // Generic proposal form with basic structure, "Title" and "Rationale" fields // Other fields can be passed as children export const GenericProposalForm: React.FunctionComponent = props => { @@ -162,7 +190,7 @@ export const GenericProposalForm: React.FunctionComponent proposalsConsts[proposalType].stake; return ( -
+
(p === '{STAKE}' ? requiredStake : p))} tx={`proposalsCodex.${txMethod}`} @@ -220,7 +247,7 @@ export const GenericProposalForm: React.FunctionComponent
-
+ ); }; diff --git a/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx b/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx index 4c228e15dd..3da45c7bb3 100644 --- a/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx @@ -11,10 +11,9 @@ import { import { FormField } from './FormFields'; import { ProposalType } from '@polkadot/joy-utils/types/proposals'; import { WorkingGroupKey, WorkingGroupDef } from '@joystream/types/common'; -import './forms.css'; import { Dropdown, Message } from 'semantic-ui-react'; import { usePromise, useTransport } from '@polkadot/joy-utils/react/hooks'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; import { WorkerData } from '@polkadot/joy-utils/types/workingGroups'; import { LeadInfo } from '@polkadot/joy-utils/react/components/working-groups/LeadInfo'; @@ -70,7 +69,7 @@ export const GenericWorkingGroupProposalForm: React.FunctionComponent(errors, touched); return ( diff --git a/pioneer/packages/joy-proposals/src/forms/MintCapacityForm.tsx b/pioneer/packages/joy-proposals/src/forms/MintCapacityForm.tsx index 8c6c0296cc..25edcc8e5d 100644 --- a/pioneer/packages/joy-proposals/src/forms/MintCapacityForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/MintCapacityForm.tsx @@ -16,7 +16,6 @@ import { InputFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; import { ProposalType } from '@polkadot/joy-utils/types/proposals'; import { formatBalance } from '@polkadot/util'; -import './forms.css'; export type FormValues = GenericFormValues & { capacity: string; diff --git a/pioneer/packages/joy-proposals/src/forms/RuntimeUpgradeForm.tsx b/pioneer/packages/joy-proposals/src/forms/RuntimeUpgradeForm.tsx index ea16a093b7..9ed2ab19c0 100644 --- a/pioneer/packages/joy-proposals/src/forms/RuntimeUpgradeForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/RuntimeUpgradeForm.tsx @@ -13,7 +13,6 @@ import { } from './GenericProposalForm'; import Validation from '../validationSchema'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import FileDropdown from './FileDropdown'; export type FormValues = GenericFormValues & { diff --git a/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupLeadForm.tsx b/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupLeadForm.tsx index 17bb481874..7144166729 100644 --- a/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupLeadForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupLeadForm.tsx @@ -17,9 +17,8 @@ import { FormField } from './FormFields'; import { withFormContainer } from './FormContainer'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; import { Membership } from '@joystream/types/members'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; import _ from 'lodash'; -import './forms.css'; export type FormValues = GenericFormValues & { workingGroupLead: any; diff --git a/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupMintCapForm.tsx b/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupMintCapForm.tsx index be7f149cb1..a48d6a5676 100644 --- a/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupMintCapForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupMintCapForm.tsx @@ -2,7 +2,7 @@ import React from 'react'; import MintCapacityForm from './MintCapacityForm'; import { RouteComponentProps } from 'react-router'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; const ContentWorkingGroupMintCapForm = (props: RouteComponentProps) => { const transport = useTransport(); diff --git a/pioneer/packages/joy-proposals/src/forms/SetCouncilParamsForm.tsx b/pioneer/packages/joy-proposals/src/forms/SetCouncilParamsForm.tsx index 1cafa3f9c8..6f1a94313a 100644 --- a/pioneer/packages/joy-proposals/src/forms/SetCouncilParamsForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SetCouncilParamsForm.tsx @@ -15,12 +15,11 @@ import { import Validation from '../validationSchema'; import { InputFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; -import { createType } from '@polkadot/types'; -import './forms.css'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; import _ from 'lodash'; -import { ElectionParameters } from '@joystream/types/council'; -import { PromiseComponent } from '@polkadot/joy-utils/react/components'; +import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent'; +import { IElectionParameters } from '@joystream/types/src/council'; +import { SimplifiedTypeInterface } from '@polkadot/joy-utils/types/common'; export type FormValues = GenericFormValues & { announcingPeriod: string; @@ -50,17 +49,17 @@ type ExportComponentProps = ProposalFormExportProps; type FormInnerProps = ProposalFormInnerProps; -function createElectionParameters (values: FormValues): ElectionParameters { - return new ElectionParameters({ - announcing_period: createType('BlockNumber', parseInt(values.announcingPeriod)), - voting_period: createType('BlockNumber', parseInt(values.votingPeriod)), - revealing_period: createType('BlockNumber', parseInt(values.revealingPeriod)), - council_size: createType('u32', values.councilSize), - candidacy_limit: createType('u32', values.candidacyLimit), - new_term_duration: createType('BlockNumber', parseInt(values.newTermDuration)), - min_council_stake: createType('Balance', values.minCouncilStake), - min_voting_stake: createType('Balance', values.minVotingStake) - }); +function createElectionParameters (values: FormValues): SimplifiedTypeInterface { + return { + announcing_period: parseInt(values.announcingPeriod), + voting_period: parseInt(values.votingPeriod), + revealing_period: parseInt(values.revealingPeriod), + council_size: values.councilSize, + candidacy_limit: values.candidacyLimit, + new_term_duration: parseInt(values.newTermDuration), + min_council_stake: values.minCouncilStake, + min_voting_stake: values.minVotingStake + }; } const SetCouncilParamsForm: React.FunctionComponent = props => { @@ -69,7 +68,7 @@ const SetCouncilParamsForm: React.FunctionComponent = props => { const [placeholders, setPlaceholders] = useState<{ [k in keyof FormValues]: string }>(defaultValues); const transport = useTransport(); - const [councilParams, error, loading] = usePromise(() => transport.council.electionParameters(), null); + const [councilParams, error, loading] = usePromise(() => transport.council.electionParameters(), null); useEffect(() => { if (councilParams) { const fetchedPlaceholders = { ...placeholders }; diff --git a/pioneer/packages/joy-proposals/src/forms/SetMaxValidatorCountForm.tsx b/pioneer/packages/joy-proposals/src/forms/SetMaxValidatorCountForm.tsx index e274d2dff6..f32031b705 100644 --- a/pioneer/packages/joy-proposals/src/forms/SetMaxValidatorCountForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SetMaxValidatorCountForm.tsx @@ -15,7 +15,6 @@ import Validation from '../validationSchema'; import { InputFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks'; -import './forms.css'; export type FormValues = GenericFormValues & { maxValidatorCount: string; diff --git a/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupLeadRewardForm.tsx b/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupLeadRewardForm.tsx index 39f15b2a13..528692ed1f 100644 --- a/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupLeadRewardForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupLeadRewardForm.tsx @@ -15,7 +15,6 @@ import { } from './GenericWorkingGroupProposalForm'; import { InputFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import { Grid } from 'semantic-ui-react'; import { formatBalance } from '@polkadot/util'; import _ from 'lodash'; diff --git a/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupMintCapacityForm.tsx b/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupMintCapacityForm.tsx index a53006273f..9b4513383a 100644 --- a/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupMintCapacityForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SetWorkingGroupMintCapacityForm.tsx @@ -15,7 +15,6 @@ import { } from './GenericWorkingGroupProposalForm'; import { InputFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import { Grid } from 'semantic-ui-react'; import { formatBalance } from '@polkadot/util'; import _ from 'lodash'; diff --git a/pioneer/packages/joy-proposals/src/forms/SignalForm.tsx b/pioneer/packages/joy-proposals/src/forms/SignalForm.tsx index 1163346db8..b0c722febe 100644 --- a/pioneer/packages/joy-proposals/src/forms/SignalForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SignalForm.tsx @@ -14,7 +14,6 @@ import { import Validation from '../validationSchema'; import { TextareaFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; -import './forms.css'; export type FormValues = GenericFormValues & { description: string; diff --git a/pioneer/packages/joy-proposals/src/forms/SlashWorkingGroupLeadStakeForm.tsx b/pioneer/packages/joy-proposals/src/forms/SlashWorkingGroupLeadStakeForm.tsx index c9e4692a62..ceaaf76375 100644 --- a/pioneer/packages/joy-proposals/src/forms/SlashWorkingGroupLeadStakeForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SlashWorkingGroupLeadStakeForm.tsx @@ -15,7 +15,6 @@ import { } from './GenericWorkingGroupProposalForm'; import { InputFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import { Grid } from 'semantic-ui-react'; import { formatBalance } from '@polkadot/util'; import _ from 'lodash'; diff --git a/pioneer/packages/joy-proposals/src/forms/SpendingProposalForm.tsx b/pioneer/packages/joy-proposals/src/forms/SpendingProposalForm.tsx index 125e2535d9..298af09cb1 100644 --- a/pioneer/packages/joy-proposals/src/forms/SpendingProposalForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/SpendingProposalForm.tsx @@ -17,7 +17,6 @@ import { InputFormField, FormField } from './FormFields'; import { withFormContainer } from './FormContainer'; import { InputAddress } from '@polkadot/react-components/index'; import { formatBalance } from '@polkadot/util'; -import './forms.css'; export type FormValues = GenericFormValues & { destinationAccount: any; diff --git a/pioneer/packages/joy-proposals/src/forms/TerminateWorkingGroupLeaderForm.tsx b/pioneer/packages/joy-proposals/src/forms/TerminateWorkingGroupLeaderForm.tsx index c44b225042..2fd5020364 100644 --- a/pioneer/packages/joy-proposals/src/forms/TerminateWorkingGroupLeaderForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/TerminateWorkingGroupLeaderForm.tsx @@ -13,20 +13,17 @@ import { defaultValues as wgFromDefaultValues } from './GenericWorkingGroupProposalForm'; import { withFormContainer } from './FormContainer'; -import './forms.css'; import _ from 'lodash'; import Validation from '../validationSchema'; import { WorkerData } from '@polkadot/joy-utils/types/workingGroups'; import { getFormErrorLabelsProps } from './errorHandling'; import FormField, { TextareaFormField } from './FormFields'; import { Checkbox } from 'semantic-ui-react'; -import { TerminateRoleParameters } from '@joystream/types/proposals'; -import { WorkerId } from '@joystream/types/working-group'; -import { Bytes } from '@polkadot/types'; -import { WorkingGroup, InputValidationLengthConstraint } from '@joystream/types/common'; -import { bool as Bool } from '@polkadot/types/primitive'; +import { InputValidationLengthConstraint } from '@joystream/types/common'; import { withCalls } from '@polkadot/react-api'; import { formatBalance } from '@polkadot/util'; +import { SimplifiedTypeInterface } from '@polkadot/joy-utils/types/common'; +import { ITerminateRoleParameters } from '@joystream/types/src/proposals'; export type FormValues = WGFormValues & { terminationRationale: string; @@ -46,13 +43,13 @@ type FormContainerProps = ProposalFormContainerProps & { }; type FormInnerProps = ProposalFormInnerProps; -const valuesToTerminateRoleParams = (values: FormValues, lead: WorkerData): TerminateRoleParameters => { - return new TerminateRoleParameters({ - worker_id: new WorkerId(lead.workerId), - rationale: new Bytes(values.terminationRationale), - slash: lead.stake ? new Bool(values.slashStake) : new Bool(false), - working_group: new WorkingGroup(values.workingGroup) - }); +const valuesToTerminateRoleParams = (values: FormValues, lead: WorkerData): SimplifiedTypeInterface => { + return { + worker_id: lead.workerId, + rationale: values.terminationRationale, + slash: lead.stake ? values.slashStake : false, + working_group: values.workingGroup + }; }; const TerminateWorkingGroupLeaderForm: React.FunctionComponent = props => { @@ -111,7 +108,7 @@ const FormContainer = withFormContainer({ validationSchema: (props: FormContainerProps) => Yup.object().shape({ ...genericFormDefaultOptions.validationSchema, ...Validation.TerminateWorkingGroupLeaderRole( - props.terminationRationaleConstraint || InputValidationLengthConstraint.createWithMaxAllowed() + props.terminationRationaleConstraint ) }), handleSubmit: genericFormDefaultOptions.handleSubmit, diff --git a/pioneer/packages/joy-proposals/src/forms/forms.css b/pioneer/packages/joy-proposals/src/forms/forms.css deleted file mode 100644 index d800c9c497..0000000000 --- a/pioneer/packages/joy-proposals/src/forms/forms.css +++ /dev/null @@ -1,23 +0,0 @@ -.Forms { - .proposal-form { - margin: 0 auto; - } - - .ui.form.proposal-form { - & label { - font-size: 1rem; - } - - & input[name="tokens"] { - max-width: 16rem; - } - } - - .form-buttons { - display: flex; - } - - .ui.dropdown .ui.avatar.image { - width: 2em !important; - } -} diff --git a/pioneer/packages/joy-proposals/src/index.css b/pioneer/packages/joy-proposals/src/index.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pioneer/packages/joy-proposals/src/index.tsx b/pioneer/packages/joy-proposals/src/index.tsx index ffa06eae0a..ec3cd611ad 100644 --- a/pioneer/packages/joy-proposals/src/index.tsx +++ b/pioneer/packages/joy-proposals/src/index.tsx @@ -4,13 +4,10 @@ import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { Breadcrumb } from 'semantic-ui-react'; -import { AppProps, I18nProps } from '@polkadot/react-components/types'; -import { TransportProvider } from '@polkadot/joy-utils/react/context'; +import { I18nProps } from '@polkadot/react-components/types'; import { ProposalPreviewList, ProposalFromId, ChooseProposalType } from './Proposal'; import _ from 'lodash'; -import './index.css'; - import translate from './translate'; import NotDone from './NotDone'; import { @@ -30,8 +27,12 @@ import { SetWorkingGroupLeadRewardForm, TerminateWorkingGroupLeaderForm } from './forms'; +import { RouteProps as AppMainRouteProps } from '@polkadot/apps-routing/types'; +import style from './style'; + +const ProposalsMain = styled.main`${style}`; -interface Props extends AppProps, I18nProps {} +interface Props extends AppMainRouteProps, I18nProps {} const StyledHeader = styled.header` text-align: left; @@ -46,59 +47,57 @@ function App (props: Props): React.ReactElement { const { basePath } = props; return ( - -
- - - - ( - <> - Proposals - - New proposal - - {_.startCase(props.match.params.type)} - - )} /> - + + + + + ( + <> Proposals - New proposal - - - Proposals - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
+ New proposal + + {_.startCase(props.match.params.type)} + + )} /> + + Proposals + + New proposal + + + Proposals + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/pioneer/packages/joy-proposals/src/stories/data/ProposalDetails.mock.ts b/pioneer/packages/joy-proposals/src/stories/data/ProposalDetails.mock.ts index 474db8c862..e985cc160e 100644 --- a/pioneer/packages/joy-proposals/src/stories/data/ProposalDetails.mock.ts +++ b/pioneer/packages/joy-proposals/src/stories/data/ProposalDetails.mock.ts @@ -1,13 +1,13 @@ import { ParsedProposal } from '@polkadot/joy-utils/types/proposals'; -import { ProposalId } from '@joystream/types/proposals'; +import { createMock } from '@joystream/types'; const mockedProposal: ParsedProposal = { - id: new ProposalId(100), + id: createMock('ProposalId', 100), title: 'Awesome Proposal', description: 'Please send me some tokens for coffee', createdAtBlock: 36, type: 'Text', - details: ['Ciao'], + details: createMock('ProposalDetails', { Text: 'Ciao' }), parameters: { approvalQuorumPercentage: 66, approvalThresholdPercentage: 80, diff --git a/pioneer/packages/joy-proposals/src/Proposal/Proposal.css b/pioneer/packages/joy-proposals/src/style.ts similarity index 80% rename from pioneer/packages/joy-proposals/src/Proposal/Proposal.css rename to pioneer/packages/joy-proposals/src/style.ts index d3dd08ab20..3802a35c76 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/Proposal.css +++ b/pioneer/packages/joy-proposals/src/style.ts @@ -1,7 +1,7 @@ -.Proposal { - position: relative; +import { css } from 'styled-components'; - .description { +export default css` + .ui.card .description { word-wrap: break-word; word-break: break-word; } @@ -26,4 +26,4 @@ .ui.tabular.list-menu { margin-bottom: 2rem; } -} +`; diff --git a/pioneer/packages/joy-proposals/src/validationSchema.ts b/pioneer/packages/joy-proposals/src/validationSchema.ts index 7b3c68c28c..0507461755 100644 --- a/pioneer/packages/joy-proposals/src/validationSchema.ts +++ b/pioneer/packages/joy-proposals/src/validationSchema.ts @@ -152,9 +152,9 @@ type FormValuesByType = never; type ValidationSchemaFuncParamsByType = - T extends 'AddWorkingGroupLeaderOpening' ? [number, InputValidationLengthConstraint] : + T extends 'AddWorkingGroupLeaderOpening' ? [number, InputValidationLengthConstraint | undefined] : T extends 'FillWorkingGroupLeaderOpening' ? [number] : - T extends 'TerminateWorkingGroupLeaderRole' ? [InputValidationLengthConstraint] : + T extends 'TerminateWorkingGroupLeaderRole' ? [InputValidationLengthConstraint | undefined] : []; /* eslint-enable @typescript-eslint/indent */ @@ -178,6 +178,23 @@ function minMaxInt (min: number, max: number, fieldName: string) { .max(max, errorMessage(fieldName, min, max)); } +function minMaxStrFromConstraint(constraint: InputValidationLengthConstraint | undefined, fieldName: string) { + const schema = Yup.string().required(`${fieldName} is required!`); + return constraint + ? ( + schema + .min( + constraint.min.toNumber(), + `${fieldName} must be at least ${constraint.min.toNumber()} character(s) long` + ) + .max( + constraint.max.toNumber(), + `${fieldName} cannot be more than ${constraint.max.toNumber()} character(s) long` + ) + ) + : schema; +} + const Validation: ValidationType = { All: () => ({ title: Yup.string() @@ -303,7 +320,7 @@ const Validation: ValidationType = { errorMessage('The max validator count', MAX_VALIDATOR_COUNT_MIN, MAX_VALIDATOR_COUNT_MAX) ) }), - AddWorkingGroupLeaderOpening: (currentBlock: number, { min: HRTMin, max: HRTMax }: InputValidationLengthConstraint) => ({ + AddWorkingGroupLeaderOpening: (currentBlock: number, HRTConstraint?: InputValidationLengthConstraint) => ({ workingGroup: Yup.string(), activateAt: Yup.string().required(), activateAtBlock: Yup.number() @@ -346,8 +363,7 @@ const Validation: ValidationType = { LEAVE_ROLE_UNSTAKING_MAX, 'Leave role unstaking period' ), - humanReadableText: Yup.string() - .required() + humanReadableText: minMaxStrFromConstraint(HRTConstraint, 'human_readable_text') .test( 'schemaIsValid', 'Schema validation failed!', @@ -368,8 +384,6 @@ const Validation: ValidationType = { return true; } ) - .min(HRTMin.toNumber(), `human_readable_text must be at least ${HRTMin.toNumber()} character(s) long`) - .max(HRTMax.toNumber(), `human_readable_text cannot be more than ${HRTMax.toNumber()} character(s) long`) }), SetWorkingGroupMintCapacity: () => ({ workingGroup: Yup.string(), @@ -421,12 +435,9 @@ const Validation: ValidationType = { workingGroup: Yup.string(), amount: minMaxInt(MIN_REWARD_AMOUNT, MAX_REWARD_AMOUNT, 'Reward amount') }), - TerminateWorkingGroupLeaderRole: ({ min, max }: InputValidationLengthConstraint) => ({ + TerminateWorkingGroupLeaderRole: (rationaleConstraint?: InputValidationLengthConstraint) => ({ workingGroup: Yup.string(), - terminationRationale: Yup.string() - .required('Termination rationale is required') - .min(min.toNumber(), `Termination rationale must be at least ${min.toNumber()} character(s) long`) - .max(max.toNumber(), `Termination rationale cannot be more than ${max.toNumber()} character(s) long`), + terminationRationale: minMaxStrFromConstraint(rationaleConstraint, 'Termination rationale'), slashStake: Yup.boolean() }) }; diff --git a/pioneer/packages/joy-utils-old/src/consts/members.ts b/pioneer/packages/joy-utils/src/consts/members.ts similarity index 100% rename from pioneer/packages/joy-utils-old/src/consts/members.ts rename to pioneer/packages/joy-utils/src/consts/members.ts diff --git a/pioneer/packages/joy-utils-old/src/consts/proposals.ts b/pioneer/packages/joy-utils/src/consts/proposals.ts similarity index 100% rename from pioneer/packages/joy-utils-old/src/consts/proposals.ts rename to pioneer/packages/joy-utils/src/consts/proposals.ts diff --git a/pioneer/packages/joy-utils-old/src/consts/workingGroups.ts b/pioneer/packages/joy-utils/src/consts/workingGroups.ts similarity index 100% rename from pioneer/packages/joy-utils-old/src/consts/workingGroups.ts rename to pioneer/packages/joy-utils/src/consts/workingGroups.ts diff --git a/pioneer/packages/joy-utils/src/react/components/TxButton.tsx b/pioneer/packages/joy-utils/src/react/components/TxButton.tsx index 73eb3b3bbe..9ada4c4d8b 100644 --- a/pioneer/packages/joy-utils/src/react/components/TxButton.tsx +++ b/pioneer/packages/joy-utils/src/react/components/TxButton.tsx @@ -1,67 +1,55 @@ -import React from 'react'; -import { BareProps, ApiProps } from '@polkadot/react-api/types'; -import { QueueTxExtrinsicAdd, PartialQueueTxExtrinsic, TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types'; +import React, { useContext } from 'react'; +import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types'; import { Button } from '@polkadot/react-components/index'; -import { QueueConsumer } from '@polkadot/react-components/Status/Context'; -import { withApi } from '@polkadot/react-api/index'; +import QueueContext from '@polkadot/react-components/Status/Context'; import { assert } from '@polkadot/util'; -import { withMyAccount, MyAccountProps } from '../hocs/accounts'; -import { IconName } from '@fortawesome/fontawesome-svg-core'; - -type InjectedProps = { - queueExtrinsic: QueueTxExtrinsicAdd; -}; +import { ButtonProps as DefaultButtonProps } from '@polkadot/react-components/Button/types'; +import { StrictButtonProps as SemanticButtonStrictProps, Button as SemanticButton } from 'semantic-ui-react'; +import { useApi } from '@polkadot/react-hooks'; +import { useMyAccount } from '../hooks'; +import _ from 'lodash'; export type OnTxButtonClick = (sendTx: () => void) => void; -type BasicButtonProps = { +type TxButtonBaseProps = { accountId?: string; type?: 'submit' | 'button'; - isBasic?: boolean; - isDisabled?: boolean; - label?: React.ReactNode; params: Array; tx: string; - - className?: string; - style?: Record; - children?: React.ReactNode; - compact?: boolean; - icon?: IconName; - onClick?: OnTxButtonClick; txFailedCb?: TxFailedCallback; txSuccessCb?: TxCallback; txStartCb?: () => void; txUpdateCb?: TxCallback; -}; - -type PropsWithApi = BareProps & ApiProps & MyAccountProps & PartialQueueTxExtrinsic & BasicButtonProps - -class TxButtonInner extends React.PureComponent { - render () { - const { myAddress, accountId, isDisabled, icon = 'check', onClick } = this.props; - const origin = accountId || myAddress; - - return ( - - + { proposalsBatch && (<> { proposalsBatch.totalBatches > 1 && ( @@ -81,16 +81,16 @@ function ProposalPreviewList ({ bestNumber }: ProposalPreviewListProps) { /> ) } - { proposalsBatch.proposals.length - ? ( - - {proposalsBatch.proposals.map((prop: ParsedProposal, idx: number) => ( - - ))} - - ) - : `There are currently no ${activeFilter !== 'All' ? activeFilter.toLocaleLowerCase() : 'submitted'} proposals.` - } + { proposalsBatch.proposals.length + ? ( + + {proposalsBatch.proposals.map((prop: ParsedProposal, idx: number) => ( + + ))} + + ) + : `There are currently no ${activeFilter !== 'All' ? activeFilter.toLocaleLowerCase() : 'submitted'} proposals.` + } ) } diff --git a/pioneer/packages/joy-proposals/src/Proposal/ProposalTypePreview.tsx b/pioneer/packages/joy-proposals/src/Proposal/ProposalTypePreview.tsx index 092848a77b..fc10dbf01b 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/ProposalTypePreview.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/ProposalTypePreview.tsx @@ -62,9 +62,9 @@ type ProposalTypePreviewProps = { }; const ProposalTypeDetail = (props: { title: string; value: string }) => ( -
-
{ `${props.title}:` }
-
{ props.value }
+
+
{ `${props.title}:` }
+
{ props.value }
); @@ -90,7 +90,7 @@ export default function ProposalTypePreview (props: ProposalTypePreviewProps) { }; return ( - + {/* TODO: We can add it once we have the actual assets @@ -98,18 +98,18 @@ export default function ProposalTypePreview (props: ProposalTypePreviewProps) { {_.startCase(type)} {description} -
+
1 ? 's' : ''}` : 'NONE' } /> 1 ? 's' : ''}` : 'NONE' } />
@@ -139,10 +139,10 @@ export default function ProposalTypePreview (props: ProposalTypePreviewProps) { ) } -
- +
+ Create - +
diff --git a/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx b/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx index 42f9c5eec7..5186ff18c3 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/Votes.tsx @@ -24,20 +24,21 @@ export default function Votes ({ proposal: { id, votingResults } }: VotesProps) + message='Fetching the votes...'> { (votes && votes.votes.length > 0) ? ( <> -
+
All Votes: ({votes.votes.length}/{votes.councilMembersLength})
- +
{votes.votes.map((proposalVote, idx) => { const { vote, member } = proposalVote; const voteStr = (vote as VoteKind).type.toString() as VoteKindStr; const { icon, textColor } = useVoteStyles(voteStr); + return ( @@ -59,7 +60,7 @@ export default function Votes ({ proposal: { id, votingResults } }: VotesProps) ) : ( -
No votes have been submitted!
+
No votes have been submitted!
) } diff --git a/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx b/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx index 086dcd5d53..a18251bd91 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/VotingSection.tsx @@ -32,8 +32,10 @@ type VoteButtonProps = { proposalId: ProposalId; onSuccess: () => void; } + function VoteButton ({ voteKind, proposalId, memberId, onSuccess }: VoteButtonProps) { const { icon, color } = useVoteStyles(voteKind); + return ( sendTx() } + onClick={ (sendTx) => sendTx() } txFailedCb={ () => null } txSuccessCb={ onSuccess } color={color} @@ -87,7 +89,7 @@ export default function VotingSection ({ - You voted {`"${voteStr}"`} + You voted {`"${voteStr}"`} ); @@ -97,7 +99,7 @@ export default function VotingSection ({ return ( <> -
Sumbit your vote
+
Sumbit your vote
{ VoteKinds.map((voteKind) => diff --git a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx index 4869c5dac0..b1c26fdea5 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPost.tsx @@ -61,6 +61,7 @@ export default function DiscussionPost ({ authorId.toNumber() === memberId.toNumber() && editsCount < constraints.maxPostEdits ); + const onEditSuccess = () => { setEditing(false); refreshDiscussion(); @@ -98,9 +99,9 @@ export default function DiscussionPost ({ setEditing(true)} primary - size="tiny" + size='tiny' icon> - + ) } diff --git a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx index c19a25a4ee..b9161990bb 100644 --- a/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx +++ b/pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx @@ -76,7 +76,7 @@ const DiscussionPostFormInner = (props: InnerProps) => { }; return ( -
+ { { /> { isEditForm ? ( )} - diff --git a/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx b/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx index 3da45c7bb3..98bc9dfaa3 100644 --- a/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx +++ b/pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx @@ -1,13 +1,11 @@ import React from 'react'; import { getFormErrorLabelsProps } from './errorHandling'; -import { - GenericProposalForm, +import { GenericProposalForm, GenericFormValues, genericFormDefaultValues, ProposalFormExportProps, ProposalFormContainerProps, - ProposalFormInnerProps -} from './GenericProposalForm'; + ProposalFormInnerProps } from './GenericProposalForm'; import { FormField } from './FormFields'; import { ProposalType } from '@polkadot/joy-utils/types/proposals'; import { WorkingGroupKey, WorkingGroupDef } from '@joystream/types/common'; @@ -45,7 +43,7 @@ type ExportComponentProps = ProposalFormExportProps; export type FormInnerProps = ProposalFormInnerProps; -export const GenericWorkingGroupProposalForm: React.FunctionComponent = props => { +export const GenericWorkingGroupProposalForm: React.FunctionComponent = (props) => { const { handleChange, errors, @@ -72,17 +70,18 @@ export const GenericWorkingGroupProposalForm: React.FunctionComponent(errors, touched); + return ( ({ text: wgKey + ' Working Group', value: wgKey }))} + options={Object.keys(WorkingGroupDef).map((wgKey) => ({ text: wgKey + ' Working Group', value: wgKey }))} value={values.workingGroup} onChange={ handleChange } /> diff --git a/pioneer/packages/joy-proposals/src/forms/LabelWithHelp.tsx b/pioneer/packages/joy-proposals/src/forms/LabelWithHelp.tsx index d8880ba5bc..deb9be8c0d 100644 --- a/pioneer/packages/joy-proposals/src/forms/LabelWithHelp.tsx +++ b/pioneer/packages/joy-proposals/src/forms/LabelWithHelp.tsx @@ -5,6 +5,7 @@ type LabelWithHelpProps = { text: string; help: string }; export default function LabelWithHelp (props: LabelWithHelpProps) { const [open, setOpen] = useState(false); + return (
{[...props.transactionDetails].map((v, k) => ( @@ -934,7 +961,7 @@ export type DoneStageProps = { export function DoneStage (props: DoneStageProps) { return ( - +

Application submitted!

Your application is #. @@ -942,8 +969,8 @@ export function DoneStage (props: DoneStageProps) {

You can track the progress of your - application in the My roles and applications section. Note that your application is attached - to your role key (see below). If you have any issues, you can message the group lead in in the Forum or contact them directly. + application in the My roles and applications section. Note that your application is attached + to your role key (see below). If you have any issues, you can message the group lead in in the Forum or contact them directly.

Your new role key

@@ -953,7 +980,7 @@ export function DoneStage (props: DoneStageProps) {

{'We\'ve generated a new role key, '}{props.roleKeyName}, automatically. A copy of the backup file should have been downloaded, or you can - get a backup from the My account section. + get a backup from the My account section.

You can also switch your role key using the Accounts selector in the top right of the screen. It works like @@ -969,7 +996,7 @@ export function DoneStage (props: DoneStageProps) { This role key has been generated with no password! - We strongly recommend that you set a password for it in the My account section. + We strongly recommend that you set a password for it in the My account section. @@ -1014,7 +1041,7 @@ export const FlowModal = Loadable( 'keypairs', 'slots' ], - props => { + (props) => { const { applicationStake, setApplicationStake, roleStake, setRoleStake, @@ -1026,6 +1053,7 @@ export const FlowModal = Loadable( } = props; const accContext = useMyAccount(); + useEffect(() => { if (txKeyAddress.isEmpty) { setTxKeyAddress(createMock('AccountId', accContext.state.address)); @@ -1033,11 +1061,14 @@ export const FlowModal = Loadable( }, [txKeyAddress]); const history = useHistory(); + const cancel = () => { if (history.length > 1) { history.goBack(); + return; } + history.push('/working-groups/'); }; @@ -1092,56 +1123,56 @@ export const FlowModal = Loadable( const cancelText = complete ? 'Close' : 'Cancel application'; return ( - -

- - - + +
+ + + - + cancel()}> {cancelText} - - + + { activeStep === ProgressSteps.ConfirmStakes && ( + {...props} + nextTransition={enterApplicationDetailsState} + prevTransition={cancel} + {...setStakeProps} + /> ) } { activeStep === ProgressSteps.ApplicationDetails && ( { props.hasConfirmStep ? enterConfirmStakeState() : cancel(); }} - /> + setData={setAppDetails} + data={appDetails} + applicationDetails={props.role.application} + nextTransition={enterSubmitApplicationState} + prevTransition={() => { props.hasConfirmStep ? enterConfirmStakeState() : cancel(); }} + /> ) } { activeStep === ProgressSteps.SubmitApplication && ( - ) } + {...props} + nextTransition={enterDoneState} + prevTransition={enterApplicationDetailsState} + keyAddress={txKeyAddress} + setKeyAddress={setTxKeyAddress} + transactionDetails={props.transactionDetails} + totalStake={Add(applicationStake, roleStake)} + /> + ) } { activeStep === ProgressSteps.Done && () } - +
{props.role.headline}
-
{txInProgress && -
-
+
+
} diff --git a/pioneer/packages/joy-roles/src/tabs.stories.tsx b/pioneer/packages/joy-roles/src/tabs.stories.tsx index 03c5640628..fa6a546449 100644 --- a/pioneer/packages/joy-roles/src/tabs.stories.tsx +++ b/pioneer/packages/joy-roles/src/tabs.stories.tsx @@ -14,7 +14,7 @@ export default { export const RolesPage = () => { const tab = ( - + diff --git a/pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx b/pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx index 92f530f081..9e159c48db 100644 --- a/pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx +++ b/pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx @@ -11,8 +11,7 @@ import { View } from '@polkadot/joy-utils/react/hocs'; import { useMyAccount } from '@polkadot/joy-utils/react/hooks'; import { QueueTxExtrinsicAdd } from '@polkadot/react-components/Status/types'; -import { - Accordion, +import { Accordion, Button, Card, Checkbox, @@ -26,44 +25,33 @@ import { Message, Modal, Table, - TextArea -} from 'semantic-ui-react'; + TextArea } from 'semantic-ui-react'; import { ITransport } from '../transport'; -import { - Application, +import { Application, ApplicationStage, ActivateOpeningAt, Opening, OpeningStage, StakingPolicy, StakingAmountLimitModeKeys, - StakingAmountLimitMode -} from '@joystream/types/hiring'; + StakingAmountLimitMode } from '@joystream/types/hiring'; -import { - Membership, - MemberId -} from '@joystream/types/members'; +import { Membership, + MemberId } from '@joystream/types/members'; import { Stake, StakeId } from '@joystream/types/stake'; -import { - CuratorApplication, CuratorApplicationId, +import { CuratorApplication, CuratorApplicationId, CuratorOpening, - IOpeningPolicyCommitment, CuratorOpeningId -} from '@joystream/types/content-working-group'; + IOpeningPolicyCommitment, CuratorOpeningId } from '@joystream/types/content-working-group'; -import { - classifyOpeningStage, +import { classifyOpeningStage, OpeningStageClassification, - OpeningState -} from '../classifiers'; + OpeningState } from '../classifiers'; -import { - openingDescription -} from '../openingStateMarkup'; +import { openingDescription } from '../openingStateMarkup'; import { Add, Zero } from '../balances'; import { createMock } from '@joystream/types'; @@ -110,46 +98,46 @@ type State = { function newHRT (title: string): Text { return createMock('Text', JSON.stringify({ - version: 1, - headline: 'some headline', - job: { - title: title, - description: 'some job description' - }, - application: { - sections: [ - { - title: 'About you', - questions: [ - { - title: 'your name', - type: 'text' - } - ] - }, - { - title: 'Something else', - questions: [ - { - title: 'another thing', - type: 'text area' - } - ] - } - ] - }, - reward: '10 JOY per block', - creator: { - membership: { - handle: 'ben' + version: 1, + headline: 'some headline', + job: { + title: title, + description: 'some job description' + }, + application: { + sections: [ + { + title: 'About you', + questions: [ + { + title: 'your name', + type: 'text' + } + ] + }, + { + title: 'Something else', + questions: [ + { + title: 'another thing', + type: 'text area' + } + ] } - }, - process: { - details: [ - 'Some custom detail' - ] + ] + }, + reward: '10 JOY per block', + creator: { + membership: { + handle: 'ben' } - }) + }, + process: { + details: [ + 'Some custom detail' + ] + } + }) ); } @@ -379,15 +367,19 @@ export class AdminController extends Controller { startAcceptingApplications (accountId: string, id = 0) { const tx = this.api.tx.contentWorkingGroup.acceptCuratorApplications(id); + this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId }); } async applyAsACurator (creatorAddress: string, openingId: number) { const membershipIds = (await this.api.query.members.memberIdsByControllerAccountId(creatorAddress)) as Vec; + if (membershipIds.length === 0) { console.error('No membship ID associated with this address'); + return; } + const tx = this.api.tx.contentWorkingGroup.applyOnCuratorOpening( membershipIds[0], openingId, @@ -396,11 +388,13 @@ export class AdminController extends Controller { 400, 'This is my application' ); + this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId: creatorAddress }); } beginApplicantReview (accountId: string, openingId: number) { const tx = this.api.tx.contentWorkingGroup.beginCuratorApplicantReview(openingId); + this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId }); } @@ -410,19 +404,23 @@ export class AdminController extends Controller { applications, null ); + this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId }); } protected async profile (id: MemberId): Promise { const member = (await this.api.query.members.membershipById(id)) as Membership; + if (member.isEmpty) { throw new Error(`Expected member profile not found! (id: ${id.toString()}`); } + return member; } protected async stakeValue (stakeId: StakeId): Promise { const stake = await this.api.query.stake.stakes(stakeId) as Stake; + return stake.value; } @@ -446,6 +444,7 @@ export class AdminController extends Controller { this.state.openings = new Map(); const nextOpeningId = await this.api.query.contentWorkingGroup.nextCuratorOpeningId() as CuratorOpeningId; + for (let i = nextOpeningId.toNumber() - 1; i >= 0; i--) { const curatorOpening = await this.api.query.contentWorkingGroup.curatorOpeningById(i) as CuratorOpening; @@ -467,6 +466,7 @@ export class AdminController extends Controller { } const nextAppid = await this.api.query.contentWorkingGroup.nextCuratorApplicationId() as CuratorApplicationId; + for (let i = 0; i < nextAppid.toNumber(); i++) { const cApplication = await this.api.query.contentWorkingGroup.curatorApplicationById(i) as CuratorApplication; @@ -507,12 +507,14 @@ type AdminContainerProps = { state: State; controller: AdminController; } + const AdminContainer = ({ state, controller }: AdminContainerProps) => { const address = useMyAccount().state.address; const containerRef = useRef(null); + return (
- + @@ -544,7 +546,7 @@ const AdminContainer = ({ state, controller }: AdminContainerProps) => { { - [...state.openings.keys()].map(key => ) + [...state.openings.keys()].map((key) => ) }
@@ -606,6 +608,7 @@ const NewOpening = (props: NewOpeningProps) => { const onChangePolicyField = (fieldName: PolicyKey, value: policyDescriptor[PolicyKey]) => { const newState = { ...policy }; + newState[fieldName] = value; setPolicy(newState); }; @@ -642,14 +645,17 @@ const NewOpening = (props: NewOpeningProps) => { ) => { if (mode === '') { const policyField = policy[fieldName]; + mode = policyField && policyField.isSome ? (policyField.unwrap().amount_mode.type as StakingAmountLimitModeKeys) : StakingAmountLimitModeKeys.Exact; // Default } + const value = createStakingPolicyOpt( stakeValue, mode === StakingAmountLimitModeKeys.Exact ? STAKING_MODE_EXACT : STAKING_MODE_AT_LEAST ); + onChangePolicyField(fieldName, value); }; @@ -686,7 +692,7 @@ const NewOpening = (props: NewOpeningProps) => { /> {showExactBlock === true && @@ -696,7 +702,7 @@ const NewOpening = (props: NewOpeningProps) => { onChangePolicyField('max_review_period_length', createMock('u32', value))} /> @@ -704,7 +710,7 @@ const NewOpening = (props: NewOpeningProps) => { - onStakeModeCheckboxChange(setRequireAppStakingPolicy, 'application_staking_policy', checked, 0)} /> + onStakeModeCheckboxChange(setRequireAppStakingPolicy, 'application_staking_policy', checked, 0)} /> {requireAppStakingPolicy && ( @@ -717,7 +723,7 @@ const NewOpening = (props: NewOpeningProps) => { changeStakingMode('application_staking_policy', '', value)} /> @@ -727,7 +733,7 @@ const NewOpening = (props: NewOpeningProps) => { - onStakeModeCheckboxChange(setRequireRoleStakingPolicy, 'role_staking_policy', checked, 0)} /> + onStakeModeCheckboxChange(setRequireRoleStakingPolicy, 'role_staking_policy', checked, 0)} /> {requireRoleStakingPolicy && ( @@ -740,7 +746,7 @@ const NewOpening = (props: NewOpeningProps) => { changeStakingMode('role_staking_policy', '', value)} /> @@ -752,7 +758,7 @@ const NewOpening = (props: NewOpeningProps) => {