aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json37
-rw-r--r--package.json3
-rw-r--r--public/index.html1
-rw-r--r--public/which-logo-512.pngbin0 -> 22412 bytes
-rw-r--r--src/components/Feed/Feed.tsx57
-rw-r--r--src/components/Header/Header.tsx99
-rw-r--r--src/components/Header/SearchBar.tsx5
-rw-r--r--src/components/PollCard/PollCard.tsx36
-rw-r--r--src/components/ScrollTopArrow/ScrollTopArrow.tsx43
-rw-r--r--src/components/UploadImage/UploadImage.tsx23
-rw-r--r--src/hooks/useNavigate.tsx3
-rw-r--r--src/index.tsx1
-rw-r--r--src/pages/FeedPage/FeedPage.tsx10
-rw-r--r--src/pages/FeedPage/PollSubmission.tsx12
-rw-r--r--src/pages/FeedPage/PollSubmissionImage.tsx5
-rw-r--r--src/pages/HomePage/HomePage.tsx144
-rw-r--r--src/pages/NotificationsPage/NotificationsPage.tsx23
-rw-r--r--src/pages/Page.tsx39
-rw-r--r--src/pages/ProfilePage/ProfilePage.tsx6
19 files changed, 438 insertions, 109 deletions
diff --git a/package-lock.json b/package-lock.json
index 3329f6f..41430ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1710,6 +1710,16 @@
"@types/react": "*"
}
},
+ "@types/react-virtualized": {
+ "version": "9.21.10",
+ "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.10.tgz",
+ "integrity": "sha512-f5Ti3A7gGdLkPPFNHTrvKblpsPNBiQoSorOEOD+JPx72g/Ng2lOt4MYfhvQFQNgyIrAro+Z643jbcKafsMW2ag==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/react": "*"
+ }
+ },
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -8697,6 +8707,15 @@
"sort-keys": "^1.0.0"
}
},
+ "notistack": {
+ "version": "0.9.17",
+ "resolved": "https://registry.npmjs.org/notistack/-/notistack-0.9.17.tgz",
+ "integrity": "sha512-nypTN6sEe+q98wMaxF/UwatA1yAq948+bZOo9JKYR+tU65DW0ipWyx8DseJ3UJYvb6VDD+Fqo83qwayQ46bEEA==",
+ "requires": {
+ "clsx": "^1.1.0",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -10808,6 +10827,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
"react-scripts": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.1.tgz",
@@ -11177,6 +11201,19 @@
"prop-types": "^15.6.2"
}
},
+ "react-virtualized": {
+ "version": "9.21.2",
+ "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.2.tgz",
+ "integrity": "sha512-oX7I7KYiUM7lVXQzmhtF4Xg/4UA5duSA+/ZcAvdWlTLFCoFYq1SbauJT5gZK9cZS/wdYR6TPGpX/dqzvTqQeBA==",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "clsx": "^1.0.1",
+ "dom-helpers": "^5.0.0",
+ "loose-envify": "^1.3.0",
+ "prop-types": "^15.6.0",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
diff --git a/package.json b/package.json
index 61cc8f3..40797fe 100644
--- a/package.json
+++ b/package.json
@@ -8,10 +8,12 @@
"@material-ui/lab": "^4.0.0-alpha.56",
"axios": "^0.19.2",
"lodash": "^4.17.15",
+ "notistack": "^0.9.17",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-icons": "^3.10.0",
"react-scripts": "3.4.1",
+ "react-virtualized": "^9.21.2",
"typeface-roboto": "0.0.75",
"which-types": "^1.6.1"
},
@@ -39,6 +41,7 @@
"@types/node": "^12.12.44",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
+ "@types/react-virtualized": "^9.21.10",
"@typescript-eslint/eslint-plugin": "^3.1.0",
"@typescript-eslint/parser": "^3.1.0",
"eslint": "^6.8.0",
diff --git a/public/index.html b/public/index.html
index c3d52e2..001cbac 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
+ <link rel="icon" type="image/png" sizes="16x16" href="./which-logo-64.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Which</title>
</head>
diff --git a/public/which-logo-512.png b/public/which-logo-512.png
new file mode 100644
index 0000000..ddcbd9f
--- /dev/null
+++ b/public/which-logo-512.png
Binary files differ
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>
diff --git a/src/hooks/useNavigate.tsx b/src/hooks/useNavigate.tsx
index befc529..d1a433d 100644
--- a/src/hooks/useNavigate.tsx
+++ b/src/hooks/useNavigate.tsx
@@ -11,7 +11,7 @@ interface ContextType {
navigate: (prefix: string, id?: string) => void;
}
-const landingPage = { prefix: 'feed' };
+const landingPage = { prefix: 'home' };
const context = createContext<ContextType>({
page: landingPage,
@@ -24,6 +24,7 @@ const useProvideNavigation = () => {
const navigate: ContextType['navigate'] = (prefix, id?) => {
setPage({ prefix, id });
+ window.scrollTo(0, 0);
};
return { page, setPage, navigate };
diff --git a/src/index.tsx b/src/index.tsx
index 180f80c..e8fbce1 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -11,7 +11,6 @@ import Page from './pages/Page';
import { AuthProvider } from './hooks/useAuth';
import { NavigationProvider } from './hooks/useNavigate';
-
const theme = createMuiTheme({
palette: {
primary: {
diff --git a/src/pages/FeedPage/FeedPage.tsx b/src/pages/FeedPage/FeedPage.tsx
index d29103a..0b7d44a 100644
--- a/src/pages/FeedPage/FeedPage.tsx
+++ b/src/pages/FeedPage/FeedPage.tsx
@@ -1,12 +1,12 @@
import React, { useState, useEffect } from 'react';
import { Poll } from 'which-types';
+import { Container } from '@material-ui/core/';
import Feed from '../../components/Feed/Feed';
import { get } from '../../requests';
import PollSubmission from './PollSubmission';
import { useAuth } from '../../hooks/useAuth';
-
const FeedPage: React.FC = () => {
const [polls, setPolls] = useState<Poll[]>([]);
const { isAuthenticated } = useAuth();
@@ -19,15 +19,15 @@ const FeedPage: React.FC = () => {
const addPoll = (poll: Poll): void => {
polls.unshift(poll);
- setPolls([...polls]);
+ setPolls([]);
+ setPolls(polls);
};
-
return (
- <>
+ <Container maxWidth="sm" disableGutters>
{isAuthenticated() && <PollSubmission addPoll={addPoll} />}
<Feed polls={polls} />
- </>
+ </Container>
);
};
diff --git a/src/pages/FeedPage/PollSubmission.tsx b/src/pages/FeedPage/PollSubmission.tsx
index 18f029c..347eecc 100644
--- a/src/pages/FeedPage/PollSubmission.tsx
+++ b/src/pages/FeedPage/PollSubmission.tsx
@@ -8,6 +8,7 @@ import {
Divider
} from '@material-ui/core';
import { Poll, Which } from 'which-types';
+import { useSnackbar } from 'notistack';
import PollSubmissionImage from './PollSubmissionImage';
import UserStrip from '../../components/UserStrip/UserStrip';
import { post } from '../../requests';
@@ -20,6 +21,9 @@ interface PropTypes{
const useStyles = makeStyles(theme => ({
root: {
+ marginBottom: theme.spacing(4)
+ },
+ images: {
height: theme.spacing(50),
display: 'flex'
}
@@ -34,6 +38,7 @@ const PollSubmission: React.FC<PropTypes> = ({ addPoll }) => {
const classes = useStyles();
const [expanded, setExpanded] = useState(false);
const [contents, setContents] = useState<Contents>(emptyContents);
+ const { enqueueSnackbar } = useSnackbar();
const { user } = useAuth();
const readyToSubmit = contents.left.url && contents.right.url;
@@ -50,6 +55,9 @@ const PollSubmission: React.FC<PropTypes> = ({ addPoll }) => {
if (expanded && readyToSubmit) {
post('/polls/', { contents }).then(response => {
addPoll(response.data);
+ enqueueSnackbar('Your poll has been successfully created!', {
+ variant: 'success'
+ });
});
setContents({ ...emptyContents });
}
@@ -58,11 +66,11 @@ const PollSubmission: React.FC<PropTypes> = ({ addPoll }) => {
return (
<ClickAwayListener onClickAway={handleClickAway}>
- <Card>
+ <Card className={classes.root}>
<Collapse in={expanded} timeout="auto" unmountOnExit>
{user && <UserStrip user={user} info="" />}
<Divider />
- <div className={classes.root}>
+ <div className={classes.images}>
<PollSubmissionImage url={contents.left.url} setUrl={setUrl('left')} />
<PollSubmissionImage url={contents.right.url} setUrl={setUrl('right')} />
</div>
diff --git a/src/pages/FeedPage/PollSubmissionImage.tsx b/src/pages/FeedPage/PollSubmissionImage.tsx
index a8ec437..8835989 100644
--- a/src/pages/FeedPage/PollSubmissionImage.tsx
+++ b/src/pages/FeedPage/PollSubmissionImage.tsx
@@ -28,6 +28,9 @@ const useStyles = makeStyles({
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
+ },
+ text: {
+ textAlign: 'center'
}
});
@@ -56,7 +59,7 @@ const PollSubmissionImage: React.FC<PropTypes> = ({ url, setUrl }) => {
const Upload = (
<>
<CloudUploadIcon fontSize="large" color="primary" />
- <Typography variant="h5"> Upload an image </Typography>
+ <Typography variant="h5" className={classes.text}> Upload an image </Typography>
</>
);
diff --git a/src/pages/HomePage/HomePage.tsx b/src/pages/HomePage/HomePage.tsx
new file mode 100644
index 0000000..f00289a
--- /dev/null
+++ b/src/pages/HomePage/HomePage.tsx
@@ -0,0 +1,144 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Typography,
+ Divider,
+ Grid,
+ Button,
+ Link
+} from '@material-ui/core/';
+import { makeStyles } from '@material-ui/core/styles';
+import TrendingUpIcon from '@material-ui/icons/TrendingUp';
+import { Rating } from '@material-ui/lab';
+import { Feedback } from 'which-types';
+
+import { useNavigate } from '../../hooks/useNavigate';
+import { useAuth } from '../../hooks/useAuth';
+import { get } from '../../requests';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ overflow: 'hidden',
+ padding: theme.spacing(0, 2)
+ },
+ logo: {
+ width: theme.spacing(20),
+ height: theme.spacing(20)
+ },
+ score: {
+ fontWeight: 'bold'
+ },
+ signup: {
+ marginLeft: theme.spacing(2)
+ }
+}));
+
+const HomePage: React.FC = () => {
+ const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
+ const classes = useStyles();
+ const { navigate } = useNavigate();
+ const { isAuthenticated } = useAuth();
+
+ const rating = feedbacks.length && feedbacks.reduce(
+ (acc: number, feedback: Feedback) => acc + feedback.score,
+ 0
+ ) / feedbacks.length;
+
+ useEffect(() => {
+ get('/feedback').then(response => {
+ setFeedbacks(response.data);
+ });
+ }, []);
+
+ const handleLetsGo = () => {
+ navigate('feed');
+ };
+
+ const handleSignUp = () => {
+ navigate('auth');
+ };
+
+ const GithubLink = <Link href="https://github.com/which-ecosystem">GitHub</Link>;
+ const TypescriptLink = <Link href="https://www.typescriptlang.org/">Typescript</Link>;
+ const ReactLink = <Link href="https://reactjs.org/">React</Link>;
+ const FeathersLink = <Link href="https://feathersjs.com">Feathers</Link>;
+ const MUILink = <Link href="https://material-ui.com">Material-UI</Link>;
+
+ return (
+ <div className={classes.root}>
+ <Grid container spacing={4}>
+ <Grid item xs={12} md={4}>
+ <Grid container direction="column" spacing={1} alignItems="center">
+ <Grid item>
+ <img src={`${process.env.PUBLIC_URL}/which-logo-512.png`} alt="logo" className={classes.logo} />
+ </Grid>
+ <Grid item>
+ <Rating value={rating} readOnly size="large" />
+ </Grid>
+ <Grid item>
+ <Typography variant="h5" className={classes.score}>
+ User score: {rating.toFixed(1)}
+ </Typography>
+ </Grid>
+ </Grid>
+ </Grid>
+ <Grid item xs={12} md={5}>
+ <Grid container direction="column" spacing={6}>
+ <Grid item>
+ <Typography variant="h4"> Which one to choose? </Typography>
+ <Divider />
+ <Typography>
+ <p>
+ Have you ever found yourself stuck between two options, not being able to choose any?
+ This is exactly the problem we are going to solve!
+ </p>
+ <p>Share your minor everyday uncertainties with the whole world and see what others think!</p>
+ <Button variant="contained" color="primary" size="large" onClick={handleLetsGo}>
+ {'let\'s go!'}
+ </Button>
+ {!isAuthenticated() && (
+ <Button
+ variant="outlined"
+ color="primary"
+ size="large"
+ className={classes.signup}
+ onClick={handleSignUp}
+ >
+ sign up
+ </Button>
+ )}
+ </Typography>
+ </Grid>
+ <Grid item>
+ <Typography variant="h4"> About the project </Typography>
+ <Divider />
+ <Typography>
+ <p>
+ The project is written in {TypescriptLink} and features {ReactLink}, {FeathersLink}, and {MUILink}.
+ It is currently open-source and you can visit our {GithubLink} (make sure to star our repositories)!
+ </p>
+ <p>
+ We encourage any developer to check it out. Feel free to open issues and create Pull Requests!
+ </p>
+ <p>
+ All the development process is being tracked on the KanBan board (thanks GitHub).
+ You can always check it to see what is the current state of the project.
+ </p>
+ <Button
+ variant="outlined"
+ color="primary"
+ startIcon={<TrendingUpIcon />}
+ href="https://github.com/orgs/which-ecosystem/projects/1"
+ >
+ track our progress
+ </Button>
+ </Typography>
+ </Grid>
+ </Grid>
+ </Grid>
+ </Grid>
+ </div>
+ );
+};
+
+export default HomePage;
+
diff --git a/src/pages/NotificationsPage/NotificationsPage.tsx b/src/pages/NotificationsPage/NotificationsPage.tsx
new file mode 100644
index 0000000..3c39ba3
--- /dev/null
+++ b/src/pages/NotificationsPage/NotificationsPage.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import { Typography } from '@material-ui/core';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ marginTop: theme.spacing(40),
+ textAlign: 'center'
+ }
+}));
+
+const NotificationsPage: React.FC = () => {
+ const classes = useStyles();
+
+ return (
+ <Typography variant="h4" className={classes.root}>
+ Sorry, this page is being constructed yet.
+ </Typography>
+ );
+};
+
+export default NotificationsPage;
+
diff --git a/src/pages/Page.tsx b/src/pages/Page.tsx
index 6d4315e..56d7372 100644
--- a/src/pages/Page.tsx
+++ b/src/pages/Page.tsx
@@ -1,28 +1,49 @@
import React from 'react';
-import { makeStyles } from '@material-ui/core/styles';
+import { makeStyles, useTheme } from '@material-ui/core/styles';
+import { useMediaQuery } from '@material-ui/core';
+import { SnackbarProvider } from 'notistack';
+
import ProfilePage from './ProfilePage/ProfilePage';
import FeedPage from './FeedPage/FeedPage';
import AuthPage from './AuthPage/AuthPage';
+import HomePage from './HomePage/HomePage';
+import NotificationsPage from './NotificationsPage/NotificationsPage';
import { useNavigate } from '../hooks/useNavigate';
+
const useStyles = makeStyles(theme => ({
root: {
- width: theme.spacing(75),
- marginTop: theme.spacing(15),
- margin: '0 auto'
+ [theme.breakpoints.down('sm')]: {
+ margin: theme.spacing(2, 0, 12, 0)
+ },
+ [theme.breakpoints.up('md')]: {
+ margin: theme.spacing(15, 5, 8, 5)
+ }
}
}));
const Page: React.FC = () => {
const { page } = useNavigate();
const classes = useStyles();
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
return (
- <div className={classes.root}>
- { page.prefix === 'profile' && <ProfilePage />}
- { page.prefix === 'feed' && <FeedPage /> }
- { page.prefix === 'auth' && <AuthPage /> }
- </div>
+ <SnackbarProvider
+ maxSnack={3}
+ anchorOrigin={{
+ vertical: isMobile ? 'top' : 'bottom',
+ horizontal: 'right'
+ }}
+ >
+ <div className={classes.root}>
+ { page.prefix === 'home' && <HomePage />}
+ { page.prefix === 'profile' && <ProfilePage />}
+ { page.prefix === 'feed' && <FeedPage /> }
+ { page.prefix === 'auth' && <AuthPage /> }
+ { page.prefix === 'notifications' && <NotificationsPage /> }
+ </div>
+ </SnackbarProvider>
);
};
diff --git a/src/pages/ProfilePage/ProfilePage.tsx b/src/pages/ProfilePage/ProfilePage.tsx
index 82e5cd8..ba4db7d 100644
--- a/src/pages/ProfilePage/ProfilePage.tsx
+++ b/src/pages/ProfilePage/ProfilePage.tsx
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { User, Poll } from 'which-types';
+import { Container } from '@material-ui/core';
import ProfileInfo from './ProfileInfo';
import Feed from '../../components/Feed/Feed';
@@ -24,6 +25,7 @@ const ProfilePage: React.FC = () => {
setUserInfo(response.data);
});
get(`/profiles/${id}`).then(response => {
+ setPolls([]);
setPolls(response.data);
setTotalVotes(response.data.reduce(
(total: number, current: Poll) => {
@@ -37,7 +39,7 @@ const ProfilePage: React.FC = () => {
}, [navigate, page, user]);
return (
- <>
+ <Container maxWidth="sm" disableGutters>
<ProfileInfo
userInfo={userInfo}
setUserInfo={setUserInfo}
@@ -46,7 +48,7 @@ const ProfilePage: React.FC = () => {
loading={isLoading}
/>
<Feed polls={[...polls]} />
- </>
+ </Container>
);
};