diff options
author | ilyayudovin <46264063+ilyayudovin@users.noreply.github.com> | 2020-06-27 19:18:33 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-27 19:18:33 +0300 |
commit | 5057fe763d423be607336d6b839909843c5a2567 (patch) | |
tree | 83356dd3ee0871e5226aa9ed67d9905f35e323f2 /src | |
parent | 9c4d6400ae83a32853d4437236f42796f08dbde7 (diff) | |
parent | 5b08023e0aa0e626264673ebb86dc82299a3b54b (diff) | |
download | which-ui-5057fe763d423be607336d6b839909843c5a2567.tar.gz |
Merge pull request #46 from which-ecosystem/profile
profile
Diffstat (limited to 'src')
-rw-r--r-- | src/components/Feed/Feed.tsx | 1 | ||||
-rw-r--r-- | src/components/Header/Header.tsx | 15 | ||||
-rw-r--r-- | src/components/UploadImage/UploadImage.tsx | 70 | ||||
-rw-r--r-- | src/index.tsx | 11 | ||||
-rw-r--r-- | src/pages/ProfilePage/Highlight.tsx | 39 | ||||
-rw-r--r-- | src/pages/ProfilePage/MoreMenu.tsx | 66 | ||||
-rw-r--r-- | src/pages/ProfilePage/ProfileInfo.tsx | 115 | ||||
-rw-r--r-- | src/pages/ProfilePage/ProfilePage.tsx | 26 | ||||
-rw-r--r-- | src/requests.ts | 4 |
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; |