diff --git a/packages/concordia-app/src/components/Placeholder/index.jsx b/packages/concordia-app/src/components/Placeholder/index.jsx new file mode 100644 index 0000000..5c9394e --- /dev/null +++ b/packages/concordia-app/src/components/Placeholder/index.jsx @@ -0,0 +1,49 @@ +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/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx new file mode 100644 index 0000000..b4bc448 --- /dev/null +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -0,0 +1,52 @@ +import React, { useContext, useMemo } from 'react'; +import { List } from 'semantic-ui-react'; +import PropTypes from 'prop-types'; +import AppContext from '../../AppContext'; + +const TopicListRow = (props) => { + const { topicData, topicId } = props; + + const { + breeze: { + orbit: { + stores, + }, + }, + } = useContext(AppContext.Context); + + const topicSubject = useMemo(() => { + const topicsDb = Object.values(stores).find((store) => store.dbname === 'topics'); + + return topicsDb.get(topicId); + }, [stores, topicId]); + + return ( + <> + + + {topicSubject && topicSubject.subject} + + + {topicData.username} + {topicData.numberOfReplies} + {' '} + replies + timestamp + + + ); +}; + +const TopicData = PropTypes.PropTypes.shape({ + userAddress: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + timestamp: PropTypes.string.isRequired, + numberOfReplies: PropTypes.number.isRequired, +}); + +TopicListRow.propTypes = { + topicData: TopicData.isRequired, + topicId: PropTypes.number.isRequired, +}; + +export default TopicListRow; diff --git a/packages/concordia-app/src/components/TopicList/index.jsx b/packages/concordia-app/src/components/TopicList/index.jsx new file mode 100644 index 0000000..354cc14 --- /dev/null +++ b/packages/concordia-app/src/components/TopicList/index.jsx @@ -0,0 +1,79 @@ +import React, { + useCallback, + useContext, useEffect, useMemo, useState, +} from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { List } from 'semantic-ui-react'; +import AppContext from '../AppContext'; +import TopicListRow from './TopicListRow'; +import { PLACEHOLDER_TYPE_TOPIC } from '../../constants/PlaceholderTypes'; +import Placeholder from '../Placeholder'; +import './styles.css'; +import { useHistory } from 'react-router'; + +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(); + + useEffect(() => { + // TODO: is the drizzleStatus check necessary? + if (drizzleStatus.initialized && !drizzleStatus.failed && getTopicCallHashes.length === 0) { + setGetTopicCallHashes(topicIds.map((topicId) => ({ + id: topicId, + hash: getTopic.cacheCall(topicId), + }))); + } + }, [drizzleStatus.failed, drizzleStatus.initialized, getTopic, getTopicCallHashes, topicIds]); + + const handleTopicClick = useCallback((topicId) => { + history.push(`/topics/${topicId}`); + }, [history]); + + 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)}> + + + ); + } + + return ( + handleTopicClick(topicId)}> + + + ); + }), [getTopicCallHashes, getTopicResults, handleTopicClick, topicIds]); + + return ( + + {topics} + + ); +}; + +TopicList.propTypes = { + topicIds: PropTypes.arrayOf(PropTypes.number).isRequired, +}; + +export default TopicList; diff --git a/packages/concordia-app/src/components/TopicList/styles.css b/packages/concordia-app/src/components/TopicList/styles.css new file mode 100644 index 0000000..5e461d1 --- /dev/null +++ b/packages/concordia-app/src/components/TopicList/styles.css @@ -0,0 +1,7 @@ +#topic-list{ + height: 100%; +} + +.list-item { + text-align: start; +} \ No newline at end of file diff --git a/packages/concordia-app/src/constants/PlaceholderTypes.js b/packages/concordia-app/src/constants/PlaceholderTypes.js new file mode 100644 index 0000000..c529cb7 --- /dev/null +++ b/packages/concordia-app/src/constants/PlaceholderTypes.js @@ -0,0 +1,2 @@ +export const PLACEHOLDER_TYPE_TOPIC = 'PLACEHOLDER_TYPE_TOPIC'; +export const PLACEHOLDER_TYPE_POST = 'PLACEHOLDER_TYPE_POST'; diff --git a/packages/concordia-app/src/views/Home/Board/index.jsx b/packages/concordia-app/src/views/Home/Board/index.jsx index 2ef3cc4..7a8b074 100644 --- a/packages/concordia-app/src/views/Home/Board/index.jsx +++ b/packages/concordia-app/src/views/Home/Board/index.jsx @@ -1,20 +1,19 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { Header } from 'semantic-ui-react'; import _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import TopicList from '../../../components/TopicList'; const Board = (props) => { const { numberOfTopics } = props; const userHasSignedUp = useSelector((state) => state.user.hasSignedUp); - const [topicIds, setTopicIds] = useState([]); const { t } = useTranslation(); const boardContents = useMemo(() => { if (numberOfTopics > 0) { - setTopicIds(_.range(0, numberOfTopics)); - - return (
TODO
); + return (); } if (!userHasSignedUp) { return (
@@ -40,11 +39,11 @@ const Board = (props) => { ); }, [numberOfTopics, userHasSignedUp, t]); - return ( -
- {boardContents} -
- ); + return (boardContents); +}; + +Board.propTypes = { + numberOfTopics: PropTypes.number.isRequired, }; export default Board; diff --git a/packages/concordia-app/src/views/Home/index.jsx b/packages/concordia-app/src/views/Home/index.jsx index 42808b0..a919f97 100644 --- a/packages/concordia-app/src/views/Home/index.jsx +++ b/packages/concordia-app/src/views/Home/index.jsx @@ -5,11 +5,12 @@ import { Container } from 'semantic-ui-react'; import { useSelector } from 'react-redux'; import AppContext from '../../components/AppContext'; import Board from './Board'; +import './styles.css'; const Home = () => { - const getNumberOfTopicsResults = useSelector((state) => state.contracts.Forum.getNumberOfTopics); 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()); @@ -21,7 +22,7 @@ const Home = () => { [getNumberOfTopicsResults, numberOfTopicsCallHash]); return ( - + {numberOfTopics !== null && } ); diff --git a/packages/concordia-app/src/views/Home/styles.css b/packages/concordia-app/src/views/Home/styles.css new file mode 100644 index 0000000..217a32d --- /dev/null +++ b/packages/concordia-app/src/views/Home/styles.css @@ -0,0 +1,3 @@ +#home-container { + height: 100%; +} \ No newline at end of file diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx index 4e0710a..56fd2dc 100644 --- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx +++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx @@ -12,7 +12,7 @@ const TopicView = (props) => { }; TopicView.propTypes = { - topicId: PropTypes.number, + topicId: PropTypes.number.isRequired, }; export default TopicView;