diff --git a/packages/concordia-app/package.json b/packages/concordia-app/package.json
index f2bcbf9..26b8b62 100644
--- a/packages/concordia-app/package.json
+++ b/packages/concordia-app/package.json
@@ -34,6 +34,7 @@
"i18next-browser-languagedetector": "^6.0.1",
"i18next-http-backend": "^1.0.21",
"lodash": "^4.17.20",
+ "moment": "^2.29.1",
"prop-types": "~15.7.2",
"react": "~16.13.1",
"react-dom": "~16.13.1",
diff --git a/packages/concordia-app/public/locales/en/translation.json b/packages/concordia-app/public/locales/en/translation.json
index c55754f..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,9 +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.create.form.post.button": "Post",
+ "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/LoadingContainer.jsx b/packages/concordia-app/src/components/LoadingContainer.jsx
index 0260132..f8cda5f 100644
--- a/packages/concordia-app/src/components/LoadingContainer.jsx
+++ b/packages/concordia-app/src/components/LoadingContainer.jsx
@@ -1,167 +1,152 @@
-import React, { Children, Component } from 'react';
-import { connect } from 'react-redux';
-
+import React, { Children } from 'react';
import { breezeConstants } from '@ezerous/breeze';
-
+import { useSelector } from 'react-redux';
import LoadingComponent from './LoadingComponent';
// CSS
import '../assets/css/loading-component.css';
-class LoadingContainer extends Component {
- render() {
- const {
- web3: {
- status, networkId, networkFailed, accountsFailed,
- },
- drizzleStatus: {
- initializing,
- failed,
- },
- contractInitialized, contractDeployed, ipfsStatus, orbitStatus, userFetched, children,
- } = this.props;
-
- if ((status === 'initializing' || !networkId)
- && !networkFailed) {
- return (
-
- );
- }
+const LoadingContainer = ({ children }) => {
+ const initializing = useSelector((state) => state.drizzleStatus.initializing);
+ const failed = useSelector((state) => state.drizzleStatus.failed);
+ const ipfsStatus = useSelector((state) => state.ipfs.status);
+ const orbitStatus = useSelector((state) => state.orbit.status);
+ const web3Status = useSelector((state) => state.web3.status);
+ const web3NetworkId = useSelector((state) => state.web3.networkId);
+ const web3NetworkFailed = useSelector((state) => state.web3.networkFailed);
+ const web3AccountsFailed = useSelector((state) => state.web3.accountsFailed);
+ const contractInitialized = useSelector((state) => state.contracts.Forum.initialized);
+ const contractDeployed = useSelector((state) => state.contracts.Forum.deployed);
+ const userFetched = useSelector((state) => state.user.address);
+
+ if ((web3Status === 'initializing' || !web3NetworkId)
+ && !web3NetworkFailed) {
+ return (
+
+ );
+ }
- if (status === 'failed' || networkFailed) {
- return (
-
- );
- }
+ if (web3Status === 'failed' || web3NetworkFailed) {
+ return (
+
+ );
+ }
- if (status === 'initialized' && accountsFailed) {
- return (
-
- );
- }
+ if (web3Status === 'initialized' && web3AccountsFailed) {
+ return (
+
+ );
+ }
- if (initializing
+ if (initializing
|| (!failed && !contractInitialized && contractDeployed)) {
- return (
-
- );
- }
-
- if (!contractDeployed) {
- return (
-
- );
- }
+ return (
+
+ );
+ }
- if (ipfsStatus === breezeConstants.STATUS_INITIALIZING) {
- return (
-
- );
- }
+ if (!contractDeployed) {
+ return (
+
+ );
+ }
- if (ipfsStatus === breezeConstants.STATUS_FAILED) {
- return (
-
- );
- }
+ if (ipfsStatus === breezeConstants.STATUS_INITIALIZING) {
+ return (
+
+ );
+ }
- if (orbitStatus === breezeConstants.STATUS_INITIALIZING) {
- const message = process.env.NODE_ENV === 'development'
- ? 'If needed, please sign the transaction in MetaMask to create the databases.'
- : 'Please sign the transaction in MetaMask to create the databases.';
- return (
-
- );
- }
+ if (ipfsStatus === breezeConstants.STATUS_FAILED) {
+ return (
+
+ );
+ }
- if (orbitStatus === breezeConstants.STATUS_FAILED) {
- return (
-
- );
- }
+ if (orbitStatus === breezeConstants.STATUS_INITIALIZING) {
+ const message = process.env.NODE_ENV === 'development'
+ ? 'If needed, please sign the transaction in MetaMask to create the databases.'
+ : 'Please sign the transaction in MetaMask to create the databases.';
+ return (
+
+ );
+ }
- if (!userFetched) {
- return (
-
- );
- }
+ if (orbitStatus === breezeConstants.STATUS_FAILED) {
+ return (
+
+ );
+ }
- return Children.only(children);
+ if (!userFetched) {
+ return (
+
+ );
}
-}
-const mapStateToProps = (state) => ({
- drizzleStatus: state.drizzleStatus,
- breezeStatus: state.breezeStatus,
- ipfsStatus: state.ipfs.status,
- orbitStatus: state.orbit.status,
- web3: state.web3,
- accounts: state.accounts,
- contractInitialized: state.contracts.Forum.initialized,
- contractDeployed: state.contracts.Forum.deployed,
- userFetched: state.user.address,
-});
+ return Children.only(children);
+};
-export default connect(mapStateToProps)(LoadingContainer);
+export default LoadingContainer;
diff --git a/packages/concordia-app/src/components/Placeholder/index.jsx b/packages/concordia-app/src/components/Placeholder/index.jsx
deleted file mode 100644
index 5c9394e..0000000
--- a/packages/concordia-app/src/components/Placeholder/index.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { List } from 'semantic-ui-react';
-import { PLACEHOLDER_TYPE_POST, PLACEHOLDER_TYPE_TOPIC } from '../../constants/PlaceholderTypes';
-
-const Placeholder = (props) => {
- const { placeholderType, extra } = props;
-
- switch (placeholderType) {
- case PLACEHOLDER_TYPE_TOPIC:
- return (
- <>
-
-
- topicSubject
-
-
- username
- Number of Replies
- timestamp
-
- >
- );
- case PLACEHOLDER_TYPE_POST:
- return (
-
LOADING POST
- );
- default:
- return ;
- }
-};
-
-const TopicPlaceholderExtra = PropTypes.PropTypes.shape({
- topicId: PropTypes.number.isRequired,
-});
-
-const PostPlaceholderExtra = PropTypes.PropTypes.shape({
- postIndex: PropTypes.number.isRequired,
-});
-
-Placeholder.propTypes = {
- placeholderType: PropTypes.string.isRequired,
- extra: PropTypes.oneOfType([
- TopicPlaceholderExtra.isRequired,
- PostPlaceholderExtra.isRequired,
- ]),
-};
-
-export default Placeholder;
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/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx
index 2fea75c..758cb68 100644
--- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx
+++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx
@@ -1,64 +1,118 @@
-import React, { useContext, useEffect, useState } from 'react';
-import { List } from 'semantic-ui-react';
+import React, {
+ memo, useEffect, useMemo, useState,
+} from 'react';
+import {
+ Dimmer, Grid, List, 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 AppContext from '../../AppContext';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
+import { breeze } from '../../../redux/store';
+import './styles.css';
+
+const { orbit } = breeze;
const TopicListRow = (props) => {
- const { topicData, topicId } = props;
- const { breeze: { orbit } } = useContext(AppContext.Context);
- const [topicSubject, setTopicSubject] = useState();
+ const { id: topicId, topicCallHash, loading } = props;
+ const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic);
+ const [numberOfReplies, setNumberOfReplies] = useState(null);
+ const [topicAuthorAddress, setTopicAuthorAddress] = useState(null);
+ const [topicAuthor, setTopicAuthor] = useState(null);
+ const [timeAgo, setTimeAgo] = useState(null);
+ const [topicSubject, setTopicSubject] = useState(null);
const userAddress = useSelector((state) => state.user.address);
const topics = useSelector((state) => state.orbitData.topics);
const dispatch = useDispatch();
+ const history = useHistory();
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (!loading && topicCallHash && getTopicResults[topicCallHash] !== undefined) {
+ setTopicAuthorAddress(getTopicResults[topicCallHash].value[0]);
+ setTopicAuthor(getTopicResults[topicCallHash].value[1]);
+ setTimeAgo(moment(getTopicResults[topicCallHash].value[2] * 1000).fromNow());
+ setNumberOfReplies(getTopicResults[topicCallHash].value[3].length);
+ }
+ }, [getTopicResults, loading, topicCallHash]);
useEffect(() => {
- if (userAddress !== topicData.userAddress) {
+ if (topicAuthorAddress && userAddress !== topicAuthorAddress) {
dispatch({
type: FETCH_USER_DATABASE,
orbit,
- userAddress: topicData.userAddress,
+ dbName: 'topics',
+ userAddress: topicAuthorAddress,
});
}
- }, [dispatch, orbit, topicData.userAddress, topicId, userAddress]);
+ }, [dispatch, topicAuthorAddress, userAddress]);
useEffect(() => {
const topicFound = topics
.find((topic) => topic.id === topicId);
if (topicFound) {
- setTopicSubject(topicFound);
+ setTopicSubject(topicFound.subject);
}
}, [topicId, topics]);
- return (
- <>
-
-
- {topicSubject && topicSubject.subject}
-
-
- {topicData.username}
- {topicData.numberOfReplies}
- {' '}
- replies
- timestamp
-
- >
- );
+ return useMemo(() => {
+ const handleTopicClick = () => {
+ history.push(`/topics/${topicId}`);
+ };
+
+ return (
+
+
+
+
+
+
+ {topicSubject !== null
+ ? topicSubject
+ : }
+
+
+
+ {t('topic.list.row.topic.id', { id: topicId })}
+
+
+
+
+
+
+
+ {topicAuthor !== null && timeAgo !== null
+ ? t('topic.list.row.author.date', { author: topicAuthor, timeAgo })
+ : }
+
+
+ {numberOfReplies !== null
+ ? (
+
+ {t('topic.list.row.number.of.replies', { numberOfReplies })}
+
+ )
+ : }
+
+
+
+
+
+ );
+ }, [history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicId, topicSubject]);
};
-const TopicData = PropTypes.PropTypes.shape({
- userAddress: PropTypes.string.isRequired,
- username: PropTypes.string.isRequired,
- timestamp: PropTypes.number.isRequired,
- numberOfReplies: PropTypes.number.isRequired,
-});
+TopicListRow.defaultProps = {
+ loading: false,
+};
TopicListRow.propTypes = {
- topicData: TopicData.isRequired,
- topicId: PropTypes.number.isRequired,
+ id: PropTypes.number.isRequired,
+ topicCallHash: PropTypes.string,
+ loading: PropTypes.bool,
};
-export default TopicListRow;
+export default memo(TopicListRow);
diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/styles.css b/packages/concordia-app/src/components/TopicList/TopicListRow/styles.css
new file mode 100644
index 0000000..8e808b6
--- /dev/null
+++ b/packages/concordia-app/src/components/TopicList/TopicListRow/styles.css
@@ -0,0 +1,8 @@
+.topic-metadata {
+ font-size: 12px !important;
+ font-weight: initial;
+}
+
+.list-item {
+ text-align: start;
+}
diff --git a/packages/concordia-app/src/components/TopicList/index.jsx b/packages/concordia-app/src/components/TopicList/index.jsx
index 4450ccd..511ae2b 100644
--- a/packages/concordia-app/src/components/TopicList/index.jsx
+++ b/packages/concordia-app/src/components/TopicList/index.jsx
@@ -1,85 +1,53 @@
import React, {
- useCallback,
- useContext, useEffect, useMemo, useState,
+ useEffect, useMemo, useState,
} from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { List } from 'semantic-ui-react';
-import { useHistory } from 'react-router';
-import AppContext from '../AppContext';
import TopicListRow from './TopicListRow';
-import { PLACEHOLDER_TYPE_TOPIC } from '../../constants/PlaceholderTypes';
-import Placeholder from '../Placeholder';
-import './styles.css';
+import { drizzle } from '../../redux/store';
+
+const { contracts: { Forum: { methods: { getTopic: { cacheCall: getTopicChainData } } } } } = drizzle;
const TopicList = (props) => {
const { topicIds } = props;
- const { drizzle: { contracts: { Forum: { methods: { getTopic } } } } } = useContext(AppContext.Context);
const [getTopicCallHashes, setGetTopicCallHashes] = useState([]);
- const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic);
- const drizzleStatus = useSelector((state) => state.drizzleStatus);
- const history = useHistory();
+ const drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized);
+ const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed);
useEffect(() => {
- // TODO: is the drizzleStatus check necessary?
- if (drizzleStatus.initialized && !drizzleStatus.failed) {
- const newTopicPosted = topicIds
- .some((topicId) => !getTopicCallHashes
+ if (drizzleInitialized && !drizzleInitializationFailed) {
+ const newTopicsFound = topicIds
+ .filter((topicId) => !getTopicCallHashes
.map((getTopicCallHash) => getTopicCallHash.id)
.includes(topicId));
- if (newTopicPosted) {
- setGetTopicCallHashes(topicIds.map((topicId) => {
- const foundGetTopicCallHash = getTopicCallHashes.find((getTopicCallHash) => getTopicCallHash.id === topicId);
-
- if (foundGetTopicCallHash !== undefined) {
- return ({ ...foundGetTopicCallHash });
- }
-
- return ({
- id: topicId,
- hash: getTopic.cacheCall(topicId),
- });
- }));
+ if (newTopicsFound.length > 0) {
+ setGetTopicCallHashes([
+ ...getTopicCallHashes,
+ ...newTopicsFound
+ .map((topicId) => ({
+ id: topicId,
+ hash: getTopicChainData(topicId),
+ })),
+ ]);
}
}
- }, [drizzleStatus.failed, drizzleStatus.initialized, getTopic, getTopicCallHashes, topicIds]);
-
- const handleTopicClick = useCallback((topicId) => {
- history.push(`/topics/${topicId}`);
- }, [history]);
+ }, [drizzleInitializationFailed, drizzleInitialized, getTopicCallHashes, topicIds]);
const topics = useMemo(() => topicIds
.map((topicId) => {
- const getTopicHash = getTopicCallHashes.find((getTopicCallHash) => getTopicCallHash.id === topicId);
-
- if (getTopicHash && getTopicResults[getTopicHash.hash] !== undefined) {
- const topicData = {
- userAddress: getTopicResults[getTopicHash.hash].value[0],
- username: getTopicResults[getTopicHash.hash].value[1],
- timestamp: getTopicResults[getTopicHash.hash].value[2] * 1000,
- numberOfReplies: getTopicResults[getTopicHash.hash].value[3].length,
- };
-
- return (
- handleTopicClick(topicId)}>
-
-
- );
- }
+ const topicHash = getTopicCallHashes.find((getTopicCallHash) => getTopicCallHash.id === topicId);
return (
- handleTopicClick(topicId)}>
-
-
+
);
- }), [getTopicCallHashes, getTopicResults, handleTopicClick, topicIds]);
+ }), [getTopicCallHashes, topicIds]);
return (
diff --git a/packages/concordia-app/src/components/TopicList/styles.css b/packages/concordia-app/src/components/TopicList/styles.css
index 5e461d1..ac3c53c 100644
--- a/packages/concordia-app/src/components/TopicList/styles.css
+++ b/packages/concordia-app/src/components/TopicList/styles.css
@@ -1,7 +1,3 @@
#topic-list{
height: 100%;
}
-
-.list-item {
- text-align: start;
-}
\ No newline at end of file
diff --git a/packages/concordia-app/src/index.jsx b/packages/concordia-app/src/index.jsx
index 536fd4c..0e89763 100644
--- a/packages/concordia-app/src/index.jsx
+++ b/packages/concordia-app/src/index.jsx
@@ -1,24 +1,14 @@
import './utils/wdyr';
import React, { Suspense } from 'react';
import { render } from 'react-dom';
-import { Drizzle } from '@ezerous/drizzle';
-import { Breeze } from '@ezerous/breeze';
import App from './App';
import store from './redux/store';
-import AppContext from './components/AppContext';
-import drizzleOptions from './options/drizzleOptions';
-import breezeOptions from './options/breezeOptions';
import * as serviceWorker from './utils/serviceWorker';
import LoadingScreen from './components/LoadingScreen';
-const drizzle = new Drizzle(drizzleOptions, store);
-const breeze = new Breeze(breezeOptions, store);
-
render(
}>
-
-
-
+
,
document.getElementById('root'),
);
diff --git a/packages/concordia-app/src/layouts/MainLayout/index.jsx b/packages/concordia-app/src/layouts/MainLayout/index.jsx
index 0864457..6236c70 100644
--- a/packages/concordia-app/src/layouts/MainLayout/index.jsx
+++ b/packages/concordia-app/src/layouts/MainLayout/index.jsx
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
-
import MainLayoutMenu from './MainLayoutMenu';
const MainLayout = (props) => {
diff --git a/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js b/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js
index dd5b70c..a7713d9 100644
--- a/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js
+++ b/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js
@@ -10,9 +10,9 @@ import {
import determineKVAddress from '../../utils/orbitUtils';
import { FETCH_USER_DATABASE, UPDATE_ORBIT_DATA } from '../actions/peerDbReplicationActions';
-function* fetchUserDb({ orbit, userAddress }) {
+function* fetchUserDb({ orbit, userAddress, dbName }) {
const peerDbAddress = yield call(determineKVAddress, {
- orbit, dbName: 'topics', userAddress,
+ orbit, dbName, userAddress,
});
yield put(addOrbitDB({ address: peerDbAddress, type: 'keyvalue' }));
diff --git a/packages/concordia-app/src/redux/store.js b/packages/concordia-app/src/redux/store.js
index d0f75f0..8ed1b9e 100644
--- a/packages/concordia-app/src/redux/store.js
+++ b/packages/concordia-app/src/redux/store.js
@@ -1,11 +1,14 @@
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
-import { drizzleReducers, drizzleMiddlewares, generateContractsInitialState } from '@ezerous/drizzle';
-import { breezeReducers } from '@ezerous/breeze';
+import {
+ drizzleReducers, drizzleMiddlewares, generateContractsInitialState, Drizzle,
+} from '@ezerous/drizzle';
+import { Breeze, breezeReducers } from '@ezerous/breeze';
import createSagaMiddleware from 'redux-saga';
import userReducer from './reducers/userReducer';
import rootSaga from './sagas/rootSaga';
import drizzleOptions from '../options/drizzleOptions';
import peerDbReplicationReducer from './reducers/peerDbReplicationReducer';
+import breezeOptions from '../options/breezeOptions';
const initialState = {
contracts: generateContractsInitialState(drizzleOptions),
@@ -24,5 +27,8 @@ const store = configureStore({
preloadedState: initialState,
});
+export const drizzle = new Drizzle(drizzleOptions, store);
+export const breeze = new Breeze(breezeOptions, store);
+
sagaMiddleware.run(rootSaga);
export default store;
diff --git a/packages/concordia-app/src/utils/styles.debug.css b/packages/concordia-app/src/utils/styles.debug.css
new file mode 100644
index 0000000..2488391
--- /dev/null
+++ b/packages/concordia-app/src/utils/styles.debug.css
@@ -0,0 +1,71 @@
+* {
+ outline: 2px dotted red
+}
+
+* * {
+ outline: 2px dotted green
+}
+
+* * * {
+ outline: 2px dotted orange
+}
+
+* * * * {
+ outline: 2px dotted blue
+}
+
+* * * * * {
+ outline: 1px solid red
+}
+
+* * * * * * {
+ outline: 1px solid green
+}
+
+* * * * * * * {
+ outline: 1px solid orange
+}
+
+* * * * * * * * {
+ outline: 1px solid blue
+}
+
+/* Solid Green */
+* *:hover {
+ border: 2px solid #89A81E
+}
+
+/* Solid Orange */
+* * *:hover {
+ border: 2px solid #F34607
+}
+
+/* Solid Blue */
+* * * *:hover {
+ border: 2px solid #5984C3
+}
+
+/* Solid Red */
+* * * * *:hover {
+ border: 2px solid #CD1821
+}
+
+/* Dotted Green */
+* * * * * *:hover {
+ border: 2px dotted #89A81E
+}
+
+/* Dotted Orange */
+* * * * * * *:hover {
+ border: 2px dotted #F34607
+}
+
+/* Dotted Blue */
+* * * * * * * *:hover {
+ border: 2px dotted #5984C3
+}
+
+/* Dotted Red */
+* * * * * * * * *:hover {
+ border: 2px dotted #CD1821
+}
\ No newline at end of file
diff --git a/packages/concordia-app/src/views/Home/index.jsx b/packages/concordia-app/src/views/Home/index.jsx
index a919f97..ce9decf 100644
--- a/packages/concordia-app/src/views/Home/index.jsx
+++ b/packages/concordia-app/src/views/Home/index.jsx
@@ -1,31 +1,32 @@
import React, {
- useContext, useEffect, useMemo, useState,
+ memo, useEffect, useMemo, useState,
} from 'react';
import { Container } from 'semantic-ui-react';
import { useSelector } from 'react-redux';
-import AppContext from '../../components/AppContext';
import Board from './Board';
import './styles.css';
+import { drizzle } from '../../redux/store';
+
+const { contracts: { Forum: { methods: { getNumberOfTopics } } } } = drizzle;
const Home = () => {
- const { drizzle: { contracts: { Forum: { methods: { getNumberOfTopics } } } } } = useContext(AppContext.Context);
const [numberOfTopicsCallHash, setNumberOfTopicsCallHash] = useState('');
const getNumberOfTopicsResults = useSelector((state) => state.contracts.Forum.getNumberOfTopics);
useEffect(() => {
setNumberOfTopicsCallHash(getNumberOfTopics.cacheCall());
- }, [getNumberOfTopics]);
+ }, []);
const numberOfTopics = useMemo(() => (getNumberOfTopicsResults[numberOfTopicsCallHash] !== undefined
? parseInt(getNumberOfTopicsResults[numberOfTopicsCallHash].value, 10)
: null),
[getNumberOfTopicsResults, numberOfTopicsCallHash]);
- return (
+ return useMemo(() => (
{numberOfTopics !== null && }
- );
+ ), [numberOfTopics]);
};
-export default Home;
+export default memo(Home);
diff --git a/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx b/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx
index 2870d1d..4df7a2e 100644
--- a/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx
+++ b/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx
@@ -1,5 +1,5 @@
import React, {
- useCallback, useContext, useEffect, useState,
+ useCallback, useEffect, useState,
} from 'react';
import {
Button, Container, Form, Icon, Input, TextArea,
@@ -7,30 +7,16 @@ import {
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';
import { useSelector } from 'react-redux';
-import AppContext from '../../../components/AppContext';
import './styles.css';
+import { drizzle, breeze } from '../../../redux/store';
+
+const { contracts: { Forum: { methods: { createTopic } } } } = drizzle;
+const { orbit: { stores } } = breeze;
const TopicCreate = (props) => {
const { account } = props;
-
- const {
- drizzle: {
- contracts: {
- Forum: {
- methods: { createTopic },
- },
- },
- },
- breeze: {
- orbit: {
- stores,
- },
- },
- } = useContext(AppContext.Context);
-
const transactionStack = useSelector((state) => state.transactionStack);
const transactions = useSelector((state) => state.transactions);
-
const [subjectInput, setSubjectInput] = useState('');
const [messageInput, setMessageInput] = useState('');
const [topicSubjectInputEmptySubmit, setTopicSubjectInputEmptySubmit] = useState(false);
@@ -95,9 +81,7 @@ const TopicCreate = (props) => {
});
}
}
- }, [
- transactions, transactionStack, history, posting, createTopicCacheSendStackId, subjectInput, messageInput, stores,
- ]);
+ }, [createTopicCacheSendStackId, history, messageInput, posting, subjectInput, transactionStack, transactions]);
const validateAndPost = useCallback(() => {
if (subjectInput === '') {
@@ -112,7 +96,7 @@ const TopicCreate = (props) => {
setPosting(true);
setCreateTopicCacheSendStackId(createTopic.cacheSend(...[], { from: account }));
- }, [account, createTopic, messageInput, subjectInput]);
+ }, [account, messageInput, subjectInput]);
return (
diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx
index 56fd2dc..c5e4687 100644
--- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx
+++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx
@@ -1,18 +1,132 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
+import { useDispatch, useSelector } from 'react-redux';
+import {
+ Container, Dimmer, Icon, Placeholder, Step,
+} from 'semantic-ui-react';
+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;
const TopicView = (props) => {
- const { topicId } = props;
+ const {
+ topicId, topicAuthorAddress: initialTopicAuthorAddress, topicAuthor: initialTopicAuthor,
+ timestamp: initialTimestamp, postIds: initialPostIds,
+ } = props;
+ const drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized);
+ const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed);
+ const userAddress = useSelector((state) => state.user.address);
+ const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic);
+ const topics = useSelector((state) => state.orbitData.topics);
+ const [getTopicCallHash, setGetTopicCallHash] = useState([]);
+ const [topicAuthorAddress, setTopicAuthorAddress] = useState(initialTopicAuthorAddress || null);
+ const [topicAuthor, setTopicAuthor] = useState(initialTopicAuthor || null);
+ const [timestamp, setTimestamp] = useState(initialTimestamp || null);
+ const [postIds, setPostIds] = useState(initialPostIds || null);
+ const [topicSubject, setTopicSubject] = useState(null);
+
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ const shouldGetTopicDataFromChain = topicAuthorAddress === null
+ || topicAuthor === null
+ || timestamp === null
+ || postIds === null;
+
+ if (drizzleInitialized && !drizzleInitializationFailed && shouldGetTopicDataFromChain) {
+ setGetTopicCallHash(getTopicChainData(topicId));
+ }
+ }, [
+ drizzleInitializationFailed, drizzleInitialized, postIds, timestamp, topicAuthor, topicAuthorAddress, topicId,
+ ]);
+
+ useEffect(() => {
+ if (getTopicCallHash && getTopicResults && getTopicResults[getTopicCallHash]) {
+ setTopicAuthorAddress(getTopicResults[getTopicCallHash].value[0]);
+ setTopicAuthor(getTopicResults[getTopicCallHash].value[1]);
+ setTimestamp(getTopicResults[getTopicCallHash].value[2]);
+ setPostIds(getTopicResults[getTopicCallHash].value[3].map((postId) => parseInt(postId, 10)));
+
+ const topicFound = topics
+ .find((topic) => topic.id === topicId);
+
+ if (topicFound === undefined && userAddress !== getTopicResults[getTopicCallHash].value[0]) {
+ dispatch({
+ type: FETCH_USER_DATABASE,
+ orbit,
+ dbName: 'topics',
+ userAddress: getTopicResults[getTopicCallHash].value[0],
+ });
+ }
+ }
+ }, [dispatch, getTopicCallHash, getTopicResults, topicId, topics, userAddress]);
+
+ useEffect(() => {
+ const topicFound = topics
+ .find((topic) => topic.id === topicId);
+
+ if (topicFound) {
+ setTopicSubject(topicFound.subject);
+ }
+ }, [topicId, topics]);
return (
-
- TODO
-
+
+
+
+
+
+
+
+ {topicAuthor || (
+
+
+
+ )}
+
+
+
+
+
+
+ {topicSubject || (
+
+
+
+ )}
+
+
+ {timestamp
+ ? moment(timestamp * 1000).fromNow()
+ : (
+
+
+
+ )}
+
+
+
+
+
+
+
);
};
TopicView.propTypes = {
topicId: PropTypes.number.isRequired,
+ topicAuthorAddress: PropTypes.string,
+ topicAuthor: PropTypes.string,
+ timestamp: PropTypes.number,
+ postIds: PropTypes.arrayOf(PropTypes.number),
};
export default TopicView;
diff --git a/packages/concordia-app/src/views/Topic/TopicView/styles.css b/packages/concordia-app/src/views/Topic/TopicView/styles.css
new file mode 100644
index 0000000..8cd3cd2
--- /dev/null
+++ b/packages/concordia-app/src/views/Topic/TopicView/styles.css
@@ -0,0 +1,12 @@
+#author-placeholder {
+ width: 150px !important;
+}
+
+#subject-placeholder {
+ width: 250px !important;
+}
+
+#date-placeholder {
+ width: 150px !important;
+ margin: 0 auto;
+}
\ No newline at end of file
diff --git a/packages/concordia-app/src/views/Topic/index.jsx b/packages/concordia-app/src/views/Topic/index.jsx
index 9f5b033..0e1900f 100644
--- a/packages/concordia-app/src/views/Topic/index.jsx
+++ b/packages/concordia-app/src/views/Topic/index.jsx
@@ -12,7 +12,7 @@ const Topic = () => {
)
: (
-
+
);
};
diff --git a/yarn.lock b/yarn.lock
index af511d6..74c45ec 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11467,6 +11467,11 @@ mock-fs@^4.1.0:
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.13.0.tgz#31c02263673ec3789f90eb7b6963676aa407a598"
integrity sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA==
+moment@^2.29.1:
+ version "2.29.1"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
+ integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
+
mortice@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mortice/-/mortice-2.0.0.tgz#7be171409c2115561ba3fc035e4527f9082eefde"