mirror of https://gitlab.com/ecentrics/concordia
Apostolos Fanakis
4 years ago
7 changed files with 352 additions and 1 deletions
@ -0,0 +1,46 @@ |
|||||
|
import React, { useMemo } from 'react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import { |
||||
|
Dimmer, Loader, Placeholder, Segment, Tab, |
||||
|
} from 'semantic-ui-react'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
|
||||
|
const CustomLoadingTabPane = (props) => { |
||||
|
const { loading, loadingMessage, children } = props; |
||||
|
const { t } = useTranslation(); |
||||
|
|
||||
|
return useMemo(() => { |
||||
|
if (loading) { |
||||
|
return ( |
||||
|
<Tab.Pane> |
||||
|
<Dimmer active inverted> |
||||
|
<Loader inverted> |
||||
|
{loadingMessage !== undefined |
||||
|
? loadingMessage |
||||
|
: t('custom.loading.tab.pane.default.generic.message')} |
||||
|
</Loader> |
||||
|
</Dimmer> |
||||
|
<Placeholder fluid> |
||||
|
<Placeholder.Line length="very long" /> |
||||
|
<Placeholder.Line length="medium" /> |
||||
|
<Placeholder.Line length="long" /> |
||||
|
</Placeholder> |
||||
|
</Tab.Pane> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<Tab.Pane> |
||||
|
{children} |
||||
|
</Tab.Pane> |
||||
|
); |
||||
|
}, [children, loading, loadingMessage, t]); |
||||
|
}; |
||||
|
|
||||
|
CustomLoadingTabPane.propTypes = { |
||||
|
loading: PropTypes.bool, |
||||
|
loadingMessage: PropTypes.string, |
||||
|
children: PropTypes.element.isRequired, |
||||
|
}; |
||||
|
|
||||
|
export default CustomLoadingTabPane; |
@ -0,0 +1,22 @@ |
|||||
|
export const GENERAL_TAB = { |
||||
|
id: 'general-tab', |
||||
|
intl_display_name_id: 'profile.general.tab.title', |
||||
|
}; |
||||
|
|
||||
|
export const TOPICS_TAB = { |
||||
|
id: 'topics-tab', |
||||
|
intl_display_name_id: 'profile.topics.tab.title', |
||||
|
}; |
||||
|
|
||||
|
export const POSTS_TAB = { |
||||
|
id: 'posts-tab', |
||||
|
intl_display_name_id: 'profile.posts.tab.title', |
||||
|
}; |
||||
|
|
||||
|
const profileTabs = [ |
||||
|
GENERAL_TAB, |
||||
|
TOPICS_TAB, |
||||
|
POSTS_TAB, |
||||
|
]; |
||||
|
|
||||
|
export default profileTabs; |
@ -0,0 +1,155 @@ |
|||||
|
import React, { useEffect, useMemo, useState } from 'react'; |
||||
|
import { |
||||
|
Icon, Image, Placeholder, Table, |
||||
|
} from 'semantic-ui-react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import moment from 'moment'; |
||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||
|
import determineKVAddress from '../../../utils/orbitUtils'; |
||||
|
import databases, { USER_DATABASE } from '../../../constants/OrbitDatabases'; |
||||
|
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; |
||||
|
import { breeze } from '../../../redux/store'; |
||||
|
import { USER_LOCATION, USER_PROFILE_PICTURE } from '../../../constants/UserDatabaseKeys'; |
||||
|
import './styles.css'; |
||||
|
|
||||
|
const { orbit } = breeze; |
||||
|
|
||||
|
const GeneralTab = (props) => { |
||||
|
const { |
||||
|
profileAddress, username, numberOfTopics, numberOfPosts, userRegistrationTimestamp, |
||||
|
} = props; |
||||
|
const [userInfoOrbitAddress, setUserInfoOrbitAddress] = useState(null); |
||||
|
const [userTopicsOrbitAddress, setUserTopicsOrbitAddress] = useState(null); |
||||
|
const [userPostsOrbitAddress, setUserPostsOrbitAddress] = useState(null); |
||||
|
const [profileMeta, setProfileMeta] = useState(null); |
||||
|
const users = useSelector((state) => state.orbitData.users); |
||||
|
const dispatch = useDispatch(); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (profileAddress) { |
||||
|
Promise |
||||
|
.all(databases |
||||
|
.map((database) => determineKVAddress({ |
||||
|
orbit, |
||||
|
dbName: database.address, |
||||
|
userAddress: profileAddress, |
||||
|
}))) |
||||
|
.then((values) => { |
||||
|
const [userOrbitAddress, topicsOrbitAddress, postsOrbitAddress] = values; |
||||
|
setUserInfoOrbitAddress(userOrbitAddress); |
||||
|
setUserTopicsOrbitAddress(topicsOrbitAddress); |
||||
|
setUserPostsOrbitAddress(postsOrbitAddress); |
||||
|
|
||||
|
const userFound = users |
||||
|
.find((user) => user.id === userOrbitAddress); |
||||
|
|
||||
|
if (userFound) { |
||||
|
setProfileMeta(userFound); |
||||
|
} else { |
||||
|
dispatch({ |
||||
|
type: FETCH_USER_DATABASE, |
||||
|
orbit, |
||||
|
dbName: USER_DATABASE, |
||||
|
userAddress: userOrbitAddress, |
||||
|
}); |
||||
|
} |
||||
|
}).catch((error) => { |
||||
|
console.error('Error during determination of key-value DB address:', error); |
||||
|
}); |
||||
|
} |
||||
|
}, [dispatch, profileAddress, users]); |
||||
|
|
||||
|
const authorAvatar = useMemo(() => (profileMeta !== null && profileMeta[USER_PROFILE_PICTURE] |
||||
|
? ( |
||||
|
<Image |
||||
|
className="general-tab-profile-picture" |
||||
|
centered |
||||
|
size="tiny" |
||||
|
src={profileMeta[USER_PROFILE_PICTURE]} |
||||
|
/> |
||||
|
) |
||||
|
: ( |
||||
|
<Icon |
||||
|
name="user circle" |
||||
|
size="massive" |
||||
|
inverted |
||||
|
color="black" |
||||
|
verticalAlign="middle" |
||||
|
/> |
||||
|
)), [profileMeta]); |
||||
|
|
||||
|
return useMemo(() => ( |
||||
|
<Table basic="very" singleLine> |
||||
|
<Table.Body> |
||||
|
<Table.Row textAlign="center"> |
||||
|
<Table.Cell colSpan="3">{authorAvatar}</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>Username:</strong></Table.Cell> |
||||
|
<Table.Cell>{username}</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>Account address:</strong></Table.Cell> |
||||
|
<Table.Cell>{profileAddress}</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>UserDB:</strong></Table.Cell> |
||||
|
<Table.Cell> |
||||
|
{userInfoOrbitAddress || (<Placeholder><Placeholder.Line /></Placeholder>)} |
||||
|
</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>TopicsDB:</strong></Table.Cell> |
||||
|
<Table.Cell> |
||||
|
{userTopicsOrbitAddress || (<Placeholder><Placeholder.Line /></Placeholder>)} |
||||
|
</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>PostsDB:</strong></Table.Cell> |
||||
|
<Table.Cell> |
||||
|
{userPostsOrbitAddress || (<Placeholder><Placeholder.Line /></Placeholder>)} |
||||
|
</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>Number of topics created:</strong></Table.Cell> |
||||
|
<Table.Cell> |
||||
|
{numberOfTopics} |
||||
|
</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>Number of posts:</strong></Table.Cell> |
||||
|
<Table.Cell> |
||||
|
{numberOfPosts} |
||||
|
</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>Number of posts:</strong></Table.Cell> |
||||
|
<Table.Cell> |
||||
|
{profileMeta !== null && profileMeta[USER_LOCATION] |
||||
|
? profileMeta[USER_LOCATION] |
||||
|
: <Placeholder><Placeholder.Line length="medium" /></Placeholder>} |
||||
|
</Table.Cell> |
||||
|
</Table.Row> |
||||
|
<Table.Row> |
||||
|
<Table.Cell><strong>Member since:</strong></Table.Cell> |
||||
|
<Table.Cell> |
||||
|
{moment(userRegistrationTimestamp * 1000).format('dddd, MMMM Do YYYY, h:mm:ss A')} |
||||
|
</Table.Cell> |
||||
|
</Table.Row> |
||||
|
</Table.Body> |
||||
|
</Table> |
||||
|
), [ |
||||
|
authorAvatar, numberOfPosts, numberOfTopics, profileAddress, profileMeta, userInfoOrbitAddress, |
||||
|
userPostsOrbitAddress, userRegistrationTimestamp, userTopicsOrbitAddress, username, |
||||
|
]); |
||||
|
}; |
||||
|
|
||||
|
GeneralTab.propTypes = { |
||||
|
profileAddress: PropTypes.string.isRequired, |
||||
|
username: PropTypes.string.isRequired, |
||||
|
numberOfTopics: PropTypes.number.isRequired, |
||||
|
numberOfPosts: PropTypes.number.isRequired, |
||||
|
userRegistrationTimestamp: PropTypes.string.isRequired, |
||||
|
}; |
||||
|
|
||||
|
export default GeneralTab; |
@ -0,0 +1,6 @@ |
|||||
|
.general-tab-profile-picture { |
||||
|
max-width: 112px; |
||||
|
max-height: 112px; |
||||
|
margin: 0; |
||||
|
vertical-align: middle; |
||||
|
} |
@ -0,0 +1,111 @@ |
|||||
|
import React, { |
||||
|
memo, useEffect, useMemo, useState, |
||||
|
} from 'react'; |
||||
|
import { Container, Header, Tab } from 'semantic-ui-react'; |
||||
|
import { useSelector } from 'react-redux'; |
||||
|
import { useHistory, useRouteMatch } from 'react-router'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
import { drizzle } from '../../redux/store'; |
||||
|
import { FORUM_CONTRACT } from '../../constants/ContractNames'; |
||||
|
import CustomLoadingTabPane from '../../components/CustomLoadingTabPane'; |
||||
|
import TopicList from '../../components/TopicList'; |
||||
|
import PostList from '../../components/PostList'; |
||||
|
import GeneralTab from './GeneralTab'; |
||||
|
import { GENERAL_TAB, POSTS_TAB, TOPICS_TAB } from '../../constants/ProfileTabs'; |
||||
|
|
||||
|
const { contracts: { [FORUM_CONTRACT]: { methods: { getUser } } } } = drizzle; |
||||
|
|
||||
|
const Profile = () => { |
||||
|
const [userCallHash, setUserCallHash] = useState(''); |
||||
|
const getUserResults = useSelector((state) => state.contracts[FORUM_CONTRACT].getUser); |
||||
|
const [profileAddress, setProfileAddress] = useState(); |
||||
|
const [username, setUsername] = useState(null); |
||||
|
const [userTopicIds, setUserTopicIds] = useState([]); |
||||
|
const [userPostIds, setUserPostIds] = useState([]); |
||||
|
const [userRegistrationTimestamp, setUserRegistrationTimestamp] = useState(null); |
||||
|
const [loading, setLoading] = useState(true); |
||||
|
const self = useSelector((state) => state.user); |
||||
|
const { t } = useTranslation(); |
||||
|
const match = useRouteMatch(); |
||||
|
const history = useHistory(); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (history.location.pathname === '/profile') { |
||||
|
if (self.hasSignedUp) { |
||||
|
setProfileAddress(self.address); |
||||
|
} else { |
||||
|
history.push('/'); |
||||
|
} |
||||
|
} else { |
||||
|
const { id: userAddress } = match.params; |
||||
|
|
||||
|
setProfileAddress(userAddress); |
||||
|
} |
||||
|
}, [history, match.params, self.address, self.hasSignedUp]); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (profileAddress) { |
||||
|
setUserCallHash(getUser.cacheCall(profileAddress)); |
||||
|
} |
||||
|
}, [profileAddress]); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (getUserResults[userCallHash] !== undefined && getUserResults[userCallHash].value) { |
||||
|
const [lUsername, topicIds, postIds, registrationTimestamp] = getUserResults[userCallHash].value; |
||||
|
setUsername(lUsername); |
||||
|
setUserTopicIds(topicIds.map((userTopicId) => parseInt(userTopicId, 10))); |
||||
|
setUserPostIds(postIds.map((userPostId) => parseInt(userPostId, 10))); |
||||
|
setUserRegistrationTimestamp(registrationTimestamp); |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}, [getUserResults, userCallHash]); |
||||
|
|
||||
|
const generalTab = useMemo(() => (loading |
||||
|
? null |
||||
|
: ( |
||||
|
<GeneralTab |
||||
|
profileAddress={profileAddress} |
||||
|
username={username} |
||||
|
numberOfTopics={userTopicIds.length} |
||||
|
numberOfPosts={userPostIds.length} |
||||
|
userRegistrationTimestamp={userRegistrationTimestamp} |
||||
|
/> |
||||
|
)), [loading, profileAddress, userPostIds.length, userRegistrationTimestamp, userTopicIds.length, username]); |
||||
|
|
||||
|
const topicsTab = useMemo(() => (userTopicIds.length > 0 |
||||
|
? (<TopicList topicIds={userTopicIds} />) |
||||
|
: ( |
||||
|
<Header textAlign="center" as="h2"> |
||||
|
{t('profile.user.has.no.topics.header.message', { user: username })} |
||||
|
</Header> |
||||
|
) |
||||
|
), [t, userTopicIds, username]); |
||||
|
|
||||
|
const postsTab = useMemo(() => (userPostIds.length > 0 |
||||
|
? (<PostList postIds={userPostIds} />) |
||||
|
: ( |
||||
|
<Header textAlign="center" as="h2"> |
||||
|
{t('profile.user.has.no.posts.header.message', { user: username })} |
||||
|
</Header> |
||||
|
)), [t, userPostIds, username]); |
||||
|
|
||||
|
const panes = useMemo(() => { |
||||
|
const generalTabPane = (<CustomLoadingTabPane loading={loading}>{generalTab}</CustomLoadingTabPane>); |
||||
|
const topicsTabPane = (<CustomLoadingTabPane loading={loading}>{topicsTab}</CustomLoadingTabPane>); |
||||
|
const postsTabPane = (<CustomLoadingTabPane loading={loading}>{postsTab}</CustomLoadingTabPane>); |
||||
|
|
||||
|
return ([ |
||||
|
{ menuItem: t(GENERAL_TAB.intl_display_name_id), render: () => generalTabPane }, |
||||
|
{ menuItem: t(TOPICS_TAB.intl_display_name_id), render: () => topicsTabPane }, |
||||
|
{ menuItem: t(POSTS_TAB.intl_display_name_id), render: () => postsTabPane }, |
||||
|
]); |
||||
|
}, [generalTab, loading, postsTab, t, topicsTab]); |
||||
|
|
||||
|
return useMemo(() => ( |
||||
|
<Container id="home-container" textAlign="center"> |
||||
|
<Tab panes={panes} /> |
||||
|
</Container> |
||||
|
), [panes]); |
||||
|
}; |
||||
|
|
||||
|
export default memo(Profile); |
Loading…
Reference in new issue