Browse Source

feat: implement vote casting

develop
Apostolos Fanakis 4 years ago
parent
commit
8c2d26bfe9
Signed by: Apostolof GPG Key ID: 8600B4C4163B3269
  1. 17
      packages/concordia-app/src/components/PollView/PollGraph/index.jsx
  2. 40
      packages/concordia-app/src/components/PollView/PollVote/index.jsx
  3. 70
      packages/concordia-app/src/components/PollView/index.jsx

17
packages/concordia-app/src/components/PollView/PollGraph/index.jsx

@ -7,7 +7,7 @@ import { CASTED_OPTION_COLOR, DEFAULT_OPTION_COLOR } from '../../../constants/po
const PollGraph = (props) => { const PollGraph = (props) => {
const { const {
pollOptions, voteCounts, hasUserVoted, userVoteHash, pollOptions, voteCounts, hasUserVoted, selectedOptionIndex,
} = props; } = props;
const { t } = useTranslation(); const { t } = useTranslation();
@ -22,16 +22,16 @@ const PollGraph = (props) => {
}, },
colors: [ colors: [
(value) => { (value) => {
if (hasUserVoted && pollOptions[value.dataPointIndex].hash === userVoteHash) { if (hasUserVoted && value.dataPointIndex === selectedOptionIndex) {
return CASTED_OPTION_COLOR; return CASTED_OPTION_COLOR;
} }
return DEFAULT_OPTION_COLOR; return DEFAULT_OPTION_COLOR;
}, },
], ],
xaxis: { xaxis: {
categories: pollOptions.map((pollOption) => pollOption.label), categories: pollOptions,
}, },
}), [hasUserVoted, pollOptions, userVoteHash]); }), [hasUserVoted, pollOptions, selectedOptionIndex]);
const chartSeries = useMemo(() => [{ const chartSeries = useMemo(() => [{
name: 'votes', name: 'votes',
@ -66,17 +66,14 @@ const PollGraph = (props) => {
PollGraph.defaultProps = { PollGraph.defaultProps = {
hasUserVoted: false, hasUserVoted: false,
userVoteHash: '', selectedOptionIndex: '',
}; };
PollGraph.propTypes = { PollGraph.propTypes = {
pollOptions: PropTypes.arrayOf(PropTypes.exact({ pollOptions: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.string,
hash: PropTypes.string,
})).isRequired,
voteCounts: PropTypes.arrayOf(PropTypes.number).isRequired, voteCounts: PropTypes.arrayOf(PropTypes.number).isRequired,
hasUserVoted: PropTypes.bool, hasUserVoted: PropTypes.bool,
userVoteHash: PropTypes.string, selectedOptionIndex: PropTypes.string,
}; };
export default PollGraph; export default PollGraph;

40
packages/concordia-app/src/components/PollView/PollVote/index.jsx

@ -2,42 +2,46 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Form } from 'semantic-ui-react'; import { Form } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
import { drizzle } from '../../../redux/store';
const { contracts: { [VOTING_CONTRACT]: { methods: { vote } } } } = drizzle;
const PollVote = (props) => { const PollVote = (props) => {
const { const {
pollOptions, enableVoteChanges, hasUserVoted, userVoteHash, topicId, account, pollOptions, enableVoteChanges, hasUserVoted, userVoteIndex,
} = props; } = props;
const [selectedOptionHash, setSelectedOptionHash] = useState(userVoteHash); const [selectedOptionIndex, setSelectedOptionIndex] = useState(userVoteIndex);
const [voting, setVoting] = useState('');
const { t } = useTranslation(); const { t } = useTranslation();
const onOptionSelected = (e, { value }) => { const onOptionSelected = (e, { value }) => {
setSelectedOptionHash(value); setSelectedOptionIndex(value);
}; };
const onCastVote = () => { const onCastVote = () => {
console.log('vote'); setVoting(true);
// TODO vote.cacheSend(...[topicId, selectedOptionIndex + 1], { from: account });
// TODO: callback for immediate poll data refresh on vote?
}; };
return ( return (
<Form onSubmit={onCastVote}> <Form onSubmit={onCastVote}>
<Form.Group grouped> <Form.Group grouped>
<label htmlFor="poll">{t('topic.poll.tab.vote.form.radio.label')}</label> <label htmlFor="poll">{t('topic.poll.tab.vote.form.radio.label')}</label>
{pollOptions.map((pollOption) => ( {pollOptions.map((pollOption, index) => (
<Form.Radio <Form.Radio
key={pollOption.hash} key={pollOption}
label={pollOption.label} label={pollOption}
value={pollOption.hash} value={index}
checked={pollOption.hash === selectedOptionHash} checked={index === selectedOptionIndex}
disabled={hasUserVoted && !enableVoteChanges && pollOption.hash !== selectedOptionHash} disabled={hasUserVoted && !enableVoteChanges && index !== selectedOptionIndex}
onChange={onOptionSelected} onChange={onOptionSelected}
/> />
))} ))}
</Form.Group> </Form.Group>
<Form.Button <Form.Button
type="submit" type="submit"
disabled={(hasUserVoted && !enableVoteChanges) || (selectedOptionHash === userVoteHash)} disabled={voting || (hasUserVoted && !enableVoteChanges) || (selectedOptionIndex === userVoteIndex)}
> >
{t('topic.poll.tab.vote.form.button.submit')} {t('topic.poll.tab.vote.form.button.submit')}
</Form.Button> </Form.Button>
@ -46,17 +50,15 @@ const PollVote = (props) => {
}; };
PollVote.defaultProps = { PollVote.defaultProps = {
userVoteHash: '', userVoteIndex: -1,
}; };
PollVote.propTypes = { PollVote.propTypes = {
pollOptions: PropTypes.arrayOf(PropTypes.exact({ topicId: PropTypes.number.isRequired,
label: PropTypes.string, pollOptions: PropTypes.arrayOf(PropTypes.string).isRequired,
hash: PropTypes.string,
})).isRequired,
enableVoteChanges: PropTypes.bool.isRequired, enableVoteChanges: PropTypes.bool.isRequired,
hasUserVoted: PropTypes.bool.isRequired, hasUserVoted: PropTypes.bool.isRequired,
userVoteHash: PropTypes.string, userVoteIndex: PropTypes.number,
}; };
export default PollVote; export default PollVote;

70
packages/concordia-app/src/components/PollView/index.jsx

@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
import { import {
Container, Grid, Header, Icon, Placeholder, Tab, Container, Header, Icon, Tab,
} from 'semantic-ui-react'; } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -13,7 +13,7 @@ import CustomLoadingTabPane from '../CustomLoadingTabPane';
import { GRAPH_TAB, VOTE_TAB } from '../../constants/polls/PollTabs'; import { GRAPH_TAB, VOTE_TAB } from '../../constants/polls/PollTabs';
import PollVote from './PollVote'; import PollVote from './PollVote';
import { FETCH_USER_DATABASE } from '../../redux/actions/peerDbReplicationActions'; import { FETCH_USER_DATABASE } from '../../redux/actions/peerDbReplicationActions';
import { generatePollHash, generateHash } from '../../utils/hashUtils'; import { generatePollHash } from '../../utils/hashUtils';
import { POLL_OPTIONS, POLL_QUESTION } from '../../constants/orbit/PollsDatabaseKeys'; import { POLL_OPTIONS, POLL_QUESTION } from '../../constants/orbit/PollsDatabaseKeys';
import PollDataInvalid from './PollDataInvalid'; import PollDataInvalid from './PollDataInvalid';
import PollGuestView from './PollGuestView'; import PollGuestView from './PollGuestView';
@ -35,7 +35,8 @@ const PollView = (props) => {
const [voters, setVoters] = useState([]); const [voters, setVoters] = useState([]);
const [pollHashValid, setPollHashValid] = useState(false); const [pollHashValid, setPollHashValid] = useState(false);
const [pollQuestion, setPollQuestion] = useState(''); const [pollQuestion, setPollQuestion] = useState('');
const [loading, setLoading] = useState(true); const [chainDataLoading, setChainDataLoading] = useState(true);
const [orbitDataLoading, setOrbitDataLoading] = useState(true);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
@ -70,6 +71,8 @@ const PollView = (props) => {
.map((subArrayEnd, index) => getPollResults[getPollCallHash].value[5] .map((subArrayEnd, index) => getPollResults[getPollCallHash].value[5]
.slice(index > 0 ? cumulativeSum[index - 1] : 0, .slice(index > 0 ? cumulativeSum[index - 1] : 0,
subArrayEnd))); subArrayEnd)));
setChainDataLoading(false);
} }
}, [getPollCallHash, getPollResults]); }, [getPollCallHash, getPollResults]);
@ -81,15 +84,12 @@ const PollView = (props) => {
if (generatePollHash(pollFound[POLL_QUESTION], pollFound[POLL_OPTIONS]) === pollHash) { if (generatePollHash(pollFound[POLL_QUESTION], pollFound[POLL_OPTIONS]) === pollHash) {
setPollHashValid(true); setPollHashValid(true);
setPollQuestion(pollFound[POLL_QUESTION]); setPollQuestion(pollFound[POLL_QUESTION]);
setPollOptions(pollFound[POLL_OPTIONS].map((pollOption) => ({ setPollOptions([...pollFound[POLL_OPTIONS]]);
label: pollOption,
hash: generateHash(pollOption),
})));
} else { } else {
setPollHashValid(false); setPollHashValid(false);
} }
setLoading(false); setOrbitDataLoading(false);
} }
}, [pollHash, polls, topicId]); }, [pollHash, polls, topicId]);
@ -97,71 +97,77 @@ const PollView = (props) => {
.some((optionVoters) => optionVoters.includes(userAddress)), .some((optionVoters) => optionVoters.includes(userAddress)),
[hasSignedUp, userAddress, voters]); [hasSignedUp, userAddress, voters]);
const userVoteHash = useMemo(() => { const userVoteIndex = useMemo(() => {
if (userHasVoted) { if (!chainDataLoading && !orbitDataLoading && userHasVoted) {
return pollOptions[voters return voters
.findIndex((optionVoters) => optionVoters.includes(userAddress))].hash; .findIndex((optionVoters) => optionVoters.includes(userAddress));
} }
return ''; return -1;
}, [pollOptions, userAddress, userHasVoted, voters]); }, [chainDataLoading, orbitDataLoading, userAddress, userHasVoted, voters]);
const pollVoteTab = useMemo(() => { const pollVoteTab = useMemo(() => {
if (!hasSignedUp) { if (!hasSignedUp) {
return <PollGuestView />; return <PollGuestView />;
} }
if (loading) { if (chainDataLoading || orbitDataLoading) {
return null; return null;
} }
return ( return (
<PollVote <PollVote
topicId={topicId}
pollOptions={pollOptions} pollOptions={pollOptions}
enableVoteChanges={pollChangeVoteEnabled} enableVoteChanges={pollChangeVoteEnabled}
hasUserVoted={userHasVoted} hasUserVoted={userHasVoted}
userVoteHash={userVoteHash} userVoteIndex={userVoteIndex}
/> />
); );
}, [hasSignedUp, loading, pollChangeVoteEnabled, pollOptions, userHasVoted, userVoteHash]); }, [
chainDataLoading, hasSignedUp, orbitDataLoading, pollChangeVoteEnabled, pollOptions, topicId, userHasVoted,
userVoteIndex,
]);
const pollGraphTab = useMemo(() => ( const pollGraphTab = useMemo(() => (
!loading !chainDataLoading || orbitDataLoading
? ( ? (
<PollGraph <PollGraph
pollOptions={pollOptions} pollOptions={pollOptions}
voteCounts={voteCounts} voteCounts={voteCounts}
hasUserVoted={userHasVoted} hasUserVoted={userHasVoted}
userVoteHash={userVoteHash} userVoteIndex={userVoteIndex}
/> />
) )
: null : null
), [loading, pollOptions, userHasVoted, userVoteHash, voteCounts]); ), [chainDataLoading, orbitDataLoading, pollOptions, userHasVoted, userVoteIndex, voteCounts]);
const panes = useMemo(() => { const panes = useMemo(() => {
const pollVotePane = (<CustomLoadingTabPane loading={loading}>{pollVoteTab}</CustomLoadingTabPane>); const pollVotePane = (
const pollGraphPane = (<CustomLoadingTabPane loading={loading}>{pollGraphTab}</CustomLoadingTabPane>); <CustomLoadingTabPane loading={chainDataLoading || orbitDataLoading}>
{pollVoteTab}
</CustomLoadingTabPane>
);
const pollGraphPane = (
<CustomLoadingTabPane loading={chainDataLoading || orbitDataLoading}>
{pollGraphTab}
</CustomLoadingTabPane>
);
return ([ return ([
{ menuItem: t(VOTE_TAB.intl_display_name_id), render: () => pollVotePane }, { menuItem: t(VOTE_TAB.intl_display_name_id), render: () => pollVotePane },
{ menuItem: t(GRAPH_TAB.intl_display_name_id), render: () => pollGraphPane }, { menuItem: t(GRAPH_TAB.intl_display_name_id), render: () => pollGraphPane },
]); ]);
}, [loading, pollGraphTab, pollVoteTab, t]); }, [chainDataLoading, orbitDataLoading, pollGraphTab, pollVoteTab, t]);
return ( return (
<Container id="topic-poll-container" textAlign="left"> <Container id="topic-poll-container" textAlign="left">
{!loading && pollHashValid {!chainDataLoading && !orbitDataLoading && pollHashValid
? ( ? (
<> <>
<Header as="h3"> <Header as="h3">
<Grid> <Icon name="chart pie" size="large" />
<Grid.Column width={1}><Icon name="chart pie" size="large" /></Grid.Column> {pollQuestion}
<Grid.Column width={15}>
{loading
? <Placeholder><Placeholder.Line length="very long" /></Placeholder>
: pollQuestion}
</Grid.Column>
</Grid>
</Header> </Header>
<Tab panes={panes} /> <Tab panes={panes} />
</> </>

Loading…
Cancel
Save