diff --git a/packages/concordia-app/public/locales/en/translation.json b/packages/concordia-app/public/locales/en/translation.json
index 107d60d..c4834b6 100644
--- a/packages/concordia-app/public/locales/en/translation.json
+++ b/packages/concordia-app/public/locales/en/translation.json
@@ -2,6 +2,8 @@
"board.header.no.topics.message": "There are no topics yet!",
"board.sub.header.no.topics.guest": "Sign up and be the first to post.",
"board.sub.header.no.topics.user": "Be the first to post.",
+ "post.list.row.author.date": "Posted by {{author}}, {{timeAgo}}",
+ "post.list.row.post.id": "#{{id}}",
"register.card.header": "Sign Up",
"register.form.button.back": "Back",
"register.form.button.guest": "Continue as guest",
@@ -15,12 +17,12 @@
"topbar.button.create.topic": "Create topic",
"topbar.button.profile": "Profile",
"topbar.button.register": "Sign Up",
- "topic.create.form.subject.field.label": "Topic subject",
- "topic.create.form.subject.field.placeholder": "Subject",
"topic.create.form.message.field.label": "First post message",
"topic.create.form.message.field.placeholder": "Message",
"topic.create.form.post.button": "Post",
- "topic.list.row.author.date": "Posted by {{author}}, {{timeAgo}}",
+ "topic.create.form.subject.field.label": "Topic subject",
+ "topic.create.form.subject.field.placeholder": "Subject",
+ "topic.list.row.author.date": "Created by {{author}}, {{timeAgo}}",
"topic.list.row.number.of.replies": "{{numberOfReplies}} replies",
"topic.list.row.topic.id": "#{{id}}"
}
\ No newline at end of file
diff --git a/packages/concordia-app/src/components/PostList/PostListRow/index.jsx b/packages/concordia-app/src/components/PostList/PostListRow/index.jsx
new file mode 100644
index 0000000..b03d475
--- /dev/null
+++ b/packages/concordia-app/src/components/PostList/PostListRow/index.jsx
@@ -0,0 +1,103 @@
+import React, {
+ memo, useEffect, useMemo, useState,
+} from 'react';
+import {
+ Dimmer, Grid, List, Loader, Placeholder,
+} from 'semantic-ui-react';
+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 { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
+import { breeze } from '../../../redux/store';
+import './styles.css';
+
+const { orbit } = breeze;
+
+const PostListRow = (props) => {
+ const { id: postId, postCallHash, loading } = props;
+ const getPostResults = useSelector((state) => state.contracts.Forum.getPost);
+ const [postAuthorAddress, setPostAuthorAddress] = useState(null);
+ const [postAuthor, setPostAuthor] = useState(null);
+ const [timeAgo, setTimeAgo] = useState(null);
+ const [postSubject, setPostSubject] = useState(null);
+ const [postMessage, setPostMessage] = useState(null);
+ const userAddress = useSelector((state) => state.user.address);
+ const posts = useSelector((state) => state.orbitData.posts);
+ const dispatch = useDispatch();
+ const history = useHistory();
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (!loading && postCallHash && getPostResults[postCallHash] !== undefined) {
+ setPostAuthorAddress(getPostResults[postCallHash].value[0]);
+ setPostAuthor(getPostResults[postCallHash].value[1]);
+ setTimeAgo(moment(getPostResults[postCallHash].value[2] * 1000).fromNow());
+ }
+ }, [getPostResults, loading, postCallHash]);
+
+ useEffect(() => {
+ if (postAuthorAddress && userAddress !== postAuthorAddress) {
+ dispatch({
+ type: FETCH_USER_DATABASE,
+ orbit,
+ dbName: 'posts',
+ userAddress: postAuthorAddress,
+ });
+ }
+ }, [dispatch, postAuthorAddress, userAddress]);
+
+ useEffect(() => {
+ const postFound = posts
+ .find((post) => post.id === postId);
+
+ if (postFound) {
+ setPostSubject(postFound.subject);
+ setPostMessage(postFound.message);
+ }
+ }, [postId, posts]);
+
+ return useMemo(() => (
+
+
+
+
+
+
+ {postSubject !== null
+ ? postSubject
+ : }
+
+
+
+ {t('post.list.row.post.id', { id: postId })}
+
+
+
+
+
+
+
+ {postAuthor !== null && timeAgo !== null
+ ? t('post.list.row.author.date', { author: postAuthor, timeAgo })
+ : }
+
+
+
+
+
+ ), [loading, postAuthor, postId, postSubject, t, timeAgo]);
+};
+
+PostListRow.defaultProps = {
+ loading: false,
+};
+
+PostListRow.propTypes = {
+ id: PropTypes.number.isRequired,
+ postCallHash: PropTypes.string,
+ loading: PropTypes.bool,
+};
+
+export default memo(PostListRow);
diff --git a/packages/concordia-app/src/components/PostList/PostListRow/styles.css b/packages/concordia-app/src/components/PostList/PostListRow/styles.css
new file mode 100644
index 0000000..0058f79
--- /dev/null
+++ b/packages/concordia-app/src/components/PostList/PostListRow/styles.css
@@ -0,0 +1,8 @@
+.post-metadata {
+ font-size: 12px !important;
+ font-weight: initial;
+}
+
+.list-item {
+ text-align: start;
+}
diff --git a/packages/concordia-app/src/components/PostList/index.jsx b/packages/concordia-app/src/components/PostList/index.jsx
new file mode 100644
index 0000000..d961233
--- /dev/null
+++ b/packages/concordia-app/src/components/PostList/index.jsx
@@ -0,0 +1,70 @@
+import React, {
+ useEffect, useMemo, useState,
+} from 'react';
+import PropTypes from 'prop-types';
+import { useSelector } from 'react-redux';
+import { Dimmer, List, Loader } from 'semantic-ui-react';
+import PostListRow from './PostListRow';
+import { drizzle } from '../../redux/store';
+
+const { contracts: { Forum: { methods: { getPost: { cacheCall: getPostChainData } } } } } = drizzle;
+
+const PostList = (props) => {
+ const { postIds, loading } = props;
+ const [getPostCallHashes, setGetPostCallHashes] = useState([]);
+ const drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized);
+ const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed);
+
+ useEffect(() => {
+ if (drizzleInitialized && !drizzleInitializationFailed && !loading) {
+ const newPostsFound = postIds
+ .filter((postId) => !getPostCallHashes
+ .map((getPostCallHash) => getPostCallHash.id)
+ .includes(postId));
+
+ if (newPostsFound.length > 0) {
+ setGetPostCallHashes([
+ ...getPostCallHashes,
+ ...newPostsFound
+ .map((postId) => ({
+ id: postId,
+ hash: getPostChainData(postId),
+ })),
+ ]);
+ }
+ }
+ }, [drizzleInitializationFailed, drizzleInitialized, getPostCallHashes, loading, postIds]);
+
+ const posts = useMemo(() => {
+ if (loading) {
+ return null;
+ }
+ return postIds
+ .map((postId) => {
+ const postHash = getPostCallHashes.find((getPostCallHash) => getPostCallHash.id === postId);
+
+ return (
+
+ );
+ });
+ }, [getPostCallHashes, loading, postIds]);
+
+ return (
+
+
+ {posts}
+
+ );
+};
+
+PostList.propTypes = {
+ postIds: PropTypes.arrayOf(PropTypes.number).isRequired,
+ loading: PropTypes.bool,
+};
+
+export default PostList;
diff --git a/packages/concordia-app/src/components/PostList/styles.css b/packages/concordia-app/src/components/PostList/styles.css
new file mode 100644
index 0000000..baf2856
--- /dev/null
+++ b/packages/concordia-app/src/components/PostList/styles.css
@@ -0,0 +1,3 @@
+#post-list{
+ height: 100%;
+}
diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx
index abc9f89..c5e4687 100644
--- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx
+++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx
@@ -8,6 +8,7 @@ import moment from 'moment';
import { breeze, drizzle } from '../../../redux/store';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import './styles.css';
+import PostList from '../../../components/PostList';
const { contracts: { Forum: { methods: { getTopic: { cacheCall: getTopicChainData } } } } } = drizzle;
const { orbit } = breeze;
@@ -115,6 +116,7 @@ const TopicView = (props) => {
+
);
};