diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/Feed/Feed.tsx | 57 | ||||
-rw-r--r-- | src/components/Header/Header.tsx | 99 | ||||
-rw-r--r-- | src/components/Header/SearchBar.tsx | 5 | ||||
-rw-r--r-- | src/components/PollCard/PollCard.tsx | 36 | ||||
-rw-r--r-- | src/components/ScrollTopArrow/ScrollTopArrow.tsx | 43 | ||||
-rw-r--r-- | src/components/UploadImage/UploadImage.tsx | 23 |
6 files changed, 175 insertions, 88 deletions
diff --git a/src/components/Feed/Feed.tsx b/src/components/Feed/Feed.tsx index 7636573..afa914d 100644 --- a/src/components/Feed/Feed.tsx +++ b/src/components/Feed/Feed.tsx @@ -1,26 +1,59 @@ import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; import { Poll } from 'which-types'; +import { WindowScroller, AutoSizer, List } from 'react-virtualized'; + import PollCard from '../PollCard/PollCard'; interface PropTypes { polls: Poll[]; } -const useStyles = makeStyles(theme => ({ - root: { - position: 'relative', - maxWidth: theme.spacing(75), - margin: '0 auto' - } -})); +interface RenderPropTypes { + index: number; + key: string; + style: React.CSSProperties; +} const Feed: React.FC<PropTypes> = ({ polls }) => { - const classes = useStyles(); + const RenderItem: React.FC<RenderPropTypes> = ({ index, style, key }) => { + const poll = polls[index]; + return ( + <div key={key} style={style}> + <PollCard initialPoll={poll} /> + </div> + ); + }; + return ( - <div className={classes.root}> - {polls.map(poll => <PollCard initialPoll={poll} key={poll._id} />)} - </div> + <WindowScroller> + {({ + height, + isScrolling, + registerChild, + onChildScroll, + scrollTop + }) => ( + <AutoSizer disableHeight> + {({ width }) => ( + <div ref={registerChild}> + <List + autoHeight + height={height} + isScrolling={isScrolling} + onScroll={onChildScroll} + rowCount={polls.length} + rowHeight={550} + rowRenderer={RenderItem} + scrollTop={scrollTop} + width={width} + containerStyle={{ pointerEvents: 'auto' }} + overscanRowCount={1} + /> + </div> + )} + </AutoSizer> + )} + </WindowScroller> ); }; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 72e40f8..41aeec7 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -3,9 +3,11 @@ import { AppBar, Toolbar, IconButton, - Typography, Avatar + Typography, + Avatar, + useMediaQuery } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +import { makeStyles, useTheme } from '@material-ui/core/styles'; import AccountCircle from '@material-ui/icons/AccountCircle'; import NotificationsIcon from '@material-ui/icons/Notifications'; import HomeIcon from '@material-ui/icons/Home'; @@ -14,28 +16,42 @@ import { useNavigate } from '../../hooks/useNavigate'; import SearchBar from './SearchBar'; -const useStyles = makeStyles({ - root: { +const useStyles = makeStyles(theme => ({ + mobile: { + top: 'auto', + bottom: 0 + }, + toolbar: { display: 'flex', - justifyContent: 'space-around', + justifyContent: 'space-around' + }, + browserToolbar: { width: '60%', margin: 'auto' }, logo: { - fontWeight: 'bold' + fontWeight: 'bold', + cursor: 'pointer', + color: 'white' }, - avatar: { - width: 24, - height: 24 + round: { + width: theme.spacing(3), + height: theme.spacing(3) } -}); +})); const Header: React.FC = () => { const classes = useStyles(); const { user } = useAuth(); const { navigate } = useNavigate(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const handleHome = (): void => { + navigate('home'); + }; + + const handleFeed = (): void => { navigate('feed'); }; @@ -44,33 +60,62 @@ const Header: React.FC = () => { else navigate('auth'); }; - const handleNotifications = (): void => {}; + const handleNotifications = (): void => { + navigate('notifications'); + }; + + const FeedButton = ( + <IconButton onClick={handleFeed}> + <HomeIcon /> + </IconButton> + ); + + const NotificationsButton = ( + <IconButton onClick={handleNotifications}> + <NotificationsIcon /> + </IconButton> + ); + + const ProfileButton = ( + <IconButton onClick={handleProfile}> + { + user?.avatarUrl + ? <Avatar className={classes.round} src={user?.avatarUrl} /> + : <AccountCircle /> + } + </IconButton> + ); - return ( + const BrowserVersion = ( <AppBar position="fixed"> - <Toolbar className={classes.root}> - <Typography variant="h5" className={classes.logo}> + <Toolbar className={`${classes.toolbar} ${classes.browserToolbar}`}> + <Typography variant="h5" className={classes.logo} onClick={handleHome}> Which </Typography> <SearchBar /> <div> - <IconButton onClick={handleHome}> - <HomeIcon /> - </IconButton> - <IconButton onClick={handleNotifications}> - <NotificationsIcon /> - </IconButton> - <IconButton onClick={handleProfile}> - { - user?.avatarUrl?.match(/\.(jpeg|jpg|gif|png)$/) - ? <Avatar className={classes.avatar} src={user?.avatarUrl} /> - : <AccountCircle /> - } - </IconButton> + {FeedButton} + {NotificationsButton} + {ProfileButton} </div> </Toolbar> </AppBar> ); + + const MobileVersion = ( + <AppBar position="fixed" className={classes.mobile}> + <Toolbar className={classes.toolbar}> + <IconButton onClick={handleHome}> + <Typography className={`${classes.logo} ${classes.round}`}>W</Typography> + </IconButton> + {FeedButton} + {NotificationsButton} + {ProfileButton} + </Toolbar> + </AppBar> + ); + + return isMobile ? MobileVersion : BrowserVersion; }; export default Header; diff --git a/src/components/Header/SearchBar.tsx b/src/components/Header/SearchBar.tsx index 253e77f..ba0943b 100644 --- a/src/components/Header/SearchBar.tsx +++ b/src/components/Header/SearchBar.tsx @@ -31,6 +31,9 @@ const useStyles = makeStyles(theme => ({ position: 'absolute', width: '100%', top: theme.spacing(5) + }, + listItem: { + padding: 0 } })); @@ -77,7 +80,7 @@ const SearchBar: React.FC = () => { { results.map((result, index) => ( <div key={result._id}> - <ListItem button onClick={handleNavigate(index)}> + <ListItem button onClick={handleNavigate(index)} className={classes.listItem}> <UserStrip user={result} /> </ListItem> {(index < results.length - 1) && <Divider />} diff --git a/src/components/PollCard/PollCard.tsx b/src/components/PollCard/PollCard.tsx index 2a23522..98ae001 100644 --- a/src/components/PollCard/PollCard.tsx +++ b/src/components/PollCard/PollCard.tsx @@ -6,10 +6,12 @@ import { CardMedia } from '@material-ui/core/'; import { Which, Poll } from 'which-types'; +import { useSnackbar } from 'notistack'; import PercentageBar from './PercentageBar'; import UserStrip from '../UserStrip/UserStrip'; import { post } from '../../requests'; +import { useAuth } from '../../hooks/useAuth'; interface PropTypes { initialPoll: Poll; @@ -24,14 +26,8 @@ const DATE_FORMAT = { }; const useStyles = makeStyles(theme => ({ - root: { - maxWidth: theme.spacing(75), - height: 488, - margin: '40px auto' - }, images: { - height: theme.spacing(50), - width: 300 + height: theme.spacing(50) }, imagesBlock: { display: 'flex' @@ -57,15 +53,31 @@ const PollCard: React.FC<PropTypes> = ({ initialPoll }) => { const [poll, setPoll] = useState<Poll>(initialPoll); const classes = useStyles(); const { author, contents: { left, right }, vote } = poll; + const { enqueueSnackbar } = useSnackbar(); + const { isAuthenticated } = useAuth(); const date: string = new Date(poll.createdAt).toLocaleString('default', DATE_FORMAT); const handleVote = (which: Which) => { - if (vote) return; - post('votes/', { which, pollId: poll._id }).then(response => { + if (!isAuthenticated()) { + enqueueSnackbar('Unauthorized users can not vote in polls', { + variant: 'error' + }); + } else if (vote) { + enqueueSnackbar('You have already voted in this poll', { + variant: 'error' + }); + } else { + const newVote = ({ which, pollId: poll._id }); + post('votes/', newVote); poll.contents[which].votes += 1; - poll.vote = response.data; + poll.vote = { + _id: '', + authorId: '', + createdAt: new Date(), + ...newVote + }; setPoll({ ...poll }); - }); + } }; const handleLeft = () => handleVote('left'); @@ -85,7 +97,7 @@ const PollCard: React.FC<PropTypes> = ({ initialPoll }) => { const dominant: Which = left.votes >= right.votes ? 'left' : 'right'; return ( - <Card className={classes.root}> + <Card> <UserStrip user={author} info={date} /> <div className={classes.imagesBlock}> <CardActionArea onDoubleClick={handleLeft}> diff --git a/src/components/ScrollTopArrow/ScrollTopArrow.tsx b/src/components/ScrollTopArrow/ScrollTopArrow.tsx index 6b9d5c9..08b8591 100644 --- a/src/components/ScrollTopArrow/ScrollTopArrow.tsx +++ b/src/components/ScrollTopArrow/ScrollTopArrow.tsx @@ -1,26 +1,33 @@ import React, { useState } from 'react'; -import { FaArrowCircleUp } from 'react-icons/fa'; -import { makeStyles } from '@material-ui/core'; -import teal from '@material-ui/core/colors/teal'; +import { makeStyles, useTheme } from '@material-ui/core/styles'; +import { useMediaQuery } from '@material-ui/core'; +import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward'; -const useStyles = makeStyles(() => ({ - scrollTop: { +const useStyles = makeStyles(theme => ({ + root: { position: 'fixed', - width: 50, - bottom: 50, - left: 50, + bottom: theme.spacing(10), + left: theme.spacing(10), zIndex: 1000, cursor: 'pointer', - opacity: 0.5, + opacity: 0.4, '&:hover': { - opacity: '1' - } + opacity: 1 + }, + background: theme.palette.primary.main, + borderRadius: '50%' + }, + icon: { + fontSize: 80, + color: 'white' } })); -const ScrollTopArrow:React.FC = () => { +const ScrollTopArrow: React.FC = () => { const [showScroll, setShowScroll] = useState(false); + const theme = useTheme(); const classes = useStyles(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const checkScrollTop = () => { if (!showScroll && window.pageYOffset > 400) { @@ -37,11 +44,13 @@ const ScrollTopArrow:React.FC = () => { window.addEventListener('scroll', checkScrollTop); return ( - <FaArrowCircleUp - className={classes.scrollTop} - onClick={scrollTop} - style={{ height: 50, display: showScroll ? 'block' : 'none', color: teal[700] }} - /> + <div className={classes.root}> + { + showScroll + && !isMobile + && <ArrowUpwardIcon className={classes.icon} color="primary" onClick={scrollTop} /> + } + </div> ); }; diff --git a/src/components/UploadImage/UploadImage.tsx b/src/components/UploadImage/UploadImage.tsx index f5e680d..8ad65d5 100644 --- a/src/components/UploadImage/UploadImage.tsx +++ b/src/components/UploadImage/UploadImage.tsx @@ -6,7 +6,6 @@ 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 get from 'axios'; interface PropTypes { isOpen: boolean; @@ -15,8 +14,7 @@ interface PropTypes { } const UploadImage: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { - const [url, setUrl] = useState(''); - const [isError, setIsError] = useState(false); + const [url, setUrl] = useState<string>(''); const handleClose = () => { @@ -24,19 +22,8 @@ const UploadImage: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { }; const handleSubmit = () => { - get(url).then(res => { - if (res.headers['content-type'] === 'image/jpeg') { - callback(url || ''); - setIsOpen(false); - setIsError(false); - } else { - // console.warn(res); TODO: handle error if response status is ok but not an image - setIsError(true); - } - }).catch(() => { - // console.warn(err); TODO: handle error if resposne status is not ok - setIsError(true); - }); + handleClose(); + callback(url || ''); }; const handleChange = (event:React.ChangeEvent<HTMLInputElement>) => { @@ -49,7 +36,7 @@ const UploadImage: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { <DialogTitle>Upload an Image</DialogTitle> <DialogContent> <DialogContentText> - Unfortunetly we do not support uploading images yet. Please provide a valid URL to your image. + Unfortunetly we do not support uploading images yet. Please provide a valid URL to your image: </DialogContentText> <TextField autoFocus @@ -60,8 +47,6 @@ const UploadImage: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { fullWidth autoComplete="off" onChange={handleChange} - error={isError} - helperText={isError === true ? 'invalid Url!' : ''} /> </DialogContent> <DialogActions> |