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 { useDispatch, useSelector } from 'react-redux';
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 { USER_PROFILE_PICTURE } from '../../constants/UserDatabaseKeys';
import { breeze } from '../../redux/store';
import { breeze, drizzle } from '../../redux/store';
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 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 [postContent, setPostContent] = 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 [storingPost, setStoringPost] = useState(false);
const userAddress = useSelector((state) => state.user.address);
const users = useSelector((state) => state.orbitData.users);
const dispatch = useDispatch();
@ -68,13 +79,58 @@ const PostCreate = (props) => {
}
}, [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(() => {
if (postSubject === '' || postContent === '') {
if (postSubject === '') {
setPostSubjectInputEmptySubmit(true);
return;
}
if (postContent === '') {
setPostContentInputEmptySubmit(true);
return;
}
setPosting(true);
}, [postContent, postSubject]);
setCreatePostCacheSendStackId(createPost.cacheSend(...[topicId], { from: account }));
}, [account, postContent, postSubject, topicId]);
return (
<Feed>
@ -93,7 +149,6 @@ const PostCreate = (props) => {
size="big"
inverted
color="black"
verticalAlign="middle"
/>
)}
</Feed.Label>
@ -105,11 +160,12 @@ const PostCreate = (props) => {
name="postSubject"
className="subject-input"
size="mini"
error={postSubjectInputEmptySubmit}
value={postSubject}
onChange={handleInputChange}
/>
<span className="post-summary-meta-index">
{t('post.list.row.post.id', { id: postId })}
{t('post.list.row.post.id', { id: postIndexInTopic })}
</span>
</div>
</Feed.Summary>
@ -121,6 +177,7 @@ const PostCreate = (props) => {
className="content-input"
size="mini"
rows={4}
error={postContentInputEmptySubmit}
value={postContent}
onChange={handleInputChange}
/>
@ -133,8 +190,8 @@ const PostCreate = (props) => {
animated
type="button"
color="green"
disabled={posting}
onClick={savePost || postSubject === '' || postContent === ''}
disabled={posting || postSubject === '' || postContent === ''}
onClick={savePost}
>
<Button.Content visible>
{t('post.create.form.send.button')}
@ -152,7 +209,8 @@ const PostCreate = (props) => {
};
PostCreate.propTypes = {
id: PropTypes.number.isRequired,
topicId: PropTypes.number.isRequired,
postIndexInTopic: PropTypes.number.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 { useTranslation } from 'react-i18next';
import moment from 'moment';
import { useHistory } from 'react-router';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import { breeze } from '../../../redux/store';
import './styles.css';
@ -21,7 +21,9 @@ import { FORUM_CONTRACT } from '../../../constants/ContractNames';
const { orbit } = breeze;
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 [postAuthorAddress, setPostAuthorAddress] = useState(null);
const [postAuthor, setPostAuthor] = useState(null);
@ -33,7 +35,6 @@ const PostListRow = (props) => {
const posts = useSelector((state) => state.orbitData.posts);
const users = useSelector((state) => state.orbitData.users);
const dispatch = useDispatch();
const history = useHistory();
const { t } = useTranslation();
useEffect(() => {
@ -89,25 +90,38 @@ const PostListRow = (props) => {
}
}, [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(() => (
<Dimmer.Dimmable as={Feed.Event} blurring dimmed={loading}>
<Feed.Label className="post-profile-picture">
{postAuthorMeta !== null && postAuthorMeta[USER_PROFILE_PICTURE]
? (
<Image
avatar
src={postAuthorMeta[USER_PROFILE_PICTURE]}
/>
)
: (
<Icon
name="user circle"
size="big"
inverted
color="black"
verticalAlign="middle"
/>
)}
{authorAvatarLink}
</Feed.Label>
<Feed.Content>
<Feed.Summary>
@ -116,15 +130,15 @@ const PostListRow = (props) => {
? postSubject
: <Placeholder><Placeholder.Line length="very long" /></Placeholder>}
<span className="post-summary-meta-index">
{t('post.list.row.post.id', { id: postId })}
{t('post.list.row.post.id', { id: postIndexInTopic })}
</span>
</div>
{postAuthor !== null && timeAgo !== null
{postAuthor !== null && setPostAuthorAddress !== null && timeAgo !== null
? (
<>
{t('post.list.row.author.pre')}
&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>
</>
)
@ -135,7 +149,9 @@ const PostListRow = (props) => {
</Feed.Extra>
</Feed.Content>
</Dimmer.Dimmable>
), [loading, postAuthor, postAuthorMeta, postId, postContent, postSubject, t, timeAgo]);
), [
authorAvatarLink, loading, postAuthor, postAuthorAddress, postContent, postIndexInTopic, postSubject, t, timeAgo,
]);
};
PostListRow.defaultProps = {
@ -144,6 +160,7 @@ PostListRow.defaultProps = {
PostListRow.propTypes = {
id: PropTypes.number.isRequired,
postIndexInTopic: PropTypes.number.isRequired,
postCallHash: PropTypes.string,
loading: PropTypes.bool,
};

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

@ -41,12 +41,13 @@ const PostList = (props) => {
return null;
}
return postIds
.map((postId) => {
.map((postId, index) => {
const postHash = getPostCallHashes.find((getPostCallHash) => getPostCallHash.id === postId);
return (
<PostListRow
id={postId}
postIndexInTopic={index + 1}
key={postId}
postCallHash={postHash && postHash.hash}
loading={postHash === undefined}
@ -56,7 +57,7 @@ const PostList = (props) => {
}, [getPostCallHashes, loading, postIds]);
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} />
{posts}
</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 { useHistory } from 'react-router';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import { breeze } from '../../../redux/store';
import './styles.css';
@ -89,6 +90,40 @@ const TopicListRow = (props) => {
}
}, [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(() => {
const handleTopicClick = () => {
history.push(`/topics/${topicId}`);
@ -96,23 +131,7 @@ const TopicListRow = (props) => {
return (
<Dimmer.Dimmable as={List.Item} onClick={handleTopicClick} blurring dimmed={loading} className="list-item">
{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"
/>
)}
{authorAvatarLink}
<List.Content className="list-content">
<List.Header>
<Grid>
@ -149,7 +168,7 @@ const TopicListRow = (props) => {
</List.Content>
</Dimmer.Dimmable>
);
}, [history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicAuthorMeta, topicId, topicSubject]);
}, [authorAvatarLink, history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicId, topicSubject]);
};
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;
// 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 = {
web3,

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

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

Loading…
Cancel
Save