aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorilyayudovin <46264063+ilyayudovin@users.noreply.github.com>2020-06-27 19:18:33 +0300
committerGitHub <noreply@github.com>2020-06-27 19:18:33 +0300
commit5057fe763d423be607336d6b839909843c5a2567 (patch)
tree83356dd3ee0871e5226aa9ed67d9905f35e323f2 /src
parent9c4d6400ae83a32853d4437236f42796f08dbde7 (diff)
parent5b08023e0aa0e626264673ebb86dc82299a3b54b (diff)
downloadwhich-ui-5057fe763d423be607336d6b839909843c5a2567.tar.gz
Merge pull request #46 from which-ecosystem/profile
profile
Diffstat (limited to 'src')
-rw-r--r--src/components/Feed/Feed.tsx1
-rw-r--r--src/components/Header/Header.tsx15
-rw-r--r--src/components/UploadImage/UploadImage.tsx70
-rw-r--r--src/index.tsx11
-rw-r--r--src/pages/ProfilePage/Highlight.tsx39
-rw-r--r--src/pages/ProfilePage/MoreMenu.tsx66
-rw-r--r--src/pages/ProfilePage/ProfileInfo.tsx115
-rw-r--r--src/pages/ProfilePage/ProfilePage.tsx26
-rw-r--r--src/requests.ts4
9 files changed, 307 insertions, 40 deletions
diff --git a/src/components/Feed/Feed.tsx b/src/components/Feed/Feed.tsx
index d81da99..0c4d84f 100644
--- a/src/components/Feed/Feed.tsx
+++ b/src/components/Feed/Feed.tsx
@@ -18,7 +18,6 @@ const useStyles = makeStyles(theme => ({
const Feed: React.FC<PropTypes> = ({ polls, navigate }) => {
const classes = useStyles();
-
return (
<div className={classes.root}>
{polls.map(poll => <PollCard initialPoll={poll} key={poll._id} navigate={navigate} />)}
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
index 4e25fa3..2e3fc20 100644
--- a/src/components/Header/Header.tsx
+++ b/src/components/Header/Header.tsx
@@ -3,7 +3,7 @@ import {
AppBar,
Toolbar,
IconButton,
- Typography
+ Typography, Avatar
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import AccountCircle from '@material-ui/icons/AccountCircle';
@@ -13,6 +13,7 @@ import HomeIcon from '@material-ui/icons/Home';
import SearchBar from './SearchBar';
interface PropTypes {
+ userImage: string | undefined;
navigate: (prefix: string) => void;
}
@@ -25,10 +26,14 @@ const useStyles = makeStyles({
},
logo: {
fontWeight: 'bold'
+ },
+ avatar: {
+ width: 24,
+ height: 24
}
});
-const Header: React.FC<PropTypes> = ({ navigate }) => {
+const Header: React.FC<PropTypes> = ({ navigate, userImage }) => {
const classes = useStyles();
const handleHome = (): void => {
@@ -56,7 +61,11 @@ const Header: React.FC<PropTypes> = ({ navigate }) => {
<NotificationsIcon />
</IconButton>
<IconButton onClick={handleProfile}>
- <AccountCircle />
+ {
+ userImage !== undefined
+ ? <Avatar className={classes.avatar} src={userImage} />
+ : <AccountCircle />
+ }
</IconButton>
</div>
</Toolbar>
diff --git a/src/components/UploadImage/UploadImage.tsx b/src/components/UploadImage/UploadImage.tsx
new file mode 100644
index 0000000..42ee989
--- /dev/null
+++ b/src/components/UploadImage/UploadImage.tsx
@@ -0,0 +1,70 @@
+import React, { useRef } from 'react';
+import Button from '@material-ui/core/Button';
+import TextField from '@material-ui/core/TextField';
+import Dialog from '@material-ui/core/Dialog';
+import DialogActions from '@material-ui/core/DialogActions';
+import DialogContent from '@material-ui/core/DialogContent';
+import DialogContentText from '@material-ui/core/DialogContentText';
+import DialogTitle from '@material-ui/core/DialogTitle';
+import { User } from 'which-types';
+import { patch } from '../../requests';
+
+interface PropTypes {
+ displayD: boolean;
+ setDisplayD: (d: boolean) => void;
+ setUserInfo: (a: User) => void;
+ setUser: (a: User) => void
+}
+
+const UploadImage: React.FC<PropTypes> = ({
+ displayD, setDisplayD, setUserInfo, setUser
+}) => {
+ const urlRef = useRef<HTMLInputElement>(null);
+
+ const handleClose = () => {
+ setDisplayD(false);
+ };
+
+ const updateAvatar = () => {
+ const id = localStorage.getItem('userId');
+ const newAvatar = urlRef.current?.value;
+ patch(`/users/${id}`, { avatarUrl: newAvatar }).then(res => {
+ setUserInfo(res.data);
+ setUser(res.data);
+ });
+ setDisplayD(false);
+ };
+
+ return (
+ <div>
+ <Dialog open={displayD} onClose={handleClose}>
+ <DialogTitle id="form-dialog-title">Upload an Image</DialogTitle>
+ <DialogContent>
+ <DialogContentText>
+ Unfortunetly we do not support uploading images yet. Please provide a valid URL to your image.
+ </DialogContentText>
+ <TextField
+ autoFocus
+ margin="dense"
+ id="name"
+ label="Image URL"
+ type="text"
+ fullWidth
+ autoComplete="off"
+ inputRef={urlRef}
+ />
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={handleClose} color="primary">
+ Cancel
+ </Button>
+ <Button onClick={updateAvatar} color="primary">
+ Submit
+ </Button>
+ </DialogActions>
+ </Dialog>
+ </div>
+ );
+};
+
+export default UploadImage;
diff --git a/src/index.tsx b/src/index.tsx
index 2e0bfe7..49c177b 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -94,9 +94,16 @@ const App: React.FC = () => {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
- <Header navigate={navigate} />
+ <Header navigate={navigate} userImage={user?.avatarUrl} />
<div className={classes.root}>
- { page.prefix === 'profile' && <ProfilePage logOut={logOut} id={page.id} navigate={navigate} /> }
+ { page.prefix === 'profile' && (
+ <ProfilePage
+ logOut={logOut}
+ id={page.id}
+ navigate={navigate}
+ setUser={setUser}
+ />
+ ) }
{ page.prefix === 'feed' && <FeedPage navigate={navigate} /> }
{ page.prefix === 'auth' && <AuthPage logIn={logIn} /> }
</div>
diff --git a/src/pages/ProfilePage/Highlight.tsx b/src/pages/ProfilePage/Highlight.tsx
new file mode 100644
index 0000000..ebc3f56
--- /dev/null
+++ b/src/pages/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/pages/ProfilePage/MoreMenu.tsx b/src/pages/ProfilePage/MoreMenu.tsx
new file mode 100644
index 0000000..bf3347b
--- /dev/null
+++ b/src/pages/ProfilePage/MoreMenu.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+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';
+
+interface PropTypes {
+ logOut: () => void;
+}
+
+const ITEM_HEIGHT = 48;
+
+const useStyles = makeStyles({
+ moreMenu: {
+ position: 'absolute',
+ right: 10,
+ zIndex: 100
+ }
+});
+
+const MoreMenu: React.FC<PropTypes> = ({ logOut }) => {
+ const classes = useStyles();
+ const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ 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={logOut}>Log out</MenuItem>
+ </Menu>
+ </div>
+ </div>
+ );
+};
+
+export default MoreMenu;
diff --git a/src/pages/ProfilePage/ProfileInfo.tsx b/src/pages/ProfilePage/ProfileInfo.tsx
index 7208ec8..f52e374 100644
--- a/src/pages/ProfilePage/ProfileInfo.tsx
+++ b/src/pages/ProfilePage/ProfileInfo.tsx
@@ -1,64 +1,123 @@
-import React from 'react';
-import { Avatar } from '@material-ui/core/';
+import React, { useState } from 'react';
+import { Avatar, Badge } from '@material-ui/core/';
import { makeStyles } from '@material-ui/core/styles';
-import Button from '@material-ui/core/Button/Button';
import { User } from 'which-types';
+import CameraAltIcon from '@material-ui/icons/CameraAlt';
+import MoreMenu from './MoreMenu';
+import Highlight from './Highlight';
+import UploadImage from '../../components/UploadImage/UploadImage';
+
interface PropTypes {
user: User | undefined;
logOut: () => void;
+ savedPolls: number;
+ totalVotes: number;
+ setUserInfo: (a: User) => void;
+ setUser: (a:User) => void;
}
-const useStyles = makeStyles({
+const useStyles = makeStyles(theme => ({
+ root: {
+ position: 'relative'
+ },
avatar: {
- margin: '0 auto',
width: 150,
height: 150,
- marginBottom: 10
+ margin: '0 auto'
},
name: {
fontSize: 20,
- textAlign: 'center'
+ textAlign: 'center',
+ margin: '10px 0'
},
profileMenu: {
display: 'flex',
width: '100%',
height: 50,
- borderBottom: '1px solid lightgray',
- margin: '50px 0'
+ margin: '50px 0',
+ borderBottom: '1px solid lightgray'
},
menuButton: {
width: 200,
height: 50,
- paddingTop: 15,
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'
}
-});
+}));
+
-const ProfileInfo: React.FC<PropTypes> = ({ user, logOut }) => {
+const ProfileInfo: React.FC<PropTypes> = ({
+ user, logOut, savedPolls, totalVotes, setUserInfo, setUser
+}) => {
const classes = useStyles();
+ const [input, setInput] = useState(false);
+
+ const dateSince = new Date(user?.createdAt || '').toLocaleDateString();
+
+ const handleClick = () => {
+ setInput(!input);
+ };
+
return (
- <div>
- <Avatar className={classes.avatar} src={user?.avatarUrl} />
+ <div className={classes.root}>
+ {
+ user?._id === localStorage.getItem('userId')
+ ? (
+ <div>
+ <MoreMenu logOut={logOut} />
+ <div className={classes.avatarContainer}>
+ <Badge
+ onClick={handleClick}
+ overlap="circle"
+ anchorOrigin={{
+ vertical: 'bottom',
+ horizontal: 'right'
+ }}
+ badgeContent={(
+ <div className={classes.badge}>
+ <CameraAltIcon />
+ </div>
+ )}
+ >
+ <Avatar className={classes.avatar} src={user?.avatarUrl} />
+ </Badge>
+ </div>
+ <UploadImage displayD={input} setDisplayD={setInput} setUserInfo={setUserInfo} setUser={setUser} />
+ </div>
+)
+ : <Avatar className={classes.avatar} src={user?.avatarUrl} />
+ }
<div className={classes.name}>
{user?.username}
</div>
<div className={classes.profileMenu}>
- <div style={{ borderBottom: '1px solid green', color: 'green' }} className={classes.menuButton}>
- Polls
- </div>
- <div className={classes.menuButton}>
- Followers
- </div>
- <div className={classes.menuButton}>
- Following
- </div>
+ <Highlight text="Polls" value={savedPolls} />
+ <Highlight text="Since" value={dateSince} />
+ <Highlight text="Total" value={totalVotes} />
</div>
- {
- user?._id === localStorage.getItem('userId')
- ? <Button variant="contained" onClick={logOut}>Log Out</Button>
- : null
- }
</div>
);
};
diff --git a/src/pages/ProfilePage/ProfilePage.tsx b/src/pages/ProfilePage/ProfilePage.tsx
index 363d4ff..b0ac103 100644
--- a/src/pages/ProfilePage/ProfilePage.tsx
+++ b/src/pages/ProfilePage/ProfilePage.tsx
@@ -9,11 +9,15 @@ interface PropTypes {
logOut: () => void;
navigate: (prefix: string, id: string) => void;
id: string;
+ setUser:(a:User)=>void;
}
-const ProfilePage: React.FC<PropTypes> = ({ logOut, id, navigate }) => {
+const ProfilePage: React.FC<PropTypes> = ({
+ logOut, id, navigate, setUser
+}) => {
const [userInfo, setUserInfo] = useState<User>();
const [polls, setPolls] = useState<Poll[]>([]);
+ const [totalVotes, setTotalVotes] = useState<number>(0);
useEffect(() => {
get(`/users/${id}`).then(response => {
@@ -24,14 +28,26 @@ const ProfilePage: React.FC<PropTypes> = ({ logOut, id, navigate }) => {
useEffect(() => {
get(`/profiles/${id}`).then(response => {
setPolls(response.data);
+ setTotalVotes(response.data.reduce(
+ (total: number, current: Poll) => {
+ const { left, right } = current.contents;
+ return total + left.votes + right.votes;
+ }, 0
+ ));
});
- }, [id]);
-
+ }, [id, userInfo]);
return (
<>
- <ProfileInfo user={userInfo} logOut={logOut} />
- <Feed polls={polls} navigate={navigate} />
+ <ProfileInfo
+ user={userInfo}
+ setUserInfo={setUserInfo}
+ setUser={setUser}
+ logOut={logOut}
+ savedPolls={polls.length}
+ totalVotes={totalVotes}
+ />
+ <Feed polls={[...polls]} navigate={navigate} />
</>
);
};
diff --git a/src/requests.ts b/src/requests.ts
index 97c1c73..b02329d 100644
--- a/src/requests.ts
+++ b/src/requests.ts
@@ -12,6 +12,8 @@ requests.interceptors.request.use(config => {
return _.set(config, 'headers.Authorization', token);
});
-export const { get, post, put } = requests;
+export const {
+ get, post, put, patch
+} = requests;
export default requests;