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 {
pollOptions, voteCounts, hasUserVoted, userVoteHash,
pollOptions, voteCounts, hasUserVoted, selectedOptionIndex,
} = props;
const { t } = useTranslation();
@ -22,16 +22,16 @@ const PollGraph = (props) => {
},
colors: [
(value) => {
if (hasUserVoted && pollOptions[value.dataPointIndex].hash === userVoteHash) {
if (hasUserVoted && value.dataPointIndex === selectedOptionIndex) {
return CASTED_OPTION_COLOR;
}
return DEFAULT_OPTION_COLOR;
},
],
xaxis: {
categories: pollOptions.map((pollOption) => pollOption.label),
categories: pollOptions,
},
}), [hasUserVoted, pollOptions, userVoteHash]);
}), [hasUserVoted, pollOptions, selectedOptionIndex]);
const chartSeries = useMemo(() => [{
name: 'votes',
@ -66,17 +66,14 @@ const PollGraph = (props) => {
PollGraph.defaultProps = {
hasUserVoted: false,
userVoteHash: '',
selectedOptionIndex: '',
};
PollGraph.propTypes = {
pollOptions: PropTypes.arrayOf(PropTypes.exact({
label: PropTypes.string,
hash: PropTypes.string,
})).isRequired,
pollOptions: PropTypes.arrayOf(PropTypes.string).isRequired,
voteCounts: PropTypes.arrayOf(PropTypes.number).isRequired,
hasUserVoted: PropTypes.bool,
userVoteHash: PropTypes.string,
selectedOptionIndex: PropTypes.string,
};
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 { Form } from 'semantic-ui-react';
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 {
pollOptions, enableVoteChanges, hasUserVoted, userVoteHash,
topicId, account, pollOptions, enableVoteChanges, hasUserVoted, userVoteIndex,
} = props;
const [selectedOptionHash, setSelectedOptionHash] = useState(userVoteHash);
const [selectedOptionIndex, setSelectedOptionIndex] = useState(userVoteIndex);
const [voting, setVoting] = useState('');
const { t } = useTranslation();
const onOptionSelected = (e, { value }) => {
setSelectedOptionHash(value);
setSelectedOptionIndex(value);
};
const onCastVote = () => {
console.log('vote');
// TODO
// TODO: callback for immediate poll data refresh on vote?
setVoting(true);
vote.cacheSend(...[topicId, selectedOptionIndex + 1], { from: account });
};
return (
<Form onSubmit={onCastVote}>
<Form.Group grouped>
<label htmlFor="poll">{t('topic.poll.tab.vote.form.radio.label')}</label>
{pollOptions.map((pollOption) => (
{pollOptions.map((pollOption, index) => (
<Form.Radio
key={pollOption.hash}
label={pollOption.label}
value={pollOption.hash}
checked={pollOption.hash === selectedOptionHash}
disabled={hasUserVoted && !enableVoteChanges && pollOption.hash !== selectedOptionHash}
key={pollOption}
label={pollOption}
value={index}
checked={index === selectedOptionIndex}
disabled={hasUserVoted && !enableVoteChanges && index !== selectedOptionIndex}
onChange={onOptionSelected}
/>
))}
</Form.Group>
<Form.Button
type="submit"
disabled={(hasUserVoted && !enableVoteChanges) || (selectedOptionHash === userVoteHash)}
disabled={voting || (hasUserVoted && !enableVoteChanges) || (selectedOptionIndex === userVoteIndex)}
>
{t('topic.poll.tab.vote.form.button.submit')}
</Form.Button>
@ -46,17 +50,15 @@ const PollVote = (props) => {
};
PollVote.defaultProps = {
userVoteHash: '',
userVoteIndex: -1,
};
PollVote.propTypes = {
pollOptions: PropTypes.arrayOf(PropTypes.exact({
label: PropTypes.string,
hash: PropTypes.string,
})).isRequired,
topicId: PropTypes.number.isRequired,
pollOptions: PropTypes.arrayOf(PropTypes.string).isRequired,
enableVoteChanges: PropTypes.bool.isRequired,
hasUserVoted: PropTypes.bool.isRequired,
userVoteHash: PropTypes.string,
userVoteIndex: PropTypes.number,
};
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 { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
import {
Container, Grid, Header, Icon, Placeholder, Tab,
Container, Header, Icon, Tab,
} from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
@ -13,7 +13,7 @@ import CustomLoadingTabPane from '../CustomLoadingTabPane';
import { GRAPH_TAB, VOTE_TAB } from '../../constants/polls/PollTabs';
import PollVote from './PollVote';
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 PollDataInvalid from './PollDataInvalid';
import PollGuestView from './PollGuestView';
@ -35,7 +35,8 @@ const PollView = (props) => {
const [voters, setVoters] = useState([]);
const [pollHashValid, setPollHashValid] = useState(false);
const [pollQuestion, setPollQuestion] = useState('');
const [loading, setLoading] = useState(true);
const [chainDataLoading, setChainDataLoading] = useState(true);
const [orbitDataLoading, setOrbitDataLoading] = useState(true);
const dispatch = useDispatch();
const { t } = useTranslation();
@ -70,6 +71,8 @@ const PollView = (props) => {
.map((subArrayEnd, index) => getPollResults[getPollCallHash].value[5]
.slice(index > 0 ? cumulativeSum[index - 1] : 0,
subArrayEnd)));
setChainDataLoading(false);
}
}, [getPollCallHash, getPollResults]);
@ -81,15 +84,12 @@ const PollView = (props) => {
if (generatePollHash(pollFound[POLL_QUESTION], pollFound[POLL_OPTIONS]) === pollHash) {
setPollHashValid(true);
setPollQuestion(pollFound[POLL_QUESTION]);
setPollOptions(pollFound[POLL_OPTIONS].map((pollOption) => ({
label: pollOption,
hash: generateHash(pollOption),
})));
setPollOptions([...pollFound[POLL_OPTIONS]]);
} else {
setPollHashValid(false);
}
setLoading(false);
setOrbitDataLoading(false);
}
}, [pollHash, polls, topicId]);
@ -97,71 +97,77 @@ const PollView = (props) => {
.some((optionVoters) => optionVoters.includes(userAddress)),
[hasSignedUp, userAddress, voters]);
const userVoteHash = useMemo(() => {
if (userHasVoted) {
return pollOptions[voters
.findIndex((optionVoters) => optionVoters.includes(userAddress))].hash;
const userVoteIndex = useMemo(() => {
if (!chainDataLoading && !orbitDataLoading && userHasVoted) {
return voters
.findIndex((optionVoters) => optionVoters.includes(userAddress));
}
return '';
}, [pollOptions, userAddress, userHasVoted, voters]);
return -1;
}, [chainDataLoading, orbitDataLoading, userAddress, userHasVoted, voters]);
const pollVoteTab = useMemo(() => {
if (!hasSignedUp) {
return <PollGuestView />;
}
if (loading) {
if (chainDataLoading || orbitDataLoading) {
return null;
}
return (
<PollVote
topicId={topicId}
pollOptions={pollOptions}
enableVoteChanges={pollChangeVoteEnabled}
hasUserVoted={userHasVoted}
userVoteHash={userVoteHash}
userVoteIndex={userVoteIndex}
/>
);
}, [hasSignedUp, loading, pollChangeVoteEnabled, pollOptions, userHasVoted, userVoteHash]);
}, [
chainDataLoading, hasSignedUp, orbitDataLoading, pollChangeVoteEnabled, pollOptions, topicId, userHasVoted,
userVoteIndex,
]);
const pollGraphTab = useMemo(() => (
!loading
!chainDataLoading || orbitDataLoading
? (
<PollGraph
pollOptions={pollOptions}
voteCounts={voteCounts}
hasUserVoted={userHasVoted}
userVoteHash={userVoteHash}
userVoteIndex={userVoteIndex}
/>
)
: null
), [loading, pollOptions, userHasVoted, userVoteHash, voteCounts]);
), [chainDataLoading, orbitDataLoading, pollOptions, userHasVoted, userVoteIndex, voteCounts]);
const panes = useMemo(() => {
const pollVotePane = (<CustomLoadingTabPane loading={loading}>{pollVoteTab}</CustomLoadingTabPane>);
const pollGraphPane = (<CustomLoadingTabPane loading={loading}>{pollGraphTab}</CustomLoadingTabPane>);
const pollVotePane = (
<CustomLoadingTabPane loading={chainDataLoading || orbitDataLoading}>
{pollVoteTab}
</CustomLoadingTabPane>
);
const pollGraphPane = (
<CustomLoadingTabPane loading={chainDataLoading || orbitDataLoading}>
{pollGraphTab}
</CustomLoadingTabPane>
);
return ([
{ menuItem: t(VOTE_TAB.intl_display_name_id), render: () => pollVotePane },
{ menuItem: t(GRAPH_TAB.intl_display_name_id), render: () => pollGraphPane },
]);
}, [loading, pollGraphTab, pollVoteTab, t]);
}, [chainDataLoading, orbitDataLoading, pollGraphTab, pollVoteTab, t]);
return (
<Container id="topic-poll-container" textAlign="left">
{!loading && pollHashValid
{!chainDataLoading && !orbitDataLoading && pollHashValid
? (
<>
<Header as="h3">
<Grid>
<Grid.Column width={1}><Icon name="chart pie" size="large" /></Grid.Column>
<Grid.Column width={15}>
{loading
? <Placeholder><Placeholder.Line length="very long" /></Placeholder>
: pollQuestion}
</Grid.Column>
</Grid>
<Icon name="chart pie" size="large" />
{pollQuestion}
</Header>
<Tab panes={panes} />
</>

Loading…
Cancel
Save