From 8f5a2e3c0a373c6001f882f5c0ed6b55f2ab1ea7 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Wed, 11 Nov 2020 23:18:57 +0200 Subject: [PATCH 01/14] Cast string id prop to number --- packages/concordia-app/src/views/Topic/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 = () => { ) : ( - + ); }; From 9efbc396fbbb46c33e5655784af0eabed788ac53 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sat, 14 Nov 2020 18:00:13 +0200 Subject: [PATCH 02/14] Use react hooks in LoadingContainer --- .../src/components/LoadingContainer.jsx | 281 +++++++++--------- 1 file changed, 133 insertions(+), 148 deletions(-) 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; From 207ce0e281bd04521146efe54a6dc943dc58aca1 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sat, 14 Nov 2020 18:00:43 +0200 Subject: [PATCH 03/14] Remove drizzle and breeze context --- packages/concordia-app/src/index.jsx | 12 +----------- packages/concordia-app/src/redux/store.js | 10 ++++++++-- 2 files changed, 9 insertions(+), 13 deletions(-) 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/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; From 1b18bd39aadc48bfc40e6e40d9020def1a61bcd2 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sat, 14 Nov 2020 18:02:34 +0200 Subject: [PATCH 04/14] Use memoization to avoid unnecessary renders --- .../TopicList/TopicListRow/index.jsx | 58 +++++++++------ .../src/components/TopicList/index.jsx | 71 ++++++++----------- .../src/layouts/MainLayout/index.jsx | 1 - .../src/views/Home/Board/index.jsx | 2 + .../concordia-app/src/views/Home/index.jsx | 17 +++-- .../src/views/Topic/TopicCreate/index.jsx | 24 ++----- .../concordia-app/src/views/Topic/index.jsx | 2 + 7 files changed, 83 insertions(+), 92 deletions(-) diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index 2fea75c..fec1ce1 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -1,27 +1,44 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { + memo, useEffect, useMemo, useState, +} from 'react'; import { List } from 'semantic-ui-react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import AppContext from '../../AppContext'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; +import { breeze } from '../../../redux/store'; + +const { orbit } = breeze; const TopicListRow = (props) => { - const { topicData, topicId } = props; - const { breeze: { orbit } } = useContext(AppContext.Context); - const [topicSubject, setTopicSubject] = useState(); + const { id: topicId, topicCallHash } = props; + const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic); + const [numberOfReplies, setNumberOfReplies] = useState(null); + const [username, setUsername] = useState(null); + const [topicAuthor, setTopicAuthor] = useState(null); + const [timestamp, setTimestamp] = useState(null); + const [topicSubject, setTopicSubject] = useState(null); const userAddress = useSelector((state) => state.user.address); const topics = useSelector((state) => state.orbitData.topics); const dispatch = useDispatch(); useEffect(() => { - if (userAddress !== topicData.userAddress) { + if (topicCallHash && getTopicResults[topicCallHash] !== undefined) { + setTopicAuthor(getTopicResults[topicCallHash].value[0]); + setUsername(getTopicResults[topicCallHash].value[1]); + setTimestamp(getTopicResults[topicCallHash].value[2] * 1000); + setNumberOfReplies(getTopicResults[topicCallHash].value[3].length); + } + }, [getTopicResults, topicCallHash]); + + useEffect(() => { + if (topicAuthor && userAddress !== topicAuthor) { dispatch({ type: FETCH_USER_DATABASE, orbit, - userAddress: topicData.userAddress, + userAddress: topicAuthor, }); } - }, [dispatch, orbit, topicData.userAddress, topicId, userAddress]); + }, [dispatch, topicAuthor, userAddress]); useEffect(() => { const topicFound = topics @@ -32,33 +49,28 @@ const TopicListRow = (props) => { } }, [topicId, topics]); - return ( + return useMemo(() => ( <> {topicSubject && topicSubject.subject} - {topicData.username} - {topicData.numberOfReplies} + {username} + {numberOfReplies} {' '} replies - timestamp + {timestamp} - ); + ), [topicSubject, username, numberOfReplies, timestamp]); }; -const TopicData = PropTypes.PropTypes.shape({ - userAddress: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - timestamp: PropTypes.number.isRequired, - numberOfReplies: PropTypes.number.isRequired, -}); - TopicListRow.propTypes = { - topicData: TopicData.isRequired, - topicId: PropTypes.number.isRequired, + id: PropTypes.number.isRequired, + topicCallHash: PropTypes.string.isRequired, }; -export default TopicListRow; +TopicListRow.whyDidYouRender = true; + +export default memo(TopicListRow); diff --git a/packages/concordia-app/src/components/TopicList/index.jsx b/packages/concordia-app/src/components/TopicList/index.jsx index 4450ccd..b7ac6c5 100644 --- a/packages/concordia-app/src/components/TopicList/index.jsx +++ b/packages/concordia-app/src/components/TopicList/index.jsx @@ -1,72 +1,57 @@ 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 drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized); + const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed); const history = useHistory(); 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); + const topicHash = 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, - }; + const handleTopicClick = () => { + history.push(`/topics/${topicId}`); + }; + if (topicHash) { return ( - handleTopicClick(topicId)}> - + + ); } @@ -79,7 +64,7 @@ const TopicList = (props) => { /> ); - }), [getTopicCallHashes, getTopicResults, handleTopicClick, topicIds]); + }), [getTopicCallHashes, history, topicIds]); return ( @@ -92,4 +77,6 @@ TopicList.propTypes = { topicIds: PropTypes.arrayOf(PropTypes.number).isRequired, }; +TopicList.whyDidYouRender = true; + export default TopicList; 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/views/Home/Board/index.jsx b/packages/concordia-app/src/views/Home/Board/index.jsx index 7a8b074..8f8814f 100644 --- a/packages/concordia-app/src/views/Home/Board/index.jsx +++ b/packages/concordia-app/src/views/Home/Board/index.jsx @@ -46,4 +46,6 @@ Board.propTypes = { numberOfTopics: PropTypes.number.isRequired, }; +Board.whyDidYouRender = true; + export default Board; diff --git a/packages/concordia-app/src/views/Home/index.jsx b/packages/concordia-app/src/views/Home/index.jsx index a919f97..bb6d17c 100644 --- a/packages/concordia-app/src/views/Home/index.jsx +++ b/packages/concordia-app/src/views/Home/index.jsx @@ -1,31 +1,34 @@ 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; +Home.whyDidYouRender = true; + +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..6b26c4a 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); diff --git a/packages/concordia-app/src/views/Topic/index.jsx b/packages/concordia-app/src/views/Topic/index.jsx index 0e1900f..6770b05 100644 --- a/packages/concordia-app/src/views/Topic/index.jsx +++ b/packages/concordia-app/src/views/Topic/index.jsx @@ -16,4 +16,6 @@ const Topic = () => { ); }; +Topic.whyDidYouRender = true; + export default Topic; From 4b8831306f99b8f5de176e882d128e1c290fe612 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sat, 14 Nov 2020 19:58:06 +0200 Subject: [PATCH 05/14] Add css for style debugging --- .../concordia-app/src/utils/styles.debug.css | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 packages/concordia-app/src/utils/styles.debug.css 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 From a17b3168450657516caf5bbe6fb8c4042f9ae5d4 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sat, 14 Nov 2020 19:59:18 +0200 Subject: [PATCH 06/14] Improve topics list UI --- packages/concordia-app/package.json | 1 + .../public/locales/en/translation.json | 5 +- .../TopicList/TopicListRow/index.jsx | 92 ++++++++++++++----- .../TopicList/TopicListRow/styles.css | 8 ++ .../src/components/TopicList/index.jsx | 31 ++----- .../src/components/TopicList/styles.css | 4 - yarn.lock | 5 + 7 files changed, 94 insertions(+), 52 deletions(-) create mode 100644 packages/concordia-app/src/components/TopicList/TopicListRow/styles.css diff --git a/packages/concordia-app/package.json b/packages/concordia-app/package.json index afb2d22..cdb1b9c 100644 --- a/packages/concordia-app/package.json +++ b/packages/concordia-app/package.json @@ -34,6 +34,7 @@ "i18next-http-backend": "^1.0.21", "level": "~6.0.1", "lodash": "^4.17.20", + "moment": "^2.29.1", "orbit-db-identity-provider": "~0.3.1", "prop-types": "~15.7.2", "react": "~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..107d60d 100644 --- a/packages/concordia-app/public/locales/en/translation.json +++ b/packages/concordia-app/public/locales/en/translation.json @@ -19,5 +19,8 @@ "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.list.row.author.date": "Posted 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/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index fec1ce1..7f83bc3 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -1,34 +1,42 @@ import React, { memo, useEffect, useMemo, useState, } from 'react'; -import { List } from 'semantic-ui-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 TopicListRow = (props) => { - const { id: topicId, topicCallHash } = props; + const { id: topicId, topicCallHash, loading } = props; const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic); const [numberOfReplies, setNumberOfReplies] = useState(null); const [username, setUsername] = useState(null); const [topicAuthor, setTopicAuthor] = useState(null); - const [timestamp, setTimestamp] = 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 (topicCallHash && getTopicResults[topicCallHash] !== undefined) { + if (!loading && topicCallHash && getTopicResults[topicCallHash] !== undefined) { setTopicAuthor(getTopicResults[topicCallHash].value[0]); setUsername(getTopicResults[topicCallHash].value[1]); - setTimestamp(getTopicResults[topicCallHash].value[2] * 1000); + setTimeAgo(moment(getTopicResults[topicCallHash].value[2] * 1000).fromNow()); setNumberOfReplies(getTopicResults[topicCallHash].value[3].length); } - }, [getTopicResults, topicCallHash]); + }, [getTopicResults, loading, topicCallHash]); useEffect(() => { if (topicAuthor && userAddress !== topicAuthor) { @@ -45,30 +53,68 @@ const TopicListRow = (props) => { .find((topic) => topic.id === topicId); if (topicFound) { - setTopicSubject(topicFound); + setTopicSubject(topicFound.subject); } }, [topicId, topics]); - return useMemo(() => ( - <> - - - {topicSubject && topicSubject.subject} - - - {username} - {numberOfReplies} - {' '} - replies - {timestamp} - - - ), [topicSubject, username, numberOfReplies, timestamp]); + return useMemo(() => { + const handleTopicClick = () => { + history.push(`/topics/${topicId}`); + }; + + return ( + + + + + + + + + + {topicSubject !== null + ? topicSubject + : } + + + + {t('topic.list.row.topic.id', { id: topicId })} + + + + + + + + {username !== null && timeAgo !== null + ? t('topic.list.row.author.date', { author: username, timeAgo }) + : } + + + {numberOfReplies !== null + ? ( + + {t('topic.list.row.number.of.replies', { numberOfReplies })} + + ) + : } + + + + + + ); + }, [history, loading, numberOfReplies, t, timeAgo, topicId, topicSubject, username]); +}; + +TopicListRow.defaultProps = { + loading: false, }; TopicListRow.propTypes = { id: PropTypes.number.isRequired, - topicCallHash: PropTypes.string.isRequired, + topicCallHash: PropTypes.string, + loading: PropTypes.bool, }; TopicListRow.whyDidYouRender = true; 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 b7ac6c5..89786df 100644 --- a/packages/concordia-app/src/components/TopicList/index.jsx +++ b/packages/concordia-app/src/components/TopicList/index.jsx @@ -4,11 +4,7 @@ import React, { import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { List } from 'semantic-ui-react'; -import { useHistory } from 'react-router'; 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; @@ -18,7 +14,6 @@ const TopicList = (props) => { const [getTopicCallHashes, setGetTopicCallHashes] = useState([]); const drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized); const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed); - const history = useHistory(); useEffect(() => { if (drizzleInitialized && !drizzleInitializationFailed) { @@ -44,27 +39,15 @@ const TopicList = (props) => { .map((topicId) => { const topicHash = getTopicCallHashes.find((getTopicCallHash) => getTopicCallHash.id === topicId); - const handleTopicClick = () => { - history.push(`/topics/${topicId}`); - }; - - if (topicHash) { - return ( - - - - ); - } - return ( - handleTopicClick(topicId)}> - - + ); - }), [getTopicCallHashes, history, 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/yarn.lock b/yarn.lock index 1a2a1ea..31f30d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11942,6 +11942,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" From 28f7006f92daf2cf1ee6c8deabbb48f94dd84c75 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sat, 14 Nov 2020 20:00:15 +0200 Subject: [PATCH 07/14] Disable wdyr in components --- .../src/components/TopicList/TopicListRow/index.jsx | 2 -- packages/concordia-app/src/components/TopicList/index.jsx | 2 -- packages/concordia-app/src/views/Home/Board/index.jsx | 2 -- packages/concordia-app/src/views/Home/index.jsx | 2 -- packages/concordia-app/src/views/Topic/index.jsx | 2 -- 5 files changed, 10 deletions(-) diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index 7f83bc3..014baa9 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -117,6 +117,4 @@ TopicListRow.propTypes = { loading: PropTypes.bool, }; -TopicListRow.whyDidYouRender = true; - export default memo(TopicListRow); diff --git a/packages/concordia-app/src/components/TopicList/index.jsx b/packages/concordia-app/src/components/TopicList/index.jsx index 89786df..511ae2b 100644 --- a/packages/concordia-app/src/components/TopicList/index.jsx +++ b/packages/concordia-app/src/components/TopicList/index.jsx @@ -60,6 +60,4 @@ TopicList.propTypes = { topicIds: PropTypes.arrayOf(PropTypes.number).isRequired, }; -TopicList.whyDidYouRender = true; - export default TopicList; diff --git a/packages/concordia-app/src/views/Home/Board/index.jsx b/packages/concordia-app/src/views/Home/Board/index.jsx index 8f8814f..7a8b074 100644 --- a/packages/concordia-app/src/views/Home/Board/index.jsx +++ b/packages/concordia-app/src/views/Home/Board/index.jsx @@ -46,6 +46,4 @@ Board.propTypes = { numberOfTopics: PropTypes.number.isRequired, }; -Board.whyDidYouRender = true; - export default Board; diff --git a/packages/concordia-app/src/views/Home/index.jsx b/packages/concordia-app/src/views/Home/index.jsx index bb6d17c..ce9decf 100644 --- a/packages/concordia-app/src/views/Home/index.jsx +++ b/packages/concordia-app/src/views/Home/index.jsx @@ -29,6 +29,4 @@ const Home = () => { ), [numberOfTopics]); }; -Home.whyDidYouRender = true; - export default memo(Home); diff --git a/packages/concordia-app/src/views/Topic/index.jsx b/packages/concordia-app/src/views/Topic/index.jsx index 6770b05..0e1900f 100644 --- a/packages/concordia-app/src/views/Topic/index.jsx +++ b/packages/concordia-app/src/views/Topic/index.jsx @@ -16,6 +16,4 @@ const Topic = () => { ); }; -Topic.whyDidYouRender = true; - export default Topic; From c5e1aa1f2f23c97f89a27d5859ef4e35b5a04ac4 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sat, 14 Nov 2020 20:15:46 +0200 Subject: [PATCH 08/14] Fix lint errors --- .../concordia-app/src/views/Topic/TopicCreate/index.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx b/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx index 6b26c4a..4df7a2e 100644 --- a/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx +++ b/packages/concordia-app/src/views/Topic/TopicCreate/index.jsx @@ -81,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 === '') { @@ -98,7 +96,7 @@ const TopicCreate = (props) => { setPosting(true); setCreateTopicCacheSendStackId(createTopic.cacheSend(...[], { from: account })); - }, [account, createTopic, messageInput, subjectInput]); + }, [account, messageInput, subjectInput]); return ( From 2968df7b3df9e321b4f88f0e34e5e7e3c59ea37e Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sun, 15 Nov 2020 02:10:59 +0200 Subject: [PATCH 09/14] Refactor to use more descriptive variable names --- .../TopicList/TopicListRow/index.jsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index 014baa9..61029a0 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -19,7 +19,7 @@ const TopicListRow = (props) => { const { id: topicId, topicCallHash, loading } = props; const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic); const [numberOfReplies, setNumberOfReplies] = useState(null); - const [username, setUsername] = useState(null); + const [topicAuthorAddress, setTopicAuthorAddress] = useState(null); const [topicAuthor, setTopicAuthor] = useState(null); const [timeAgo, setTimeAgo] = useState(null); const [topicSubject, setTopicSubject] = useState(null); @@ -31,22 +31,22 @@ const TopicListRow = (props) => { useEffect(() => { if (!loading && topicCallHash && getTopicResults[topicCallHash] !== undefined) { - setTopicAuthor(getTopicResults[topicCallHash].value[0]); - setUsername(getTopicResults[topicCallHash].value[1]); + 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 (topicAuthor && userAddress !== topicAuthor) { + if (topicAuthorAddress && userAddress !== topicAuthorAddress) { dispatch({ type: FETCH_USER_DATABASE, orbit, - userAddress: topicAuthor, + userAddress: topicAuthorAddress, }); } - }, [dispatch, topicAuthor, userAddress]); + }, [dispatch, topicAuthorAddress, userAddress]); useEffect(() => { const topicFound = topics @@ -86,8 +86,8 @@ const TopicListRow = (props) => { - {username !== null && timeAgo !== null - ? t('topic.list.row.author.date', { author: username, timeAgo }) + {topicAuthor !== null && timeAgo !== null + ? t('topic.list.row.author.date', { author: topicAuthor, timeAgo }) : } @@ -104,7 +104,7 @@ const TopicListRow = (props) => { ); - }, [history, loading, numberOfReplies, t, timeAgo, topicId, topicSubject, username]); + }, [history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicId, topicSubject]); }; TopicListRow.defaultProps = { From c2c42bfbd4176fc38d0ca761add74fd25e68497b Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sun, 15 Nov 2020 02:11:13 +0200 Subject: [PATCH 10/14] Remove Placeholder component --- .../src/components/Placeholder/index.jsx | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 packages/concordia-app/src/components/Placeholder/index.jsx 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; From 241fd3285b3a9dd99fe7c9fce5298919678f7b85 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sun, 15 Nov 2020 02:11:37 +0200 Subject: [PATCH 11/14] Init topic view --- .../src/views/Topic/TopicView/index.jsx | 127 +++++++++++++++++- .../src/views/Topic/TopicView/styles.css | 12 ++ 2 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 packages/concordia-app/src/views/Topic/TopicView/styles.css diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx index 56fd2dc..f8cd963 100644 --- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx +++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx @@ -1,18 +1,135 @@ -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, Loader, 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'; + +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]); + + const topicFound = topics + .find((topic) => topic.id === topicId); + + if (topicFound === undefined && userAddress !== getTopicResults[getTopicCallHash].value[0]) { + dispatch({ + type: FETCH_USER_DATABASE, + orbit, + 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 From b09ec87e4704a5268724d3a44cf9633e85d4dd97 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sun, 15 Nov 2020 02:15:53 +0200 Subject: [PATCH 12/14] Fix Dimmer usage --- .../TopicList/TopicListRow/index.jsx | 3 - .../src/views/Topic/TopicView/index.jsx | 88 +++++++++---------- 2 files changed, 41 insertions(+), 50 deletions(-) diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index 61029a0..510ab88 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -64,9 +64,6 @@ const TopicListRow = (props) => { return ( - - - diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx index f8cd963..e9856f6 100644 --- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx +++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx @@ -74,53 +74,47 @@ const TopicView = (props) => { }, [topicId, topics]); return ( - - - - - - - - - - {topicAuthor || ( - - - - )} - - - - - - - - {topicSubject || ( - - - - )} - - - {timestamp - ? moment(timestamp * 1000).fromNow() - : ( - - - - )} - - - - - + + + + + + + + {topicAuthor || ( + + + + )} + + + + + + + {topicSubject || ( + + + + )} + + + {timestamp + ? moment(timestamp * 1000).fromNow() + : ( + + + + )} + + + + + + ); }; From d436bec16a92c926ae9139f0033a0b178220fcb8 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sun, 15 Nov 2020 03:04:21 +0200 Subject: [PATCH 13/14] Add db name to fetchUserDb arguments --- .../src/components/TopicList/TopicListRow/index.jsx | 3 ++- .../concordia-app/src/redux/sagas/peerDbReplicationSaga.js | 4 ++-- packages/concordia-app/src/views/Topic/TopicView/index.jsx | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index 510ab88..758cb68 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -2,7 +2,7 @@ import React, { memo, useEffect, useMemo, useState, } from 'react'; import { - Dimmer, Grid, List, Loader, Placeholder, + Dimmer, Grid, List, Placeholder, } from 'semantic-ui-react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; @@ -43,6 +43,7 @@ const TopicListRow = (props) => { dispatch({ type: FETCH_USER_DATABASE, orbit, + dbName: 'topics', userAddress: topicAuthorAddress, }); } diff --git a/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js b/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js index d5e1165..ae7224a 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 '../../orbit/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(createOrbitDatabase(orbit, { name: peerDbAddress, type: 'keyvalue' })); diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx index e9856f6..abc9f89 100644 --- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx +++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { - Container, Dimmer, Icon, Loader, Placeholder, Step, + Container, Dimmer, Icon, Placeholder, Step, } from 'semantic-ui-react'; import moment from 'moment'; import { breeze, drizzle } from '../../../redux/store'; @@ -49,7 +49,7 @@ const TopicView = (props) => { setTopicAuthorAddress(getTopicResults[getTopicCallHash].value[0]); setTopicAuthor(getTopicResults[getTopicCallHash].value[1]); setTimestamp(getTopicResults[getTopicCallHash].value[2]); - setPostIds(getTopicResults[getTopicCallHash].value[3]); + setPostIds(getTopicResults[getTopicCallHash].value[3].map((postId) => parseInt(postId, 10))); const topicFound = topics .find((topic) => topic.id === topicId); @@ -58,6 +58,7 @@ const TopicView = (props) => { dispatch({ type: FETCH_USER_DATABASE, orbit, + dbName: 'topics', userAddress: getTopicResults[getTopicCallHash].value[0], }); } From 8aad624e92e522ffb124f9a9886b5d6f87564076 Mon Sep 17 00:00:00 2001 From: Apostolof Date: Sun, 15 Nov 2020 03:04:37 +0200 Subject: [PATCH 14/14] Init post list UI --- .../public/locales/en/translation.json | 8 +- .../components/PostList/PostListRow/index.jsx | 103 ++++++++++++++++++ .../PostList/PostListRow/styles.css | 8 ++ .../src/components/PostList/index.jsx | 70 ++++++++++++ .../src/components/PostList/styles.css | 3 + .../src/views/Topic/TopicView/index.jsx | 2 + 6 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 packages/concordia-app/src/components/PostList/PostListRow/index.jsx create mode 100644 packages/concordia-app/src/components/PostList/PostListRow/styles.css create mode 100644 packages/concordia-app/src/components/PostList/index.jsx create mode 100644 packages/concordia-app/src/components/PostList/styles.css 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) => { + ); };