Browse Source

Polls creation init

develop
Ezerous 4 years ago
parent
commit
66e4716a07
  1. 1
      packages/concordia-app/package.json
  2. 9
      packages/concordia-app/public/locales/en/translation.json
  3. 50
      packages/concordia-app/src/components/PollCreate/PollOption/index.jsx
  4. 4
      packages/concordia-app/src/components/PollCreate/PollOption/styles.css
  5. 190
      packages/concordia-app/src/components/PollCreate/index.jsx
  6. 10
      packages/concordia-app/src/components/PollCreate/styles.css
  7. 6
      packages/concordia-app/src/components/PostList/PostVoting/index.jsx
  8. 9
      packages/concordia-app/src/constants/orbit/PollsDatabaseKeys.js
  9. 8
      packages/concordia-app/src/redux/actions/contractEventActions.js
  10. 7
      packages/concordia-app/src/utils/hashUtils.js
  11. 12
      packages/concordia-app/src/views/Profile/GeneralTab/index.jsx
  12. 81
      packages/concordia-app/src/views/Topic/TopicCreate/index.jsx
  13. 2
      packages/concordia-app/src/views/Topic/TopicCreate/styles.css
  14. 7
      packages/concordia-pinner/src/utils/orbitUtils.js
  15. 13
      packages/concordia-shared/src/constants/contracts/events/VotingContractEvents.js
  16. 4
      packages/concordia-shared/src/constants/contracts/events/index.js
  17. 9
      packages/concordia-shared/src/constants/orbit/OrbitDatabases.js
  18. 5
      yarn.lock

1
packages/concordia-app/package.json

@ -31,6 +31,7 @@
"@welldone-software/why-did-you-render": "~6.0.5", "@welldone-software/why-did-you-render": "~6.0.5",
"concordia-contracts": "~0.1.0", "concordia-contracts": "~0.1.0",
"concordia-shared": "~0.1.0", "concordia-shared": "~0.1.0",
"crypto-js": "~4.0.0",
"i18next": "^19.8.3", "i18next": "^19.8.3",
"i18next-browser-languagedetector": "^6.0.1", "i18next-browser-languagedetector": "^6.0.1",
"i18next-http-backend": "^1.0.21", "i18next-http-backend": "^1.0.21",

9
packages/concordia-app/public/locales/en/translation.json

@ -22,6 +22,12 @@
"edit.information.modal.form.profile.picture.field.placeholder": "URL", "edit.information.modal.form.profile.picture.field.placeholder": "URL",
"edit.information.modal.form.submit.button": "Submit", "edit.information.modal.form.submit.button": "Submit",
"edit.information.modal.title": "Edit profile information", "edit.information.modal.title": "Edit profile information",
"poll.create.question.field.label": "Poll Question",
"poll.create.question.field.placeholder": "Question",
"poll.create.allow.vote.changes.field.label": "Allow vote changes",
"poll.create.option.field.label": "Option #{{id}}",
"poll.create.option.field.placeholder": "Option #{{id}}",
"poll.create.add.option.button": "Add Option",
"post.create.form.send.button": "Post", "post.create.form.send.button": "Post",
"post.form.content.field.placeholder": "Message", "post.form.content.field.placeholder": "Message",
"post.form.subject.field.placeholder": "Subject", "post.form.subject.field.placeholder": "Subject",
@ -34,6 +40,7 @@
"profile.general.tab.number.of.posts.row.title": "Number of posts:", "profile.general.tab.number.of.posts.row.title": "Number of posts:",
"profile.general.tab.number.of.topics.row.title": "Number of topics created:", "profile.general.tab.number.of.topics.row.title": "Number of topics created:",
"profile.general.tab.posts.db.address.row.title": "PostsDB:", "profile.general.tab.posts.db.address.row.title": "PostsDB:",
"profile.general.tab.polls.db.address.row.title": "PollsDB:",
"profile.general.tab.registration.date.row.title": "Member since:", "profile.general.tab.registration.date.row.title": "Member since:",
"profile.general.tab.save.info.button.title": "Save information", "profile.general.tab.save.info.button.title": "Save information",
"profile.general.tab.title": "General", "profile.general.tab.title": "General",
@ -73,6 +80,8 @@
"topic.create.form.post.button": "Create Topic", "topic.create.form.post.button": "Create Topic",
"topic.create.form.subject.field.label": "Topic subject", "topic.create.form.subject.field.label": "Topic subject",
"topic.create.form.subject.field.placeholder": "Subject", "topic.create.form.subject.field.placeholder": "Subject",
"topic.create.form.add.poll.button": "Add Poll",
"topic.create.form.remove.poll.button": "Remove Poll",
"topic.list.row.topic.id": "#{{id}}", "topic.list.row.topic.id": "#{{id}}",
"username.selector.error.username.empty.message": "Username is required", "username.selector.error.username.empty.message": "Username is required",
"username.selector.error.username.taken.message": "The username {{username}} is already taken.", "username.selector.error.username.taken.message": "The username {{username}} is already taken.",

50
packages/concordia-app/src/components/PollCreate/PollOption/index.jsx

@ -0,0 +1,50 @@
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import {
Button, Form, Icon, Input,
} from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
import './styles.css';
const PollOption = (props) => {
const {
id, removable, onChange, onRemove,
} = props;
const { t } = useTranslation();
return (
<Form.Field className="form-poll-option" required>
<label className="form-topic-create-header" htmlFor="form-poll-create-field-subject">
{t('poll.create.option.field.label', { id })}
</label>
<Input
id="form-poll-create-field-subject"
placeholder={t('poll.create.option.field.placeholder', { id })}
name="pollQuestionInput"
className="form-input"
onChange={(e) => onChange(e, id)}
/>
{removable
&& (
<Button
className="remove-option-button"
key={`form-remove-option-button-${id}`}
negative
icon
onClick={(e) => onRemove(e, id)}
>
<Icon name="x" />
</Button>
)}
</Form.Field>
);
};
PollOption.propTypes = {
id: PropTypes.number.isRequired,
onChange: PropTypes.func,
onRemove: PropTypes.func,
removable: PropTypes.bool,
};
export default memo(PollOption);

4
packages/concordia-app/src/components/PollCreate/PollOption/styles.css

@ -0,0 +1,4 @@
.form-poll-option > .form-input{
width: 50% !important;
margin-right: 0.25em;
}

190
packages/concordia-app/src/components/PollCreate/index.jsx

@ -0,0 +1,190 @@
import React, {
useMemo, useState, useCallback, useEffect, forwardRef, useImperativeHandle,
} from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import {
Button, Checkbox, Form, Icon, Input,
} from 'semantic-ui-react';
import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
import { POLL_CREATED_EVENT } from 'concordia-shared/src/constants/contracts/events/VotingContractEvents';
import { POLLS_DATABASE } from 'concordia-shared/src/constants/orbit/OrbitDatabases';
import PollOption from './PollOption';
import { breeze, drizzle } from '../../redux/store';
import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../constants/TransactionStatus';
import './styles.css';
import { POLL_OPTIONS, POLL_QUESTION } from '../../constants/orbit/PollsDatabaseKeys';
import generateHash from '../../utils/hashUtils';
const { contracts: { [VOTING_CONTRACT]: { methods: { createPoll } } } } = drizzle;
const { orbit: { stores } } = breeze;
const PollCreate = forwardRef((props, ref) => {
const { account, onChange, onCreated } = props;
const transactionStack = useSelector((state) => state.transactionStack);
const transactions = useSelector((state) => state.transactions);
const [createPollCacheSendStackId, setCreatePollCacheSendStackId] = useState('');
const [question, setQuestion] = useState('');
const [options, setOptions] = useState(
[{ id: 1 }, { id: 2 }],
);
const [optionValues, setOptionValues] = useState(
['', ''],
);
const [optionsNextId, setOptionsNextId] = useState(3);
const [allowVoteChanges, setAllowVoteChanges] = useState(false);
const [creating, setCreating] = useState(false);
const [errored, setErrored] = useState(false);
const { t } = useTranslation();
const handlePollQuestionChange = useCallback((event) => {
const newQuestion = event.target.value.trim();
if (newQuestion !== question) setQuestion(newQuestion);
}, [question]);
const addOption = useCallback((e) => {
e.currentTarget.blur();
const newOptions = [...options, { id: optionsNextId, removable: true }];
const newOptionValues = [...optionValues, ''];
setOptionsNextId(optionsNextId + 1);
setOptions(newOptions);
setOptionValues(newOptionValues);
}, [optionValues, options, optionsNextId]);
const removeOption = useCallback((e, id) => {
e.currentTarget.blur();
const newOptions = [...options];
newOptions.splice(id - 1, 1);
const newOptionValues = [...optionValues];
newOptionValues.splice(id - 1, 1);
setOptions(newOptions);
setOptionValues(newOptionValues);
}, [optionValues, options]);
const handlePollOptionChange = useCallback((event, id) => {
const newValue = event.target.value.trim();
if (newValue !== optionValues[id - 1]) {
const newOptionValues = [...optionValues];
newOptionValues[id - 1] = newValue;
setOptionValues(newOptionValues);
}
}, [optionValues]);
const pollOptions = useMemo(() => options
.map((option, index) => {
const { id, removable } = option;
return (
<PollOption
id={index + 1}
key={id}
removable={removable}
onRemove={removeOption}
onChange={handlePollOptionChange}
/>
);
}), [handlePollOptionChange, options, removeOption]);
useEffect(() => {
onChange({ question, optionValues });
}, [onChange, optionValues, question]);
const handleCheckboxChange = useCallback((event, data) => {
setAllowVoteChanges(data.checked);
}, []);
useImperativeHandle(ref, () => ({
createPoll(topicId) {
setCreating(true);
const dataHash = generateHash(JSON.stringify({ question, optionValues }));
setCreatePollCacheSendStackId(createPoll.cacheSend(
...[topicId, options.length, dataHash, allowVoteChanges], { from: account },
));
},
pollCreating() {
return creating;
},
pollErrored() {
return errored;
},
}));
useEffect(() => {
if (creating && transactionStack && transactionStack[createPollCacheSendStackId]
&& transactions[transactionStack[createPollCacheSendStackId]]) {
if (transactions[transactionStack[createPollCacheSendStackId]].status === TRANSACTION_ERROR) {
setErrored(true);
setCreating(false);
onCreated(false);
} else if (transactions[transactionStack[createPollCacheSendStackId]].status === TRANSACTION_SUCCESS) {
const {
receipt: {
events: {
[POLL_CREATED_EVENT]: {
returnValues: {
topicID: topicId,
},
},
},
},
} = transactions[transactionStack[createPollCacheSendStackId]];
const pollsDb = Object.values(stores).find((store) => store.dbname === POLLS_DATABASE);
pollsDb
.put(topicId, { [POLL_QUESTION]: question, [POLL_OPTIONS]: optionValues })
.then(() => {
onCreated(topicId);
})
.catch((reason) => {
console.error(reason);
setErrored(true);
setCreating(false);
onCreated(false);
});
}
}
}, [createPollCacheSendStackId, creating, onCreated, optionValues, question, transactionStack, transactions]);
return useMemo(() => (
<div className="poll-create">
<Form.Field required>
<label className="form-topic-create-header" htmlFor="form-poll-create-field-subject">
{t('poll.create.question.field.label')}
</label>
<Input
id="form-poll-create-field-subject"
placeholder={t('poll.create.question.field.placeholder')}
name="pollQuestionInput"
className="form-input"
onChange={handlePollQuestionChange}
/>
</Form.Field>
<Form.Field>
<Checkbox
label={t('poll.create.allow.vote.changes.field.label')}
onClick={handleCheckboxChange}
/>
</Form.Field>
{pollOptions}
<Button
id="add-option-button"
key="form-add-option-button"
positive
icon
labelPosition="left"
onClick={addOption}
>
<Icon name="plus" />
{t('poll.create.add.option.button')}
</Button>
</div>
), [addOption, handleCheckboxChange, handlePollQuestionChange, pollOptions, t]);
});
PollCreate.propTypes = {
onChange: PropTypes.func,
onCreated: PropTypes.func,
};
export default PollCreate;

10
packages/concordia-app/src/components/PollCreate/styles.css

@ -0,0 +1,10 @@
.poll-create {
padding-top: 1em;
padding-bottom: 1em;
}
.poll-create .checkbox > label, label:focus, label:hover{
color: white !important;
font-weight: 700;
}

6
packages/concordia-app/src/components/PostList/PostVoting/index.jsx

@ -77,9 +77,9 @@ const PostVoting = (props) => {
if (voting) return; if (voting) return;
setVoting(true); setVoting(true);
if ((ownVote === CHOICE_DEFAULT || ownVote === CHOICE_DOWN) && choice === CHOICE_UP) setVoteCacheSendStackId(upvote.cacheSend(...[postId], { from: userAccount })); if ((ownVote === CHOICE_DEFAULT || ownVote === CHOICE_DOWN) && choice === CHOICE_UP) setVoteCacheSendStackId(upvote.cacheSend(postId, { from: userAccount }));
else if ((ownVote === CHOICE_DEFAULT || ownVote === CHOICE_UP) && choice === CHOICE_DOWN) setVoteCacheSendStackId(downvote.cacheSend(...[postId], { from: userAccount })); else if ((ownVote === CHOICE_DEFAULT || ownVote === CHOICE_UP) && choice === CHOICE_DOWN) setVoteCacheSendStackId(downvote.cacheSend(postId, { from: userAccount }));
else if ((ownVote === CHOICE_UP && choice === CHOICE_UP) || (ownVote === CHOICE_DOWN && choice === CHOICE_DOWN)) setVoteCacheSendStackId(unvote.cacheSend(...[postId], { from: userAccount })); else if ((ownVote === CHOICE_UP && choice === CHOICE_UP) || (ownVote === CHOICE_DOWN && choice === CHOICE_DOWN)) setVoteCacheSendStackId(unvote.cacheSend(postId, { from: userAccount }));
else setVoting(false); else setVoting(false);
}, [ownVote, postId, userAccount, voting]); }, [ownVote, postId, userAccount, voting]);

9
packages/concordia-app/src/constants/orbit/PollsDatabaseKeys.js

@ -0,0 +1,9 @@
export const POLL_QUESTION = 'question';
export const POLL_OPTIONS = 'options';
const pollsDatabaseKeys = [
POLL_QUESTION,
POLL_OPTIONS,
];
export default pollsDatabaseKeys;

8
packages/concordia-app/src/redux/actions/contractEventActions.js

@ -7,6 +7,9 @@ import {
import { import {
USER_VOTED_POST_EVENT, USER_VOTED_POST_EVENT,
} from 'concordia-shared/src/constants/contracts/events/PostVotingContractEvents'; } from 'concordia-shared/src/constants/contracts/events/PostVotingContractEvents';
import {
POLL_CREATED_EVENT, USER_VOTED_POLL_EVENT
} from 'concordia-shared/src/constants/contracts/events/VotingContractEvents';
export const FORUM_EVENT_USER_SIGNED_UP = 'FORUM_EVENT_USER_SIGNED_UP'; export const FORUM_EVENT_USER_SIGNED_UP = 'FORUM_EVENT_USER_SIGNED_UP';
export const FORUM_EVENT_USERNAME_UPDATED = 'FORUM_EVENT_USERNAME_UPDATED'; export const FORUM_EVENT_USERNAME_UPDATED = 'FORUM_EVENT_USERNAME_UPDATED';
@ -15,12 +18,17 @@ export const FORUM_EVENT_POST_CREATED = 'FORUM_EVENT_POST_CREATED';
export const POST_VOTING_USER_VOTED_POST = 'POST_VOTING_USER_VOTED_POST'; export const POST_VOTING_USER_VOTED_POST = 'POST_VOTING_USER_VOTED_POST';
export const VOTING_POLL_CREATED = 'VOTING_POLL_CREATED';
export const VOTING_USER_VOTED_POLL = 'VOTING_USER_VOTED_POLL';
const eventActionMap = { const eventActionMap = {
[USER_SIGNED_UP_EVENT]: FORUM_EVENT_USER_SIGNED_UP, [USER_SIGNED_UP_EVENT]: FORUM_EVENT_USER_SIGNED_UP,
[USERNAME_UPDATED_EVENT]: FORUM_EVENT_USERNAME_UPDATED, [USERNAME_UPDATED_EVENT]: FORUM_EVENT_USERNAME_UPDATED,
[TOPIC_CREATED_EVENT]: FORUM_EVENT_TOPIC_CREATED, [TOPIC_CREATED_EVENT]: FORUM_EVENT_TOPIC_CREATED,
[POST_CREATED_EVENT]: FORUM_EVENT_POST_CREATED, [POST_CREATED_EVENT]: FORUM_EVENT_POST_CREATED,
[USER_VOTED_POST_EVENT]: POST_VOTING_USER_VOTED_POST, [USER_VOTED_POST_EVENT]: POST_VOTING_USER_VOTED_POST,
[POLL_CREATED_EVENT]: VOTING_POLL_CREATED,
[USER_VOTED_POLL_EVENT]: VOTING_USER_VOTED_POLL,
}; };
export default eventActionMap; export default eventActionMap;

7
packages/concordia-app/src/utils/hashUtils.js

@ -0,0 +1,7 @@
import sha256 from 'crypto-js/sha256';
function generateHash(message) {
return sha256(message).toString().substring(0, 16);
}
export default generateHash;

12
packages/concordia-app/src/views/Profile/GeneralTab/index.jsx

@ -23,6 +23,7 @@ const GeneralTab = (props) => {
const [userInfoOrbitAddress, setUserInfoOrbitAddress] = useState(null); const [userInfoOrbitAddress, setUserInfoOrbitAddress] = useState(null);
const [userTopicsOrbitAddress, setUserTopicsOrbitAddress] = useState(null); const [userTopicsOrbitAddress, setUserTopicsOrbitAddress] = useState(null);
const [userPostsOrbitAddress, setUserPostsOrbitAddress] = useState(null); const [userPostsOrbitAddress, setUserPostsOrbitAddress] = useState(null);
const [userPollsOrbitAddress, setUserPollsOrbitAddress] = useState(null);
const [profileMetadataFetched, setProfileMetadataFetched] = useState(false); const [profileMetadataFetched, setProfileMetadataFetched] = useState(false);
const [userAvatarUrl, setUserAvatarUrl] = useState(null); const [userAvatarUrl, setUserAvatarUrl] = useState(null);
const [userLocation, setUserLocation] = useState(null); const [userLocation, setUserLocation] = useState(null);
@ -41,10 +42,11 @@ const GeneralTab = (props) => {
userAddress: profileAddress, userAddress: profileAddress,
}))) })))
.then((values) => { .then((values) => {
const [userOrbitAddress, topicsOrbitAddress, postsOrbitAddress] = values; const [userOrbitAddress, topicsOrbitAddress, postsOrbitAddress, pollsOrbitAddress] = values;
setUserInfoOrbitAddress(userOrbitAddress); setUserInfoOrbitAddress(userOrbitAddress);
setUserTopicsOrbitAddress(topicsOrbitAddress); setUserTopicsOrbitAddress(topicsOrbitAddress);
setUserPostsOrbitAddress(postsOrbitAddress); setUserPostsOrbitAddress(postsOrbitAddress);
setUserPollsOrbitAddress(pollsOrbitAddress);
const userFound = users const userFound = users
.find((user) => user.id === userOrbitAddress); .find((user) => user.id === userOrbitAddress);
@ -141,6 +143,12 @@ const GeneralTab = (props) => {
{userPostsOrbitAddress || (<Placeholder><Placeholder.Line /></Placeholder>)} {userPostsOrbitAddress || (<Placeholder><Placeholder.Line /></Placeholder>)}
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
<Table.Row>
<Table.Cell><strong>{t('profile.general.tab.polls.db.address.row.title')}</strong></Table.Cell>
<Table.Cell>
{userPollsOrbitAddress || (<Placeholder><Placeholder.Line /></Placeholder>)}
</Table.Cell>
</Table.Row>
<Table.Row> <Table.Row>
<Table.Cell><strong>{t('profile.general.tab.number.of.topics.row.title')}</strong></Table.Cell> <Table.Cell><strong>{t('profile.general.tab.number.of.topics.row.title')}</strong></Table.Cell>
<Table.Cell> <Table.Cell>
@ -192,7 +200,7 @@ const GeneralTab = (props) => {
</Table> </Table>
{isSelf && editInformationModal} {isSelf && editInformationModal}
</> </>
), [editInformationModal, isSelf, numberOfPosts, numberOfTopics, profileAddress, profileMetadataFetched, t, userAvatarUrl, userInfoOrbitAddress, userLocationCell, userPostsOrbitAddress, userRegistrationTimestamp, userTopicsOrbitAddress, username]); ), [editInformationModal, isSelf, numberOfPosts, numberOfTopics, profileAddress, profileMetadataFetched, t, userAvatarUrl, userInfoOrbitAddress, userLocationCell, userPollsOrbitAddress, userPostsOrbitAddress, userRegistrationTimestamp, userTopicsOrbitAddress, username]);
}; };
GeneralTab.defaultProps = { GeneralTab.defaultProps = {

81
packages/concordia-app/src/views/Topic/TopicCreate/index.jsx

@ -1,5 +1,5 @@
import React, { import React, {
useCallback, useEffect, useState, useCallback, useEffect, useRef, useState,
} from 'react'; } from 'react';
import { import {
Button, Container, Form, Header, Icon, Input, TextArea, Button, Container, Form, Header, Icon, Input, TextArea,
@ -15,6 +15,7 @@ import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../../constants/Trans
import { TOPIC_SUBJECT } from '../../../constants/orbit/TopicsDatabaseKeys'; import { TOPIC_SUBJECT } from '../../../constants/orbit/TopicsDatabaseKeys';
import { POST_CONTENT } from '../../../constants/orbit/PostsDatabaseKeys'; import { POST_CONTENT } from '../../../constants/orbit/PostsDatabaseKeys';
import './styles.css'; import './styles.css';
import PollCreate from '../../../components/PollCreate';
const { contracts: { [FORUM_CONTRACT]: { methods: { createTopic } } } } = drizzle; const { contracts: { [FORUM_CONTRACT]: { methods: { createTopic } } } } = drizzle;
const { orbit: { stores } } = breeze; const { orbit: { stores } } = breeze;
@ -25,9 +26,12 @@ const TopicCreate = (props) => {
const transactions = useSelector((state) => state.transactions); const transactions = useSelector((state) => state.transactions);
const [subjectInput, setSubjectInput] = useState(''); const [subjectInput, setSubjectInput] = useState('');
const [contentInput, setContentInput] = useState(''); const [contentInput, setContentInput] = useState('');
const [newTopicId, setNewTopicId] = useState(null);
const [createTopicCacheSendStackId, setCreateTopicCacheSendStackId] = useState(''); const [createTopicCacheSendStackId, setCreateTopicCacheSendStackId] = useState('');
const [posting, setPosting] = useState(false); const [posting, setPosting] = useState(false);
const [pollAdded, setPollAdded] = useState(false);
const [pollFilled, setPollFilled] = useState(false);
const pollCreateRef = useRef();
const history = useHistory(); const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
@ -48,9 +52,18 @@ const TopicCreate = (props) => {
} }
}, [posting]); }, [posting]);
const goToTopic = useCallback((topicId) => {
if (topicId) history.push(`/topics/${topicId}`);
else {
console.error('Error creating poll!');
history.push(`/topics/${newTopicId}`);
}
}, [history, newTopicId]);
useEffect(() => { useEffect(() => {
if (posting && transactionStack && transactionStack[createTopicCacheSendStackId] if (posting && transactionStack && transactionStack[createTopicCacheSendStackId]
&& transactions[transactionStack[createTopicCacheSendStackId]]) { && transactions[transactionStack[createTopicCacheSendStackId]]
&& (!pollAdded || (!pollCreateRef.current.pollCreating() && !pollCreateRef.current.pollErrored()))) {
if (transactions[transactionStack[createTopicCacheSendStackId]].status === TRANSACTION_ERROR) { if (transactions[transactionStack[createTopicCacheSendStackId]].status === TRANSACTION_ERROR) {
setPosting(false); setPosting(false);
} else if (transactions[transactionStack[createTopicCacheSendStackId]].status === TRANSACTION_SUCCESS) { } else if (transactions[transactionStack[createTopicCacheSendStackId]].status === TRANSACTION_SUCCESS) {
@ -67,6 +80,12 @@ const TopicCreate = (props) => {
}, },
} = transactions[transactionStack[createTopicCacheSendStackId]]; } = transactions[transactionStack[createTopicCacheSendStackId]];
setNewTopicId(topicId);
if (pollAdded) {
pollCreateRef.current.createPoll(topicId);
}
const topicsDb = Object.values(stores).find((store) => store.dbname === TOPICS_DATABASE); const topicsDb = Object.values(stores).find((store) => store.dbname === TOPICS_DATABASE);
const postsDb = Object.values(stores).find((store) => store.dbname === POSTS_DATABASE); const postsDb = Object.values(stores).find((store) => store.dbname === POSTS_DATABASE);
@ -77,14 +96,15 @@ const TopicCreate = (props) => {
[POST_CONTENT]: contentInput, [POST_CONTENT]: contentInput,
})) }))
.then(() => { .then(() => {
history.push(`/topics/${topicId}`); if (!pollAdded) goToTopic();
}) })
.catch((reason) => { .catch((reason) => {
console.error(reason); console.error(reason);
}); });
} }
} }
}, [createTopicCacheSendStackId, history, contentInput, posting, subjectInput, transactionStack, transactions]); }, [createTopicCacheSendStackId,
contentInput, posting, subjectInput, transactionStack, transactions, pollAdded, goToTopic]);
const validateAndPost = useCallback(() => { const validateAndPost = useCallback(() => {
if (subjectInput === '' || contentInput === '') { if (subjectInput === '' || contentInput === '') {
@ -95,8 +115,24 @@ const TopicCreate = (props) => {
setCreateTopicCacheSendStackId(createTopic.cacheSend(...[], { from: account })); setCreateTopicCacheSendStackId(createTopic.cacheSend(...[], { from: account }));
}, [account, contentInput, subjectInput]); }, [account, contentInput, subjectInput]);
const togglePollCreate = useCallback((e) => {
e.currentTarget.blur();
if (!pollAdded) {
setPollAdded(true);
} else {
setPollAdded(false);
}
}, [pollAdded]);
const handlePollCreateChanges = useCallback((pollCreateState) => {
const { question, optionValues } = pollCreateState;
if (question !== '' && !optionValues.includes('')) {
setPollFilled(true);
} else setPollFilled(false);
}, []);
return ( return (
<Container> <Container id="form-topic-create">
<Header id="new-topic-header" as="h2">New Topic</Header> <Header id="new-topic-header" as="h2">New Topic</Header>
<Form loading={posting}> <Form loading={posting}>
<Form.Field required> <Form.Field required>
@ -126,13 +162,43 @@ const TopicCreate = (props) => {
onChange={handleSubjectInputChange} onChange={handleSubjectInputChange}
/> />
</Form.Field> </Form.Field>
<Button
id="toggle-poll-button"
key="form-poll-button"
positive={!pollAdded}
negative={pollAdded}
icon
labelPosition="left"
onClick={togglePollCreate}
>
{!pollAdded ? (
<>
<Icon name="plus" />
{t('topic.create.form.add.poll.button')}
</>
)
: (
<>
<Icon name="x" />
{t('topic.create.form.remove.poll.button')}
</>
)}
</Button>
{pollAdded && (
<PollCreate
ref={pollCreateRef}
onChange={handlePollCreateChanges}
onCreated={goToTopic}
account={account}
/>
)}
<Button <Button
id="create-topic-button" id="create-topic-button"
animated animated
key="form-topic-create-button-submit" key="form-topic-create-button-submit"
type="button" type="button"
className="primary-button" className="primary-button"
disabled={posting || subjectInput === '' || contentInput === ''} disabled={posting || subjectInput === '' || contentInput === '' || (pollAdded && !pollFilled)}
onClick={validateAndPost} onClick={validateAndPost}
> >
<Button.Content visible> <Button.Content visible>
@ -142,7 +208,6 @@ const TopicCreate = (props) => {
<Icon name="send" /> <Icon name="send" />
</Button.Content> </Button.Content>
</Button> </Button>
</Form> </Form>
</Container> </Container>
); );

2
packages/concordia-app/src/views/Topic/TopicCreate/styles.css

@ -17,5 +17,5 @@
#create-topic-button { #create-topic-button {
float: right; float: right;
margin: 1rem 0 4rem 0; margin: 0 0 4rem 0;
} }

7
packages/concordia-pinner/src/utils/orbitUtils.js

@ -2,6 +2,7 @@ import OrbitDB from 'orbit-db';
import Identities from 'orbit-db-identity-provider'; import Identities from 'orbit-db-identity-provider';
import { EthereumContractIdentityProvider } from '@ezerous/eth-identity-provider'; import { EthereumContractIdentityProvider } from '@ezerous/eth-identity-provider';
import Web3 from 'web3'; import Web3 from 'web3';
import { databaseNames } from 'concordia-shared/src/constants/orbit/OrbitDatabases';
import { ORBIT_DIRECTORY_DEFAULT } from '../constants'; import { ORBIT_DIRECTORY_DEFAULT } from '../constants';
import { logger } from './logger'; import { logger } from './logger';
@ -31,11 +32,7 @@ export const createOrbitInstance = async (ipfs, contractAddress) => {
}; };
export const getPeerDatabases = async (orbit, userAddresses) => Promise.all(userAddresses export const getPeerDatabases = async (orbit, userAddresses) => Promise.all(userAddresses
.flatMap((userAddress) => [ .flatMap((userAddress) => databaseNames.map((dbName) => determineKVAddress({ orbit, dbName, userAddress }))));
determineKVAddress({ orbit, dbName: 'user', userAddress }),
determineKVAddress({ orbit, dbName: 'posts', userAddress }),
determineKVAddress({ orbit, dbName: 'topics', userAddress }),
]));
export const openKVDBs = async (orbit, databases) => { export const openKVDBs = async (orbit, databases) => {
databases databases

13
packages/concordia-shared/src/constants/contracts/events/VotingContractEvents.js

@ -0,0 +1,13 @@
const POLL_CREATED_EVENT = 'PollCreated';
const USER_VOTED_POLL_EVENT = 'UserVotedPoll';
const votingContractEvents = Object.freeze([
POLL_CREATED_EVENT,
USER_VOTED_POLL_EVENT,
]);
module.exports = {
POLL_CREATED_EVENT,
USER_VOTED_POLL_EVENT,
votingContractEvents,
};

4
packages/concordia-shared/src/constants/contracts/events/index.js

@ -1,10 +1,12 @@
const { forumContractEvents } = require('./ForumContractEvents'); const { forumContractEvents } = require('./ForumContractEvents');
const { postVotingContractEvents } = require('./PostVotingContractEvents'); const { postVotingContractEvents } = require('./PostVotingContractEvents');
const { FORUM_CONTRACT, POST_VOTING_CONTRACT } = require('../ContractNames'); const { votingContractEvents } = require('./VotingContractEvents');
const { FORUM_CONTRACT, POST_VOTING_CONTRACT, VOTING_CONTRACT } = require('../ContractNames');
const appEvents = Object.freeze({ const appEvents = Object.freeze({
[FORUM_CONTRACT]: forumContractEvents, [FORUM_CONTRACT]: forumContractEvents,
[POST_VOTING_CONTRACT]: postVotingContractEvents, [POST_VOTING_CONTRACT]: postVotingContractEvents,
[VOTING_CONTRACT]: votingContractEvents,
}); });
module.exports = appEvents; module.exports = appEvents;

9
packages/concordia-shared/src/constants/orbit/OrbitDatabases.js

@ -1,6 +1,7 @@
const USER_DATABASE = 'user'; const USER_DATABASE = 'user';
const TOPICS_DATABASE = 'topics'; const TOPICS_DATABASE = 'topics';
const POSTS_DATABASE = 'posts'; const POSTS_DATABASE = 'posts';
const POLLS_DATABASE = 'polls';
const databases = [ const databases = [
{ {
@ -15,11 +16,19 @@ const databases = [
address: POSTS_DATABASE, address: POSTS_DATABASE,
type: 'keyvalue', type: 'keyvalue',
}, },
{
address: POLLS_DATABASE,
type: 'keyvalue',
},
]; ];
const databaseNames = [USER_DATABASE, TOPICS_DATABASE, POSTS_DATABASE, POLLS_DATABASE];
module.exports = Object.freeze({ module.exports = Object.freeze({
USER_DATABASE, USER_DATABASE,
TOPICS_DATABASE, TOPICS_DATABASE,
POSTS_DATABASE, POSTS_DATABASE,
POLLS_DATABASE,
databases, databases,
databaseNames,
}); });

5
yarn.lock

@ -5071,6 +5071,11 @@ crypto-browserify@3.12.0, crypto-browserify@^3.11.0:
randombytes "^2.0.0" randombytes "^2.0.0"
randomfill "^1.0.3" randomfill "^1.0.3"
crypto-js@~4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc"
integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==
crypto-random-string@^2.0.0: crypto-random-string@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"

Loading…
Cancel
Save