aboutsummaryrefslogtreecommitdiff
path: root/src/containers/ProfilePage
diff options
context:
space:
mode:
Diffstat (limited to 'src/containers/ProfilePage')
-rw-r--r--src/containers/ProfilePage/Highlight.tsx39
-rw-r--r--src/containers/ProfilePage/MoreMenu.tsx72
-rw-r--r--src/containers/ProfilePage/ProfileInfo.tsx166
-rw-r--r--src/containers/ProfilePage/ProfilePage.tsx55
4 files changed, 332 insertions, 0 deletions
diff --git a/src/containers/ProfilePage/Highlight.tsx b/src/containers/ProfilePage/Highlight.tsx
new file mode 100644
index 0000000..ebc3f56
--- /dev/null
+++ b/src/containers/ProfilePage/Highlight.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+
+interface PropTypes {
+ text: string;
+ value: string | number;
+}
+
+const useStyles = makeStyles({
+ root: {
+ position: 'relative'
+ },
+ menuButton: {
+ width: 200,
+ height: 50,
+ textAlign: 'center'
+ },
+ menuNumber: {
+ fontWeight: 800,
+ color: 'black'
+ },
+ menuText: {
+ color: 'darkgray'
+ }
+});
+
+
+const Highlight: React.FC<PropTypes> = ({ text, value }) => {
+ const classes = useStyles();
+
+ return (
+ <div className={classes.menuButton}>
+ <div className={classes.menuNumber}>{value}</div>
+ <div className={classes.menuText}>{text}</div>
+ </div>
+ );
+};
+
+export default Highlight;
diff --git a/src/containers/ProfilePage/MoreMenu.tsx b/src/containers/ProfilePage/MoreMenu.tsx
new file mode 100644
index 0000000..1f41879
--- /dev/null
+++ b/src/containers/ProfilePage/MoreMenu.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import IconButton from '@material-ui/core/IconButton';
+import Menu from '@material-ui/core/Menu';
+import MenuItem from '@material-ui/core/MenuItem';
+import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
+import { makeStyles } from '@material-ui/core';
+import { useAuth } from '../../hooks/useAuth';
+
+const ITEM_HEIGHT = 48;
+
+const useStyles = makeStyles({
+ moreMenu: {
+ position: 'absolute',
+ right: 10,
+ zIndex: 100
+ }
+});
+
+const MoreMenu: React.FC = () => {
+ const classes = useStyles();
+ const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
+ const { logout } = useAuth();
+ const history = useHistory();
+
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleLogout = () => {
+ logout();
+ history.push('/login');
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+ <div className={classes.moreMenu}>
+ <div>
+ <IconButton
+ aria-label="more"
+ aria-controls="long-menu"
+ aria-haspopup="true"
+ onClick={handleClick}
+ >
+ <MoreHorizIcon />
+ </IconButton>
+ <Menu
+ id="long-menu"
+ anchorEl={anchorEl}
+ keepMounted
+ open={open}
+ onClose={handleClose}
+ PaperProps={{
+ style: {
+ maxHeight: ITEM_HEIGHT * 4.5,
+ width: '20ch'
+ }
+ }}
+ >
+ <MenuItem onClick={handleLogout}>Log out</MenuItem>
+ </Menu>
+ </div>
+ </div>
+ );
+};
+
+export default MoreMenu;
diff --git a/src/containers/ProfilePage/ProfileInfo.tsx b/src/containers/ProfilePage/ProfileInfo.tsx
new file mode 100644
index 0000000..9eee4c1
--- /dev/null
+++ b/src/containers/ProfilePage/ProfileInfo.tsx
@@ -0,0 +1,166 @@
+import React, { useState } from 'react';
+import { Avatar, Badge, Typography } from '@material-ui/core/';
+import { makeStyles } from '@material-ui/core/styles';
+import { User } from 'which-types';
+import CameraAltIcon from '@material-ui/icons/CameraAlt';
+import VerifiedIcon from '@material-ui/icons/CheckCircleOutline';
+import Skeleton from '@material-ui/lab/Skeleton';
+import MoreMenu from './MoreMenu';
+import Highlight from './Highlight';
+import UploadImage from '../../components/UploadImage/UploadImage';
+import { patch } from '../../requests';
+import { useAuth } from '../../hooks/useAuth';
+
+interface PropTypes {
+ savedPolls: number;
+ totalVotes: number;
+ userInfo: User | undefined;
+ setUserInfo: (userInfo: User) => void;
+}
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ position: 'relative'
+ },
+ avatar: {
+ width: 150,
+ height: 150,
+ margin: '0 auto'
+ },
+ name: {
+ margin: theme.spacing(1, 0),
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ verified: {
+ marginLeft: theme.spacing(0.5),
+ width: theme.spacing(3),
+ height: theme.spacing(3)
+ },
+ profileMenu: {
+ display: 'flex',
+ width: '100%',
+ height: 50,
+ margin: '50px 0',
+ borderBottom: '1px solid lightgray'
+ },
+ menuButton: {
+ width: 200,
+ height: 50,
+ textAlign: 'center'
+ },
+ badge: {
+ width: theme.spacing(5),
+ height: theme.spacing(5),
+ borderRadius: '50%',
+ cursor: 'pointer',
+ background: '#d3d3d3',
+ display: 'flex',
+ alignItems: 'center',
+ '& svg': {
+ margin: '0 auto'
+ }
+ },
+ avatarContainer: {
+ position: 'relative',
+ textAlign: 'center'
+ },
+ menuNumber: {
+ fontWeight: 800,
+ color: 'black'
+ },
+ menuText: {
+ color: 'darkgray'
+ },
+ skeleton: {
+ margin: '10px auto',
+ borderRadius: 2
+ }
+
+}));
+
+
+const ProfileInfo: React.FC<PropTypes> = ({
+ savedPolls, totalVotes, setUserInfo, userInfo
+}) => {
+ const classes = useStyles();
+ const [input, setInput] = useState(false);
+ const { user } = useAuth();
+ const dateSince = new Date(userInfo?.createdAt || '').toLocaleDateString();
+
+ const handleClick = () => {
+ setInput(!input);
+ };
+
+ const patchAvatar = (url: string) => {
+ const id = user?._id;
+ patch(`/users/${id}`, { avatarUrl: url }).then(res => {
+ setUserInfo(res.data);
+ });
+ };
+
+ return (
+ <div className={classes.root}>
+ {
+ !userInfo
+ ? <Skeleton animation="wave" variant="circle" width={150} height={150} className={classes.avatar} />
+ : userInfo?._id === user?._id
+ ? (
+ <div>
+ <MoreMenu />
+ <div className={classes.avatarContainer}>
+ <Badge
+ overlap="circle"
+ anchorOrigin={{
+ vertical: 'bottom',
+ horizontal: 'right'
+ }}
+ badgeContent={(
+ <div className={classes.badge}>
+ <CameraAltIcon onClick={handleClick} />
+ </div>
+ )}
+ >
+ <Avatar className={classes.avatar} src={userInfo?.avatarUrl} />
+ </Badge>
+ </div>
+ <UploadImage isOpen={input} setIsOpen={setInput} callback={patchAvatar} />
+ </div>
+ )
+ : <Avatar className={classes.avatar} src={userInfo?.avatarUrl} />
+ }
+ {
+ !userInfo
+ ? <Skeleton animation="wave" variant="rect" width={100} height={20} className={classes.skeleton} />
+ : (
+ <Typography variant="h5" className={classes.name}>
+ {userInfo?.username}
+ {userInfo?.verified && <VerifiedIcon className={classes.verified} color="primary" />}
+ </Typography>
+ )
+ }
+ <div className={classes.profileMenu}>
+ {
+ !userInfo
+ ? (
+ <>
+ <Skeleton animation="wave" variant="rect" width={170} height={20} className={classes.skeleton} />
+ <Skeleton animation="wave" variant="rect" width={170} height={20} className={classes.skeleton} />
+ <Skeleton animation="wave" variant="rect" width={170} height={20} className={classes.skeleton} />
+ </>
+ )
+ : (
+ <>
+ <Highlight text="Polls" value={savedPolls} />
+ <Highlight text="Since" value={dateSince} />
+ <Highlight text="Total votes" value={totalVotes} />
+ </>
+ )
+ }
+ </div>
+ </div>
+ );
+};
+
+export default ProfileInfo;
diff --git a/src/containers/ProfilePage/ProfilePage.tsx b/src/containers/ProfilePage/ProfilePage.tsx
new file mode 100644
index 0000000..db27d25
--- /dev/null
+++ b/src/containers/ProfilePage/ProfilePage.tsx
@@ -0,0 +1,55 @@
+import React, { useEffect, useCallback } from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+import { Poll } from 'which-types';
+import { Container } from '@material-ui/core';
+
+import ProfileInfo from './ProfileInfo';
+import Feed from '../../components/Feed/Feed';
+import Loading from '../../components/Loading/Loading';
+import { useAuth } from '../../hooks/useAuth';
+import { useUser, useProfile } from '../../hooks/APIClient';
+
+
+const ProfilePage: React.FC = () => {
+ const history = useHistory();
+ const { username } = useParams();
+ const { user } = useAuth();
+
+ const { data: userInfo, mutate: setUserInfo } = useUser(username);
+ const { data: polls, isValidating } = useProfile(userInfo?._id);
+
+ useEffect(() => {
+ if (!username) {
+ if (user) history.push(`/profile/${user.username}`);
+ else history.push('/login');
+ }
+ }, [username, history, user]);
+
+
+ const totalVotes = useCallback(
+ polls.reduce(
+ (total: number, current: Poll) => {
+ const { left, right } = current.contents;
+ return total + left.votes + right.votes;
+ }, 0
+ ),
+ [polls]
+ );
+
+ return (
+ <Container maxWidth="sm" disableGutters>
+ <ProfileInfo
+ userInfo={userInfo}
+ setUserInfo={setUserInfo}
+ savedPolls={polls.length}
+ totalVotes={totalVotes}
+ />
+ {!polls.length && isValidating
+ ? <Loading />
+ : <Feed polls={polls} />
+ }
+ </Container>
+ );
+};
+
+export default ProfilePage;