Browse Source

Merge branch 'implement-ui' into add-event-saga-fix-user-state

# Conflicts:
#	packages/concordia-app/src/components/PostList/PostListRow/index.jsx
develop
Apostolos Fanakis 4 years ago
parent
commit
f567314e0b
  1. 78
      packages/concordia-app/src/components/PostCreate/index.jsx
  2. 63
      packages/concordia-app/src/components/PostList/PostListRow/index.jsx
  3. 5
      packages/concordia-app/src/components/PostList/index.jsx
  4. 55
      packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx
  5. 4
      packages/concordia-app/src/options/web3Options.js
  6. 2
      packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js
  7. 58
      packages/concordia-app/src/views/Topic/TopicView/index.jsx

78
packages/concordia-app/src/components/PostCreate/index.jsx

@ -8,20 +8,31 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import determineKVAddress from '../../utils/orbitUtils'; import determineKVAddress from '../../utils/orbitUtils';
import { USER_DATABASE } from '../../constants/OrbitDatabases'; import { POSTS_DATABASE, USER_DATABASE } from '../../constants/OrbitDatabases';
import { FETCH_USER_DATABASE } from '../../redux/actions/peerDbReplicationActions'; import { FETCH_USER_DATABASE } from '../../redux/actions/peerDbReplicationActions';
import { USER_PROFILE_PICTURE } from '../../constants/UserDatabaseKeys'; import { USER_PROFILE_PICTURE } from '../../constants/UserDatabaseKeys';
import { breeze } from '../../redux/store'; import { breeze, drizzle } from '../../redux/store';
import './styles.css'; import './styles.css';
import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../constants/TransactionStatus';
import { POST_CONTENT, POST_SUBJECT } from '../../constants/PostsDatabaseKeys';
const { contracts: { Forum: { methods: { createPost } } } } = drizzle;
const { orbit } = breeze; const { orbit } = breeze;
const PostCreate = (props) => { const PostCreate = (props) => {
const { id: postId, initialPostSubject } = props; const {
topicId, postIndexInTopic, initialPostSubject, account,
} = props;
const transactionStack = useSelector((state) => state.transactionStack);
const transactions = useSelector((state) => state.transactions);
const [postSubject, setPostSubject] = useState(initialPostSubject); const [postSubject, setPostSubject] = useState(initialPostSubject);
const [postContent, setPostContent] = useState(''); const [postContent, setPostContent] = useState('');
const [userProfilePictureUrl, setUserProfilePictureUrl] = useState(); const [userProfilePictureUrl, setUserProfilePictureUrl] = useState();
const [postSubjectInputEmptySubmit, setPostSubjectInputEmptySubmit] = useState(false);
const [postContentInputEmptySubmit, setPostContentInputEmptySubmit] = useState(false);
const [createPostCacheSendStackId, setCreatePostCacheSendStackId] = useState('');
const [posting, setPosting] = useState(false); const [posting, setPosting] = useState(false);
const [storingPost, setStoringPost] = useState(false);
const userAddress = useSelector((state) => state.user.address); const userAddress = useSelector((state) => state.user.address);
const users = useSelector((state) => state.orbitData.users); const users = useSelector((state) => state.orbitData.users);
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -68,13 +79,58 @@ const PostCreate = (props) => {
} }
}, [posting]); }, [posting]);
useEffect(() => {
if (posting && !storingPost && transactionStack && transactionStack[createPostCacheSendStackId]
&& transactions[transactionStack[createPostCacheSendStackId]]) {
if (transactions[transactionStack[createPostCacheSendStackId]].status === TRANSACTION_ERROR) {
setPosting(false);
} else if (transactions[transactionStack[createPostCacheSendStackId]].status === TRANSACTION_SUCCESS) {
const {
receipt: { events: { PostCreated: { returnValues: { postID: contractPostId } } } },
} = transactions[transactionStack[createPostCacheSendStackId]];
const { stores } = orbit;
const postsDb = Object.values(stores).find((store) => store.dbname === POSTS_DATABASE);
postsDb
.put(contractPostId, {
[POST_SUBJECT]: postSubject,
[POST_CONTENT]: postContent,
}, { pin: true })
.then(() => {
setPostSubject(initialPostSubject);
setPostContent('');
setPosting(false);
setPostSubjectInputEmptySubmit(false);
setPostContentInputEmptySubmit(false);
setCreatePostCacheSendStackId('');
})
.catch((reason) => {
console.log(reason);
});
setStoringPost(true);
}
}
}, [
createPostCacheSendStackId, initialPostSubject, postContent, postSubject, posting, storingPost, transactionStack,
transactions,
]);
const savePost = useCallback(() => { const savePost = useCallback(() => {
if (postSubject === '' || postContent === '') { if (postSubject === '') {
setPostSubjectInputEmptySubmit(true);
return;
}
if (postContent === '') {
setPostContentInputEmptySubmit(true);
return; return;
} }
setPosting(true); setPosting(true);
}, [postContent, postSubject]); setCreatePostCacheSendStackId(createPost.cacheSend(...[topicId], { from: account }));
}, [account, postContent, postSubject, topicId]);
return ( return (
<Feed> <Feed>
@ -93,7 +149,6 @@ const PostCreate = (props) => {
size="big" size="big"
inverted inverted
color="black" color="black"
verticalAlign="middle"
/> />
)} )}
</Feed.Label> </Feed.Label>
@ -105,11 +160,12 @@ const PostCreate = (props) => {
name="postSubject" name="postSubject"
className="subject-input" className="subject-input"
size="mini" size="mini"
error={postSubjectInputEmptySubmit}
value={postSubject} value={postSubject}
onChange={handleInputChange} onChange={handleInputChange}
/> />
<span className="post-summary-meta-index"> <span className="post-summary-meta-index">
{t('post.list.row.post.id', { id: postId })} {t('post.list.row.post.id', { id: postIndexInTopic })}
</span> </span>
</div> </div>
</Feed.Summary> </Feed.Summary>
@ -121,6 +177,7 @@ const PostCreate = (props) => {
className="content-input" className="content-input"
size="mini" size="mini"
rows={4} rows={4}
error={postContentInputEmptySubmit}
value={postContent} value={postContent}
onChange={handleInputChange} onChange={handleInputChange}
/> />
@ -133,8 +190,8 @@ const PostCreate = (props) => {
animated animated
type="button" type="button"
color="green" color="green"
disabled={posting} disabled={posting || postSubject === '' || postContent === ''}
onClick={savePost || postSubject === '' || postContent === ''} onClick={savePost}
> >
<Button.Content visible> <Button.Content visible>
{t('post.create.form.send.button')} {t('post.create.form.send.button')}
@ -152,7 +209,8 @@ const PostCreate = (props) => {
}; };
PostCreate.propTypes = { PostCreate.propTypes = {
id: PropTypes.number.isRequired, topicId: PropTypes.number.isRequired,
postIndexInTopic: PropTypes.number.isRequired,
initialPostSubject: PropTypes.string.isRequired, initialPostSubject: PropTypes.string.isRequired,
}; };

63
packages/concordia-app/src/components/PostList/PostListRow/index.jsx

@ -7,8 +7,8 @@ import {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import moment from 'moment'; import moment from 'moment';
import { useHistory } from 'react-router';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import { breeze } from '../../../redux/store'; import { breeze } from '../../../redux/store';
import './styles.css'; import './styles.css';
@ -21,7 +21,9 @@ import { FORUM_CONTRACT } from '../../../constants/ContractNames';
const { orbit } = breeze; const { orbit } = breeze;
const PostListRow = (props) => { const PostListRow = (props) => {
const { id: postId, postCallHash, loading } = props; const {
id: postId, postIndexInTopic, postCallHash, loading,
} = props;
const getPostResults = useSelector((state) => state.contracts[FORUM_CONTRACT].getPost); const getPostResults = useSelector((state) => state.contracts[FORUM_CONTRACT].getPost);
const [postAuthorAddress, setPostAuthorAddress] = useState(null); const [postAuthorAddress, setPostAuthorAddress] = useState(null);
const [postAuthor, setPostAuthor] = useState(null); const [postAuthor, setPostAuthor] = useState(null);
@ -33,7 +35,6 @@ const PostListRow = (props) => {
const posts = useSelector((state) => state.orbitData.posts); const posts = useSelector((state) => state.orbitData.posts);
const users = useSelector((state) => state.orbitData.users); const users = useSelector((state) => state.orbitData.users);
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {
@ -89,25 +90,38 @@ const PostListRow = (props) => {
} }
}, [postAuthorAddress, users]); }, [postAuthorAddress, users]);
const authorAvatar = useMemo(() => (postAuthorMeta !== null && postAuthorMeta[USER_PROFILE_PICTURE]
? (
<Image
avatar
src={postAuthorMeta[USER_PROFILE_PICTURE]}
/>
)
: (
<Icon
name="user circle"
size="big"
inverted
color="black"
/>
)), [postAuthorMeta]);
const authorAvatarLink = useMemo(() => {
if (postAuthorAddress) {
return (
<Link to={`/users/${postAuthorAddress}`}>
{authorAvatar}
</Link>
);
}
return authorAvatar;
}, [authorAvatar, postAuthorAddress]);
return useMemo(() => ( return useMemo(() => (
<Dimmer.Dimmable as={Feed.Event} blurring dimmed={loading}> <Dimmer.Dimmable as={Feed.Event} blurring dimmed={loading}>
<Feed.Label className="post-profile-picture"> <Feed.Label className="post-profile-picture">
{postAuthorMeta !== null && postAuthorMeta[USER_PROFILE_PICTURE] {authorAvatarLink}
? (
<Image
avatar
src={postAuthorMeta[USER_PROFILE_PICTURE]}
/>
)
: (
<Icon
name="user circle"
size="big"
inverted
color="black"
verticalAlign="middle"
/>
)}
</Feed.Label> </Feed.Label>
<Feed.Content> <Feed.Content>
<Feed.Summary> <Feed.Summary>
@ -116,15 +130,15 @@ const PostListRow = (props) => {
? postSubject ? postSubject
: <Placeholder><Placeholder.Line length="very long" /></Placeholder>} : <Placeholder><Placeholder.Line length="very long" /></Placeholder>}
<span className="post-summary-meta-index"> <span className="post-summary-meta-index">
{t('post.list.row.post.id', { id: postId })} {t('post.list.row.post.id', { id: postIndexInTopic })}
</span> </span>
</div> </div>
{postAuthor !== null && timeAgo !== null {postAuthor !== null && setPostAuthorAddress !== null && timeAgo !== null
? ( ? (
<> <>
{t('post.list.row.author.pre')} {t('post.list.row.author.pre')}
&nbsp; &nbsp;
<Feed.User>{postAuthor}</Feed.User> <Feed.User as={Link} to={`/users/${postAuthorAddress}`}>{postAuthor}</Feed.User>
<Feed.Date className="post-summary-meta-date">{timeAgo}</Feed.Date> <Feed.Date className="post-summary-meta-date">{timeAgo}</Feed.Date>
</> </>
) )
@ -135,7 +149,9 @@ const PostListRow = (props) => {
</Feed.Extra> </Feed.Extra>
</Feed.Content> </Feed.Content>
</Dimmer.Dimmable> </Dimmer.Dimmable>
), [loading, postAuthor, postAuthorMeta, postId, postContent, postSubject, t, timeAgo]); ), [
authorAvatarLink, loading, postAuthor, postAuthorAddress, postContent, postIndexInTopic, postSubject, t, timeAgo,
]);
}; };
PostListRow.defaultProps = { PostListRow.defaultProps = {
@ -144,6 +160,7 @@ PostListRow.defaultProps = {
PostListRow.propTypes = { PostListRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
postIndexInTopic: PropTypes.number.isRequired,
postCallHash: PropTypes.string, postCallHash: PropTypes.string,
loading: PropTypes.bool, loading: PropTypes.bool,
}; };

5
packages/concordia-app/src/components/PostList/index.jsx

@ -41,12 +41,13 @@ const PostList = (props) => {
return null; return null;
} }
return postIds return postIds
.map((postId) => { .map((postId, index) => {
const postHash = getPostCallHashes.find((getPostCallHash) => getPostCallHash.id === postId); const postHash = getPostCallHashes.find((getPostCallHash) => getPostCallHash.id === postId);
return ( return (
<PostListRow <PostListRow
id={postId} id={postId}
postIndexInTopic={index + 1}
key={postId} key={postId}
postCallHash={postHash && postHash.hash} postCallHash={postHash && postHash.hash}
loading={postHash === undefined} loading={postHash === undefined}
@ -56,7 +57,7 @@ const PostList = (props) => {
}, [getPostCallHashes, loading, postIds]); }, [getPostCallHashes, loading, postIds]);
return ( return (
<Dimmer.Dimmable as={Feed} blurring dimmed={loading} selection divided id="post-list" size="big"> <Dimmer.Dimmable as={Feed} blurring dimmed={loading} id="post-list" size="large">
<Loader active={loading} /> <Loader active={loading} />
{posts} {posts}
</Dimmer.Dimmable> </Dimmer.Dimmable>

55
packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx

@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
import moment from 'moment'; import moment from 'moment';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import { breeze } from '../../../redux/store'; import { breeze } from '../../../redux/store';
import './styles.css'; import './styles.css';
@ -89,6 +90,40 @@ const TopicListRow = (props) => {
} }
}, [topicAuthorAddress, users]); }, [topicAuthorAddress, users]);
const stopClickPropagation = (event) => {
event.stopPropagation();
};
const authorAvatar = useMemo(() => (topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE]
? (
<Image
className="profile-picture"
avatar
src={topicAuthorMeta[USER_PROFILE_PICTURE]}
/>
)
: (
<List.Icon
name="user circle"
size="big"
inverted
color="black"
verticalAlign="middle"
/>
)), [topicAuthorMeta]);
const authorAvatarLink = useMemo(() => {
if (topicAuthorAddress) {
return (
<Link to={`/users/${topicAuthorAddress}`} onClick={stopClickPropagation}>
{authorAvatar}
</Link>
);
}
return authorAvatar;
}, [authorAvatar, topicAuthorAddress]);
return useMemo(() => { return useMemo(() => {
const handleTopicClick = () => { const handleTopicClick = () => {
history.push(`/topics/${topicId}`); history.push(`/topics/${topicId}`);
@ -96,23 +131,7 @@ const TopicListRow = (props) => {
return ( return (
<Dimmer.Dimmable as={List.Item} onClick={handleTopicClick} blurring dimmed={loading} className="list-item"> <Dimmer.Dimmable as={List.Item} onClick={handleTopicClick} blurring dimmed={loading} className="list-item">
{topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] {authorAvatarLink}
? (
<Image
className="profile-picture"
avatar
src={topicAuthorMeta[USER_PROFILE_PICTURE]}
/>
)
: (
<List.Icon
name="user circle"
size="big"
inverted
color="black"
verticalAlign="middle"
/>
)}
<List.Content className="list-content"> <List.Content className="list-content">
<List.Header> <List.Header>
<Grid> <Grid>
@ -149,7 +168,7 @@ const TopicListRow = (props) => {
</List.Content> </List.Content>
</Dimmer.Dimmable> </Dimmer.Dimmable>
); );
}, [history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicAuthorMeta, topicId, topicSubject]); }, [authorAvatarLink, history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicId, topicSubject]);
}; };
TopicListRow.defaultProps = { TopicListRow.defaultProps = {

4
packages/concordia-app/src/options/web3Options.js

@ -3,7 +3,9 @@ import Web3 from 'web3';
const { WEB3_URL, WEB3_PORT } = process.env; const { WEB3_URL, WEB3_PORT } = process.env;
// We need fallback ws://127.0.0.1:8545 because drizzle has not the patched web3 we use here // We need fallback ws://127.0.0.1:8545 because drizzle has not the patched web3 we use here
const web3 = (WEB3_URL && WEB3_PORT) ? `ws://${WEB3_URL}:${WEB3_PORT}` : new Web3(Web3.givenProvider || new Web3.providers.WebsocketProvider('ws://127.0.0.1:8545')); const web3 = (WEB3_URL && WEB3_PORT)
? `ws://${WEB3_URL}:${WEB3_PORT}`
: new Web3(Web3.givenProvider || new Web3.providers.WebsocketProvider('ws://127.0.0.1:8545'));
const web3Options = { const web3Options = {
web3, web3,

2
packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js

@ -31,7 +31,7 @@ function* updateReduxState({ database }) {
if (database.dbname === USER_DATABASE) { if (database.dbname === USER_DATABASE) {
const oldUsersUnchanged = users const oldUsersUnchanged = users
.filter((user) => !database.id !== user.id); .filter((user) => database.id !== user.id);
yield put({ yield put({
type: UPDATE_ORBIT_DATA, type: UPDATE_ORBIT_DATA,

58
packages/concordia-app/src/views/Topic/TopicView/index.jsx

@ -5,6 +5,8 @@ import {
Container, Dimmer, Icon, Image, Placeholder, Step, Container, Dimmer, Icon, Image, Placeholder, Step,
} from 'semantic-ui-react'; } from 'semantic-ui-react';
import moment from 'moment'; import moment from 'moment';
import { Link } from 'react-router-dom';
import { useHistory } from 'react-router';
import { breeze, drizzle } from '../../../redux/store'; import { breeze, drizzle } from '../../../redux/store';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import './styles.css'; import './styles.css';
@ -37,7 +39,7 @@ const TopicView = (props) => {
const [timestamp, setTimestamp] = useState(initialTimestamp || null); const [timestamp, setTimestamp] = useState(initialTimestamp || null);
const [postIds, setPostIds] = useState(initialPostIds || null); const [postIds, setPostIds] = useState(initialPostIds || null);
const [topicSubject, setTopicSubject] = useState(null); const [topicSubject, setTopicSubject] = useState(null);
const history = useHistory();
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
@ -55,6 +57,11 @@ const TopicView = (props) => {
useEffect(() => { useEffect(() => {
if (getTopicCallHash && getTopicResults && getTopicResults[getTopicCallHash]) { if (getTopicCallHash && getTopicResults && getTopicResults[getTopicCallHash]) {
if (getTopicResults[getTopicCallHash].value == null) {
history.push('/');
return;
}
setTopicAuthorAddress(getTopicResults[getTopicCallHash].value[0]); setTopicAuthorAddress(getTopicResults[getTopicCallHash].value[0]);
setTopicAuthor(getTopicResults[getTopicCallHash].value[1]); setTopicAuthor(getTopicResults[getTopicCallHash].value[1]);
setTimestamp(getTopicResults[getTopicCallHash].value[2]); setTimestamp(getTopicResults[getTopicCallHash].value[2]);
@ -72,7 +79,7 @@ const TopicView = (props) => {
}); });
} }
} }
}, [dispatch, getTopicCallHash, getTopicResults, topicId, topics, userAddress]); }, [dispatch, getTopicCallHash, getTopicResults, history, topicId, topics, userAddress]);
useEffect(() => { useEffect(() => {
if (topicAuthorAddress !== null) { if (topicAuthorAddress !== null) {
@ -115,28 +122,32 @@ const TopicView = (props) => {
> >
<Step.Group fluid> <Step.Group fluid>
<Step key="topic-header-step-user"> <Step key="topic-header-step-user">
{topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] <Link to={`/users/${topicAuthorAddress}`}>
? ( {topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE]
<Image ? (
avatar <Image
src={topicAuthorMeta[USER_PROFILE_PICTURE]} avatar
/> src={topicAuthorMeta[USER_PROFILE_PICTURE]}
) />
: ( )
<Icon : (
name="user circle" <Icon
size="big" name="user circle"
inverted size="big"
color="black" inverted
/> color="black"
)} />
)}
</Link>
<Step.Content> <Step.Content>
<Step.Title> <Step.Title>
{topicAuthor || ( <Link to={`/users/${topicAuthorAddress}`}>
<Placeholder id="author-placeholder" inverted> {topicAuthor || (
<Placeholder.Line length="full" /> <Placeholder id="author-placeholder" inverted>
</Placeholder> <Placeholder.Line length="full" />
)} </Placeholder>
)}
</Link>
</Step.Title> </Step.Title>
</Step.Content> </Step.Content>
</Step> </Step>
@ -165,7 +176,8 @@ const TopicView = (props) => {
<PostList postIds={postIds || []} loading={postIds === null} /> <PostList postIds={postIds || []} loading={postIds === null} />
{topicSubject !== null && postIds !== null && ( {topicSubject !== null && postIds !== null && (
<PostCreate <PostCreate
id={postIds.length} topicId={topicId}
postIndexInTopic={postIds.length + 1}
initialPostSubject={topicSubject} initialPostSubject={topicSubject}
/> />
)} )}

Loading…
Cancel
Save