diff --git a/README.md b/README.md index 97b0b0c..5ede2d4 100644 --- a/README.md +++ b/README.md @@ -4,28 +4,28 @@ ## Setup ```shell script -cd apella +cd concordia yarn ``` ## Compile contracts ```shell script -cd packages/apella-contracts +cd packages/concordia-contracts yarn compile ``` ## Run app ```shell script -cd packages/apella-app +cd packages/concordia-app yarn start ``` ## Build app ```shell script -cd packages/apella-app +cd packages/concordia-app yarn build ``` diff --git a/packages/concordia-app/package.json b/packages/concordia-app/package.json index 8727d5a..5931f0f 100644 --- a/packages/concordia-app/package.json +++ b/packages/concordia-app/package.json @@ -37,6 +37,7 @@ "lodash": "^4.17.20", "prop-types": "~15.7.2", "react": "~16.13.1", + "react-avatar": "~3.9.7", "react-dom": "~16.13.1", "react-i18next": "^11.7.3", "react-markdown": "^5.0.3", @@ -48,7 +49,7 @@ "react-timeago": "~5.2.0", "redux-saga": "~1.1.3", "semantic-ui-css": "~2.4.1", - "semantic-ui-react": "~1.2.1", + "semantic-ui-react": "~2.0.3", "web3": "~1.3.3" }, "devDependencies": { diff --git a/packages/concordia-app/public/locales/en/translation.json b/packages/concordia-app/public/locales/en/translation.json index 33a1c4d..d37376c 100644 --- a/packages/concordia-app/public/locales/en/translation.json +++ b/packages/concordia-app/public/locales/en/translation.json @@ -69,14 +69,12 @@ "topbar.button.register": "Sign Up", "topic.create.form.content.field.label": "First post content", "topic.create.form.content.field.placeholder": "Message", - "topic.create.form.post.button": "Post", + "topic.create.form.post.button": "Create Topic", "topic.create.form.subject.field.label": "Topic subject", "topic.create.form.subject.field.placeholder": "Subject", - "topic.list.row.author": "by {{author}}", - "topic.list.row.number.of.replies": "{{numberOfReplies}} replies", "topic.list.row.topic.id": "#{{id}}", "username.selector.error.username.empty.message": "Username is required", "username.selector.error.username.taken.message": "The username {{username}} is already taken.", "username.selector.username.field.label": "Username", "username.selector.username.field.placeholder": "Username" -} \ No newline at end of file +} diff --git a/packages/concordia-app/src/assets/About.md b/packages/concordia-app/src/assets/About.md index b9a5885..5b9ad1f 100644 --- a/packages/concordia-app/src/assets/About.md +++ b/packages/concordia-app/src/assets/About.md @@ -23,13 +23,13 @@ authentication that makes trusted, direct voting possible. You can read more about the technological stack in Concordia's [whitepaper][concordia-whitepaper]. ---- +--- Developed by [apostolof][devs-apostolof-profile], [ezerous][devs-ezerous-profile] -[concordia-repository]: https://gitlab.com/ecentrics/apella -[concordia-docker-hub]: https://hub.docker.com/repository/docker/ecentrics/apella-app -[concordia-license]: https://gitlab.com/ecentrics/apella/-/blob/master/LICENSE.md +[concordia-repository]: https://gitlab.com/ecentrics/concordia +[concordia-docker-hub]: https://hub.docker.com/repository/docker/ecentrics/concordia-app +[concordia-license]: https://gitlab.com/ecentrics/concordia/-/blob/master/LICENSE.md [devs-apostolof-profile]: https://gitlab.com/Apostolof [devs-ezerous-profile]: https://gitlab.com/Ezerous [concordia-whitepaper]: https://whitepaper.concordia.ecentrics.net diff --git a/packages/concordia-app/src/assets/css/index.css b/packages/concordia-app/src/assets/css/index.css index 31db996..cd7fb04 100644 --- a/packages/concordia-app/src/assets/css/index.css +++ b/packages/concordia-app/src/assets/css/index.css @@ -1,6 +1,16 @@ +:root { + --primary-color: #EA6954; + --primary-color-highlighted: #DB5844; + --secondary-color: #0B2540; + --secondary-color-highlighted: #061A30; +} + body.app { + height: auto; + padding-bottom: 4rem; overflow: auto; margin: 0; + background: #E6E6E6; } div { @@ -19,3 +29,41 @@ div { color: gray; font-style: italic; } + +.primary-button{ + color: white !important; + background-color: var(--primary-color) !important; +} + +.primary-button:hover { + background-color: var(--primary-color-highlighted) !important; +} + +.secondary-button{ + color: white !important; + background-color: var(--secondary-color) !important; +} + +.secondary-button:hover { + background-color: var(--secondary-color-highlighted) !important; +} + +.skip-button { + color: var(--secondary-color) !important; + background-color: white !important; + box-shadow: 0 0 0 1px var(--secondary-color) inset !important; +} + +.skip-button:hover { + color: var(--secondary-color-highlighted) !important; + box-shadow: 0 0 0 1px var(--secondary-color-highlighted) inset !important; +} + +.unselectable { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} diff --git a/packages/concordia-app/src/assets/particles.js b/packages/concordia-app/src/assets/particles.js index d4ce239..68a19b7 100644 --- a/packages/concordia-app/src/assets/particles.js +++ b/packages/concordia-app/src/assets/particles.js @@ -16,7 +16,7 @@ const particlesOptions = { speed: 0.12, }, size: { - value: 1, + value: 1.5, }, opacity: { anim: { diff --git a/packages/concordia-app/src/components/ClearDatabasesModal/index.jsx b/packages/concordia-app/src/components/ClearDatabasesModal/index.jsx index 26e47fe..db2aa72 100644 --- a/packages/concordia-app/src/components/ClearDatabasesModal/index.jsx +++ b/packages/concordia-app/src/components/ClearDatabasesModal/index.jsx @@ -126,10 +126,10 @@ const ClearDatabasesModal = (props) => { {!isClearing && ( - - diff --git a/packages/concordia-app/src/components/InitializationScreen/CustomLoader/index.jsx b/packages/concordia-app/src/components/InitializationScreen/CustomLoader/index.jsx index e64aa29..d97d214 100644 --- a/packages/concordia-app/src/components/InitializationScreen/CustomLoader/index.jsx +++ b/packages/concordia-app/src/components/InitializationScreen/CustomLoader/index.jsx @@ -8,7 +8,7 @@ import metamaskLogo from '../../../assets/images/metamask_logo.svg'; import ethereumLogo from '../../../assets/images/ethereum_logo.svg'; import ipfsLogo from '../../../assets/images/ipfs_logo.svg'; import orbitdbLogo from '../../../assets/images/orbitdb_logo.svg'; -import appLogo from '../../../assets/images/app_logo.svg'; +import appLogo from '../../../assets/images/app_logo_circle.svg'; const LoadingComponent = (props) => { useEffect(() => function cleanup() { diff --git a/packages/concordia-app/src/components/LoadingScreen.jsx b/packages/concordia-app/src/components/LoadingScreen.jsx index da719f1..a24d278 100644 --- a/packages/concordia-app/src/components/LoadingScreen.jsx +++ b/packages/concordia-app/src/components/LoadingScreen.jsx @@ -1,9 +1,8 @@ import React from 'react'; +import { Loader } from 'semantic-ui-react'; const LoadingScreen = () => ( -
- Loading -
+ ); export default LoadingScreen; diff --git a/packages/concordia-app/src/components/PostCreate/index.jsx b/packages/concordia-app/src/components/PostCreate/index.jsx index b912417..32d2915 100644 --- a/packages/concordia-app/src/components/PostCreate/index.jsx +++ b/packages/concordia-app/src/components/PostCreate/index.jsx @@ -2,7 +2,7 @@ import React, { memo, useCallback, useEffect, useState, } from 'react'; import { - Button, Feed, Form, Icon, Image, TextArea, + Button, Feed, Form, Icon, TextArea, } from 'semantic-ui-react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; @@ -12,7 +12,6 @@ import { POSTS_DATABASE, USER_DATABASE } from 'concordia-shared/src/constants/or import { POST_CREATED_EVENT } from 'concordia-shared/src/constants/contracts/events/ForumContractEvents'; import determineKVAddress from '../../utils/orbitUtils'; import { FETCH_USER_DATABASE } from '../../redux/actions/peerDbReplicationActions'; -import { USER_PROFILE_PICTURE } from '../../constants/orbit/UserDatabaseKeys'; import { breeze, drizzle } from '../../redux/store'; import './styles.css'; import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../constants/TransactionStatus'; @@ -28,7 +27,6 @@ const PostCreate = (props) => { const transactionStack = useSelector((state) => state.transactionStack); const transactions = useSelector((state) => state.transactions); const [postContent, setPostContent] = useState(''); - const [userProfilePictureUrl, setUserProfilePictureUrl] = useState(); const [createPostCacheSendStackId, setCreatePostCacheSendStackId] = useState(''); const [posting, setPosting] = useState(false); const [storingPost, setStoringPost] = useState(false); @@ -44,9 +42,7 @@ const PostCreate = (props) => { const userFound = users .find((user) => user.id === userOrbitAddress); - if (userFound) { - setUserProfilePictureUrl(userFound[USER_PROFILE_PICTURE]); - } else { + if (!userFound) { dispatch({ type: FETCH_USER_DATABASE, orbit, @@ -122,23 +118,6 @@ const PostCreate = (props) => { return ( - - {userProfilePictureUrl - ? ( - - ) - : ( - - )} -
@@ -152,12 +131,12 @@ const PostCreate = (props) => { />
- + - @@ -167,7 +146,7 @@ const PostCreate = (props) => { - +
diff --git a/packages/concordia-app/src/components/PostCreate/styles.css b/packages/concordia-app/src/components/PostCreate/styles.css index 0bf8ac6..8772846 100644 --- a/packages/concordia-app/src/components/PostCreate/styles.css +++ b/packages/concordia-app/src/components/PostCreate/styles.css @@ -1,7 +1,3 @@ -.post-profile-picture { - margin: 5px 0 0 0; -} - .post-summary-meta-index { float: right; font-size: 12px; @@ -11,3 +7,12 @@ .like:hover .icon { color: #fff !important; } + +#post-button-div { + float: right; + margin: 1rem 0 4rem 0; +} + +#post-button-div button { + margin: 0; +} diff --git a/packages/concordia-app/src/components/PostList/PostListRow/index.jsx b/packages/concordia-app/src/components/PostList/PostListRow/index.jsx index de93824..e2a86b7 100644 --- a/packages/concordia-app/src/components/PostList/PostListRow/index.jsx +++ b/packages/concordia-app/src/components/PostList/PostListRow/index.jsx @@ -2,7 +2,7 @@ import React, { memo, useEffect, useMemo, useState, useCallback, } from 'react'; import { - Dimmer, Icon, Image, Feed, Placeholder, Ref, + Dimmer, Feed, Placeholder, Ref, } from 'semantic-ui-react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; @@ -15,8 +15,9 @@ import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationAct import { breeze } from '../../../redux/store'; import './styles.css'; import determineKVAddress from '../../../utils/orbitUtils'; -import { USER_PROFILE_PICTURE } from '../../../constants/orbit/UserDatabaseKeys'; import { POST_CONTENT } from '../../../constants/orbit/PostsDatabaseKeys'; +import ProfileImage from '../../ProfileImage'; +import PostVoting from '../PostVoting'; const { orbit } = breeze; @@ -90,34 +91,6 @@ const PostListRow = (props) => { } }, [postAuthorAddress, users]); - const authorAvatar = useMemo(() => (postAuthorMeta !== null && postAuthorMeta[USER_PROFILE_PICTURE] - ? ( - - ) - : ( - - )), [postAuthorMeta]); - - const authorAvatarLink = useMemo(() => { - if (postAuthorAddress) { - return ( - - {authorAvatar} - - ); - } - - return authorAvatar; - }, [authorAvatar, postAuthorAddress]); - const focusRef = useCallback((node) => { if (focus && node !== null) { node.scrollIntoView({ behavior: 'smooth' }); @@ -130,13 +103,20 @@ const PostListRow = (props) => { blurring dimmed={loading} id={`post-${postId}`} + className="post-list-row" > - {authorAvatarLink} + - + @@ -147,7 +127,7 @@ const PostListRow = (props) => { ? ( <> {postAuthor} - + @@ -159,10 +139,10 @@ const PostListRow = (props) => { ? postContent : } + - ), [ - authorAvatarLink, focusRef, loading, postAuthor, postAuthorAddress, postContent, postId, postIndex, t, timeAgo, + ), [focusRef, loading, postAuthor, postAuthorAddress, postAuthorMeta, postContent, postId, postIndex, t, timeAgo, topicId, ]); }; diff --git a/packages/concordia-app/src/components/PostList/PostListRow/styles.css b/packages/concordia-app/src/components/PostList/PostListRow/styles.css index f6760c2..d360343 100644 --- a/packages/concordia-app/src/components/PostList/PostListRow/styles.css +++ b/packages/concordia-app/src/components/PostList/PostListRow/styles.css @@ -1,9 +1,36 @@ +.post-list-row { + padding: 1.2rem 0.1rem 1.2rem 0.6rem !important; +} + .post-profile-picture { - margin: 5px 0 0 0; + margin-top: 0.9rem; +} + +.post-content { + margin-left: 1.3rem !important; +} + +.post-content > div.summary { + font-size: 1rem !important; +} + +.post-content > div.extra { + padding-right: 1rem !important; +} + +.post-content .placeholder { + margin: 0.5rem 0 0; + font-size: 2rem !important; +} + +.post-content .placeholder > .line{ + height: auto; } .post-summary-meta-index { float: right; - font-size: 12px; + width: 3rem; + text-align: right; + font-size: 0.75rem !important; opacity: 0.4; } diff --git a/packages/concordia-app/src/components/PostList/PostVoting/index.jsx b/packages/concordia-app/src/components/PostList/PostVoting/index.jsx new file mode 100644 index 0000000..68a6178 --- /dev/null +++ b/packages/concordia-app/src/components/PostList/PostVoting/index.jsx @@ -0,0 +1,177 @@ +import React, { + memo, useCallback, useEffect, useMemo, useState, +} from 'react'; +import { Button, Popup } from 'semantic-ui-react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { POST_VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; +import { drizzle } from '../../../redux/store'; +import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../../constants/TransactionStatus'; +import './styles.css'; + +const CHOICE_DEFAULT = '0'; +const CHOICE_UP = '1'; +const CHOICE_DOWN = '2'; + +const { + contracts: { + [POST_VOTING_CONTRACT]: { + methods: { + getVote: { cacheCall: getVoteChainData }, + getTotalVoteCount: { cacheCall: getTotalVoteCountChainData }, + getUpvoteCount: { cacheCall: getUpvoteCountChainData }, + getDownvoteCount: { cacheCall: getDownvoteCountChainData }, + upvote, downvote, unvote, + }, + }, + }, +} = drizzle; + +const PostVoting = (props) => { + const { postId, postAuthorAddress } = props; + const drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized); + const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed); + const hasSignedUp = useSelector((state) => state.user.hasSignedUp); + const userAccount = useSelector((state) => state.accounts[0]); + + // Current votes + const [getVoteCallHash, setGetVoteCallHash] = useState(null); + const [getTotalVoteCountCallHash, setGetTotalVoteCountCallHash] = useState(null); + const [getUpvoteCountCallHash, setGetUpvoteCountCallHash] = useState(null); + const [getDownvoteCountCallHash, setGetDownvoteCountCallHash] = useState(null); + + const getVoteResult = useSelector((state) => state.contracts[POST_VOTING_CONTRACT].getVote[getVoteCallHash]); + const getTotalVoteCountResult = useSelector((state) => state.contracts[POST_VOTING_CONTRACT].getTotalVoteCount[getTotalVoteCountCallHash]); + const getUpvoteCountResult = useSelector((state) => state.contracts[POST_VOTING_CONTRACT].getUpvoteCount[getUpvoteCountCallHash]); + const getDownvoteCountResult = useSelector((state) => state.contracts[POST_VOTING_CONTRACT].getDownvoteCount[getDownvoteCountCallHash]); + + const [ownVote, setOwnVote] = useState(null); + const [totalVoteCount, setTotalVoteCount] = useState(null); + const [upvoteCount, setUpvoteCount] = useState(null); + const [downvoteCount, setDownvoteCount] = useState(null); + + // Voting + const transactionStack = useSelector((state) => state.transactionStack); + const transactions = useSelector((state) => state.transactions); + const [voting, setVoting] = useState(false); + const [createVoteCacheSendStackId, setVoteCacheSendStackId] = useState(''); + + // Current votes + useEffect(() => { + if (drizzleInitialized && !drizzleInitializationFailed && postId !== null) { + if (getTotalVoteCountCallHash === null) setGetTotalVoteCountCallHash(getTotalVoteCountChainData(postId)); + if (getUpvoteCountCallHash === null) setGetUpvoteCountCallHash(getUpvoteCountChainData(postId)); + if (getDownvoteCountCallHash === null) setGetDownvoteCountCallHash(getDownvoteCountChainData(postId)); + } + }, [drizzleInitializationFailed, drizzleInitialized, getDownvoteCountCallHash, + getTotalVoteCountCallHash, getUpvoteCountCallHash, postId]); + + useEffect(() => { + const shouldGetOwnVoteFromChain = ownVote === null; + + if (drizzleInitialized && !drizzleInitializationFailed && shouldGetOwnVoteFromChain + && postId !== null && userAccount !== null && getVoteCallHash === null) { + setGetVoteCallHash(getVoteChainData(postId, userAccount)); + } + }, [drizzleInitializationFailed, drizzleInitialized, getVoteCallHash, ownVote, postId, userAccount]); + + useEffect(() => { + if (getVoteResult) { + setOwnVote(getVoteResult.value); + } + }, [getVoteResult]); + + useEffect(() => { + if (getTotalVoteCountResult) { + setTotalVoteCount(getTotalVoteCountResult.value); + } + }, [getTotalVoteCountResult]); + + useEffect(() => { + if (getUpvoteCountResult) { + setUpvoteCount(getUpvoteCountResult.value); + } + }, [getUpvoteCountResult]); + + useEffect(() => { + if (getDownvoteCountResult) { + setDownvoteCount(getDownvoteCountResult.value); + } + }, [getDownvoteCountResult]); + + // Voting + useEffect(() => { + if (voting && transactionStack && transactionStack[createVoteCacheSendStackId] + && transactions[transactionStack[createVoteCacheSendStackId]]) { + if (transactions[transactionStack[createVoteCacheSendStackId]].status === TRANSACTION_SUCCESS + || transactions[transactionStack[createVoteCacheSendStackId]].status === TRANSACTION_ERROR) { + setVoting(false); + } + } + }, [createVoteCacheSendStackId, transactionStack, transactions, voting]); + + const vote = useCallback((choice) => { + if (voting) return; + + setVoting(true); + if ((ownVote === CHOICE_DEFAULT || ownVote === CHOICE_DOWN) && choice === CHOICE_UP) setVoteCacheSendStackId(upvote.cacheSend(...[postId], { from: userAccount })); + else if ((ownVote === CHOICE_DEFAULT || ownVote === CHOICE_UP) && choice === CHOICE_DOWN) setVoteCacheSendStackId(downvote.cacheSend(...[postId], { from: userAccount })); + else if ((ownVote === CHOICE_UP && choice === CHOICE_UP) || (ownVote === CHOICE_DOWN && choice === CHOICE_DOWN)) setVoteCacheSendStackId(unvote.cacheSend(...[postId], { from: userAccount })); + }, [ownVote, postId, userAccount, voting]); + + const disableVoting = userAccount === null || !hasSignedUp || postAuthorAddress === null || userAccount === postAuthorAddress; + return useMemo(() => ( +
+
+ ), [disableVoting, downvoteCount, ownVote, totalVoteCount, upvoteCount, vote]); +}; + +PostVoting.propTypes = { + postId: PropTypes.number.isRequired, + postAuthorAddress: PropTypes.string, + totalVoteCount: PropTypes.number, +}; + +export default memo(PostVoting); diff --git a/packages/concordia-app/src/components/PostList/PostVoting/styles.css b/packages/concordia-app/src/components/PostList/PostVoting/styles.css new file mode 100644 index 0000000..340e95d --- /dev/null +++ b/packages/concordia-app/src/components/PostList/PostVoting/styles.css @@ -0,0 +1,20 @@ +.post-voting { + float: left; + margin-top: 1.2rem; +} + +.post-voting > button { + margin: 0 !important; +} + +.post-voting > span{ + vertical-align: middle; +} + +.upvote-count { + color: #21ba45; +} + +.downvote-count { + color: #db2828; +} diff --git a/packages/concordia-app/src/components/PostList/index.jsx b/packages/concordia-app/src/components/PostList/index.jsx index 504a3a7..e2ca121 100644 --- a/packages/concordia-app/src/components/PostList/index.jsx +++ b/packages/concordia-app/src/components/PostList/index.jsx @@ -3,7 +3,9 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; -import { Dimmer, Feed, Loader } from 'semantic-ui-react'; +import { + Dimmer, Feed, Loader, +} from 'semantic-ui-react'; import { FORUM_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; import PostListRow from './PostListRow'; import { drizzle } from '../../redux/store'; diff --git a/packages/concordia-app/src/components/ProfileImage.jsx b/packages/concordia-app/src/components/ProfileImage.jsx new file mode 100644 index 0000000..5c78f66 --- /dev/null +++ b/packages/concordia-app/src/components/ProfileImage.jsx @@ -0,0 +1,52 @@ +import React, { useMemo } from 'react'; +import Avatar from 'react-avatar'; +import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { USER_PROFILE_PICTURE } from '../constants/orbit/UserDatabaseKeys'; + +const ProfileImage = (props) => { + const { + topicAuthorAddress, topicAuthor, topicAuthorMeta, avatarUrl, size, link, + } = props; + + const stopClickPropagation = (event) => { + event.stopPropagation(); + }; + + const authorAvatar = useMemo(() => { + let profileImageUrl = ''; + if (avatarUrl) profileImageUrl = avatarUrl; + else if (topicAuthorMeta && topicAuthorMeta[USER_PROFILE_PICTURE]) profileImageUrl = topicAuthorMeta[USER_PROFILE_PICTURE]; + + return ( + + ); + }, [avatarUrl, size, topicAuthor, topicAuthorMeta]); + + return useMemo(() => { + if (link && topicAuthorAddress) { + return ( + + {authorAvatar} + + ); + } + return authorAvatar; + }, [authorAvatar, link, topicAuthorAddress]); +}; + +ProfileImage.propTypes = { + topicAuthorAddress: PropTypes.string, + topicAuthor: PropTypes.string, + topicAuthorMeta: PropTypes.shape({ id: PropTypes.string, profile_picture: PropTypes.string }), + avatarUrl: PropTypes.string, + size: PropTypes.string, + link: PropTypes.bool, +}; + +export default ProfileImage; diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index 1a50ba9..6146736 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, Image, List, Placeholder, + Dimmer, Grid, Icon, Item, List, Placeholder, Segment, } from 'semantic-ui-react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; @@ -12,11 +12,11 @@ import { useDispatch, useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; import { FORUM_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; import { TOPICS_DATABASE, USER_DATABASE } from 'concordia-shared/src/constants/orbit/OrbitDatabases'; +import ProfileImage from '../../ProfileImage'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import { breeze } from '../../../redux/store'; import './styles.css'; import determineKVAddress from '../../../utils/orbitUtils'; -import { USER_PROFILE_PICTURE } from '../../../constants/orbit/UserDatabaseKeys'; import { TOPIC_SUBJECT } from '../../../constants/orbit/TopicsDatabaseKeys'; const { orbit } = breeze; @@ -42,7 +42,7 @@ const TopicListRow = (props) => { setTopicAuthorAddress(getTopicResults[topicCallHash].value[0]); setTopicAuthor(getTopicResults[topicCallHash].value[1]); setTimeAgo(getTopicResults[topicCallHash].value[2] * 1000); - setNumberOfReplies(getTopicResults[topicCallHash].value[3].length); + setNumberOfReplies(getTopicResults[topicCallHash].value[3].length - 1); } }, [getTopicResults, loading, topicCallHash]); @@ -94,87 +94,78 @@ const TopicListRow = (props) => { event.stopPropagation(); }; - const authorAvatar = useMemo(() => (topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] - ? ( - - ) - : ( - - )), [topicAuthorMeta]); - - const authorAvatarLink = useMemo(() => { - if (topicAuthorAddress) { - return ( - - {authorAvatar} - - ); - } - - return authorAvatar; - }, [authorAvatar, topicAuthorAddress]); - return useMemo(() => { const handleTopicClick = () => { history.push(`/topics/${topicId}`); }; - return ( - - {authorAvatarLink} - - - - - {topicSubject !== null - ? topicSubject - : } - - - - {t('topic.list.row.topic.id', { id: topicId })} - - - - - - - - {topicAuthor !== null && timeAgo !== null - ? ( -
- {t('topic.list.row.author', { author: topicAuthor })} - ,  - -
- ) - : } -
- - {numberOfReplies !== null - ? ( - - {t('topic.list.row.number.of.replies', { numberOfReplies })} - - ) - : } - -
-
-
+ + + + + + + + + + + + + {topicSubject !== null + ? topicSubject + : } + + + + {t('topic.list.row.topic.id', { id: topicId })} + + + + + + {topicAuthor !== null && timeAgo !== null + ? ( +
+ +  •  + + {topicAuthor} + +
+ ) + : } +
+ + {numberOfReplies !== null + ? ( + + +   + { numberOfReplies } + + ) + : ( + + + + )} + +
+
+
+
+ +
+
); - }, [authorAvatarLink, history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicId, topicSubject]); + }, [history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicAuthorAddress, topicAuthorMeta, topicId, topicSubject]); }; TopicListRow.defaultProps = { diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/styles.css b/packages/concordia-app/src/components/TopicList/TopicListRow/styles.css index 6f92675..2a7dda0 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/styles.css +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/styles.css @@ -1,21 +1,55 @@ -.topic-metadata { - font-size: 12px !important; - font-weight: initial; -} - -.list-item { +.topic-row { display: flex !important; text-align: start; -} - -.profile-picture { cursor: pointer; - max-width: 36px; - max-height: 36px; - margin: 0; - vertical-align: middle; } -.list-content { +.topic-row-segment { flex-grow: 1; } + +.topic-row-segment:hover { + background-color: #F7F7F7; +} + +.topic-row-segment div { + color: black; +} + +.topic-row-segment .placeholder{ + font-size: 0.9rem; +} + +.topic-row-segment .placeholder>.line { + background-color:transparent; +} + +.topic-row-avatar { + margin: auto; + padding-left: 1rem !important; + padding-right: 0 !important; + font-size: 2rem; + color: red; +} + +.topic-row-avatar span{ + color: white; +} + +.topic-row-content { + padding-left: 2rem !important; +} + +.topic-row-subject { + font-size: 1.4rem; + font-weight: bold; +} + +.topic-row-metadata { + font-size: 0.9em !important; +} + +.replies-placeholder{ + float:right; + width: 6rem; +} diff --git a/packages/concordia-app/src/components/TopicList/index.jsx b/packages/concordia-app/src/components/TopicList/index.jsx index 8a6b9fd..cfc85fe 100644 --- a/packages/concordia-app/src/components/TopicList/index.jsx +++ b/packages/concordia-app/src/components/TopicList/index.jsx @@ -7,6 +7,7 @@ import { List } from 'semantic-ui-react'; import { FORUM_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; import TopicListRow from './TopicListRow'; import { drizzle } from '../../redux/store'; +import './styles.css'; const { contracts: { [FORUM_CONTRACT]: { methods: { getTopic: { cacheCall: getTopicChainData } } } } } = drizzle; @@ -51,7 +52,7 @@ const TopicList = (props) => { }), [getTopicCallHashes, topicIds]); return ( - + {topics} ); diff --git a/packages/concordia-app/src/components/TopicList/styles.css b/packages/concordia-app/src/components/TopicList/styles.css index ac3c53c..5fae8a7 100644 --- a/packages/concordia-app/src/components/TopicList/styles.css +++ b/packages/concordia-app/src/components/TopicList/styles.css @@ -1,3 +1,6 @@ #topic-list{ height: 100%; + margin-bottom: 4em; + clear: both; } + diff --git a/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx b/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx index b2453f0..012fad6 100644 --- a/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx +++ b/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx @@ -42,17 +42,6 @@ const MainLayoutMenu = () => { app_logo - {hasSignedUp && history.location.pathname === '/home' && ( - history.push('/topics/new')} - position="right" - > - {t('topbar.button.create.topic')} - - )} {hasSignedUp ? ( ({ href, children }) => ( {children} @@ -26,7 +28,7 @@ const About = () => { return (
- + {`v${process.env.REACT_APP_VERSION}`}
{ const { numberOfTopics } = props; - const userHasSignedUp = useSelector((state) => state.user.hasSignedUp); + const hasSignedUp = useSelector((state) => state.user.hasSignedUp); + const history = useHistory(); const { t } = useTranslation(); - const boardContents = useMemo(() => { - if (numberOfTopics > 0) { - return (); - } if (!userHasSignedUp) { - return ( -
-
- {t('board.header.no.topics.message')} -
-
- {t('board.sub.header.no.topics.guest')} -
-
- ); - } - - return ( -
-
- {t('board.header.no.topics.message')} -
-
- {t('board.sub.header.no.topics.user')} -
-
- ); - }, [numberOfTopics, userHasSignedUp, t]); + const boardContents = useMemo(() => ( + <> + {hasSignedUp + ? ( + +
); diff --git a/packages/concordia-app/src/views/Topic/TopicCreate/styles.css b/packages/concordia-app/src/views/Topic/TopicCreate/styles.css index f418207..5d078ac 100644 --- a/packages/concordia-app/src/views/Topic/TopicCreate/styles.css +++ b/packages/concordia-app/src/views/Topic/TopicCreate/styles.css @@ -1,6 +1,16 @@ +#new-topic-header { + padding: 2rem; + text-align: center; +} + .form-textarea-required { color: rgb(159, 58, 56) !important; outline-color: rgb(159, 58, 56) !important; border-color: rgb(224, 180, 180) !important; background-color: rgb(255, 246, 246) !important; } + +#create-topic-button { + float: right; + margin: 1rem 0 4rem 0; +} diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx index 406bb2e..f77715a 100644 --- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx +++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx @@ -2,11 +2,10 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { - Container, Dimmer, Icon, Image, Placeholder, Step, + Container, Dimmer, Divider, Header, Icon, Placeholder, Segment, } from 'semantic-ui-react'; import { Link } from 'react-router-dom'; import { useHistory } from 'react-router'; -import TimeAgo from 'react-timeago'; import { FORUM_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; import { TOPICS_DATABASE, USER_DATABASE } from 'concordia-shared/src/constants/orbit/OrbitDatabases'; import { breeze, drizzle } from '../../../redux/store'; @@ -14,7 +13,6 @@ import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationAct import './styles.css'; import PostList from '../../../components/PostList'; import determineKVAddress from '../../../utils/orbitUtils'; -import { USER_PROFILE_PICTURE } from '../../../constants/orbit/UserDatabaseKeys'; import { TOPIC_SUBJECT } from '../../../constants/orbit/TopicsDatabaseKeys'; import PostCreate from '../../../components/PostCreate'; @@ -36,9 +34,9 @@ const TopicView = (props) => { const [getTopicCallHash, setGetTopicCallHash] = useState([]); const [topicAuthorAddress, setTopicAuthorAddress] = useState(initialTopicAuthorAddress || null); const [topicAuthor, setTopicAuthor] = useState(initialTopicAuthor || null); - const [topicAuthorMeta, setTopicAuthorMeta] = useState(null); const [timestamp, setTimestamp] = useState(initialTimestamp || null); const [postIds, setPostIds] = useState(initialPostIds || null); + const [numberOfReplies, setReplyCount] = useState(0); const [topicSubject, setTopicSubject] = useState(null); const history = useHistory(); const dispatch = useDispatch(); @@ -66,7 +64,9 @@ const TopicView = (props) => { setTopicAuthorAddress(getTopicResults[getTopicCallHash].value[0]); setTopicAuthor(getTopicResults[getTopicCallHash].value[1]); setTimestamp(getTopicResults[getTopicCallHash].value[2] * 1000); - setPostIds(getTopicResults[getTopicCallHash].value[3].map((postId) => parseInt(postId, 10))); + const postIds = getTopicResults[getTopicCallHash].value[3].map((postId) => parseInt(postId, 10)); + setPostIds(postIds); + setReplyCount(postIds.length - 1); const topicFound = topics .find((topic) => topic.id === topicId); @@ -89,9 +89,7 @@ const TopicView = (props) => { const userFound = users .find((user) => user.id === userOrbitAddress); - if (userFound) { - setTopicAuthorMeta(userFound); - } else { + if (!userFound) { dispatch({ type: FETCH_USER_DATABASE, orbit, @@ -115,66 +113,45 @@ const TopicView = (props) => { } }, [topicId, topics]); + const stopClickPropagation = (event) => { + event.stopPropagation(); + }; + return ( - - - - - - {topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] - ? ( - - ) - : ( - - )} - - - - - {topicAuthor || ( - - - - )} - - - - - - - - {topicSubject || ( - - - - )} - - - {timestamp - ? - : ( - - - - )} - - - - - - + + + +
+
+ {topicSubject || ( + + + + )} +
+ +
+ +   + {new Date(timestamp).toLocaleString('el-gr', { hour12: false })} +     + +   + { topicAuthor } +     + +   + { numberOfReplies } +
+
+ +
+ +
+ {topicSubject !== null && postIds !== null && hasSignedUp && (