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
- ? (
-
- )
- : (
-
- )}
-
-
+
-
@@ -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(() => (
+
+
vote(CHOICE_DOWN)}
+ />
+
+
+ {totalVoteCount || 0}
+
+
+ )}
+ disabled={(upvoteCount === null && downvoteCount === null) || (upvoteCount === '0' && downvoteCount === '0')}
+ position="bottom center"
+ >
+ {upvoteCount !== '0' ? (
+
+ +
+ {upvoteCount}
+
+
+ ) : null}
+
+ {downvoteCount !== '0' ? (
+
+ -
+ {downvoteCount}
+
+ ) : null}
+
+ vote(CHOICE_UP)}
+ />
+
+ ), [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 = () => {
- {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
+ ? (
+ history.push('/topics/new')}
+ />
+ )
+ : null}
+ {/* eslint-disable-next-line no-nested-ternary */}
+ {numberOfTopics > 0
+ ? ()
+ : (!hasSignedUp
+ ? (
+
+
+ {t('board.header.no.topics.message')}
+
+
+ {t('board.sub.header.no.topics.guest')}
+
+
+ )
+ : (
+
+
+ {t('board.header.no.topics.message')}
+
+
+ {t('board.sub.header.no.topics.user')}
+
+
+ ))}
+
+ >
+ ), [numberOfTopics, hasSignedUp, t, history]);
return (boardContents);
};
diff --git a/packages/concordia-app/src/views/Home/Board/styles.css b/packages/concordia-app/src/views/Home/Board/styles.css
new file mode 100644
index 0000000..631aa1d
--- /dev/null
+++ b/packages/concordia-app/src/views/Home/Board/styles.css
@@ -0,0 +1,9 @@
+#no-topic-message {
+ padding-top: 7rem;
+ clear: both;
+}
+
+#new-topic-button{
+ float:right;
+ margin-bottom: 2em;
+}
diff --git a/packages/concordia-app/src/views/Profile/GeneralTab/EditInformationModal/index.jsx b/packages/concordia-app/src/views/Profile/GeneralTab/EditInformationModal/index.jsx
index d86f395..12c9837 100644
--- a/packages/concordia-app/src/views/Profile/GeneralTab/EditInformationModal/index.jsx
+++ b/packages/concordia-app/src/views/Profile/GeneralTab/EditInformationModal/index.jsx
@@ -197,10 +197,11 @@ const EditInformationModal = (props) => {
-
+
{t('edit.information.modal.form.cancel.button')}
{
}
}, [dispatch, profileAddress, users]);
- const authorAvatar = useMemo(() => (profileMetadataFetched && userAvatarUrl
- ? (
-
- )
- : (
-
- )), [profileMetadataFetched, userAvatarUrl]);
-
const userLocationCell = useMemo(() => {
if (!profileMetadataFetched) {
return (
@@ -123,7 +106,14 @@ const GeneralTab = (props) => {
- {authorAvatar}
+
+
+
{t('profile.general.tab.username.row.title')}
@@ -172,7 +162,7 @@ const GeneralTab = (props) => {
{t('profile.general.tab.registration.date.row.title')}
- {new Date(userRegistrationTimestamp * 1000).toLocaleString()}
+ {new Date(userRegistrationTimestamp * 1000).toLocaleString('el-gr', { hour12: false })}
@@ -182,6 +172,8 @@ const GeneralTab = (props) => {
{
{isSelf && editInformationModal}
>
- ), [
- authorAvatar, editInformationModal, isSelf, numberOfPosts, numberOfTopics, profileAddress, profileMetadataFetched,
- t, userInfoOrbitAddress, userLocationCell, userPostsOrbitAddress, userRegistrationTimestamp, userTopicsOrbitAddress,
- username,
- ]);
+ ), [editInformationModal, isSelf, numberOfPosts, numberOfTopics, profileAddress, profileMetadataFetched, t, userAvatarUrl, userInfoOrbitAddress, userLocationCell, userPostsOrbitAddress, userRegistrationTimestamp, userTopicsOrbitAddress, username]);
};
GeneralTab.defaultProps = {
diff --git a/packages/concordia-app/src/views/Profile/GeneralTab/styles.css b/packages/concordia-app/src/views/Profile/GeneralTab/styles.css
index 6ea4f94..18a1dcc 100644
--- a/packages/concordia-app/src/views/Profile/GeneralTab/styles.css
+++ b/packages/concordia-app/src/views/Profile/GeneralTab/styles.css
@@ -1,6 +1,7 @@
-.general-tab-profile-picture {
- max-width: 112px;
- max-height: 112px;
- margin: 0;
- vertical-align: middle;
-}
\ No newline at end of file
+.profile-image{
+ padding: 3rem !important;
+}
+
+#edit-info-button {
+ margin: 1rem;
+}
diff --git a/packages/concordia-app/src/views/Profile/index.jsx b/packages/concordia-app/src/views/Profile/index.jsx
index 7751e51..50aff6b 100644
--- a/packages/concordia-app/src/views/Profile/index.jsx
+++ b/packages/concordia-app/src/views/Profile/index.jsx
@@ -12,6 +12,7 @@ import TopicList from '../../components/TopicList';
import PostList from '../../components/PostList';
import GeneralTab from './GeneralTab';
import { GENERAL_TAB, POSTS_TAB, TOPICS_TAB } from '../../constants/ProfileTabs';
+import './styles.css';
const { contracts: { [FORUM_CONTRACT]: { methods: { getUser } } } } = drizzle;
@@ -105,7 +106,7 @@ const Profile = () => {
}, [generalTab, loading, postsTab, t, topicsTab]);
return useMemo(() => (
-
+
), [panes]);
diff --git a/packages/concordia-app/src/views/Profile/styles.css b/packages/concordia-app/src/views/Profile/styles.css
new file mode 100644
index 0000000..efabf36
--- /dev/null
+++ b/packages/concordia-app/src/views/Profile/styles.css
@@ -0,0 +1,3 @@
+#profile-container {
+ height: auto !important;
+}
diff --git a/packages/concordia-app/src/views/Register/PersonalInformationStep/index.jsx b/packages/concordia-app/src/views/Register/PersonalInformationStep/index.jsx
index 5cf10d9..cefb47f 100644
--- a/packages/concordia-app/src/views/Register/PersonalInformationStep/index.jsx
+++ b/packages/concordia-app/src/views/Register/PersonalInformationStep/index.jsx
@@ -150,16 +150,15 @@ const PersonalInformationStep = (props) => {
)}
diff --git a/packages/concordia-app/src/views/Register/SignUpStep/index.jsx b/packages/concordia-app/src/views/Register/SignUpStep/index.jsx
index e4d494c..f3df7c5 100644
--- a/packages/concordia-app/src/views/Register/SignUpStep/index.jsx
+++ b/packages/concordia-app/src/views/Register/SignUpStep/index.jsx
@@ -94,7 +94,7 @@ const SignUpStep = (props) => {
)}
{
loading={!usernameIsChecked}
/>
{
const [currentStep, setCurrentStep] = useState('signup');
@@ -107,7 +107,7 @@ const Register = () => {
{
return (
+
-
-
-
- {t('topic.create.form.post.button')}
-
-
-
-
-
-
+
+
+ {t('topic.create.form.post.button')}
+
+
+
+
+
+
);
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 !== null && postIds !== null && hasSignedUp && (