aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/components/Drawer/Drawer.tsx90
-rw-r--r--src/components/Drawer/UserInfo.tsx38
-rw-r--r--src/components/Header/Header.tsx22
-rw-r--r--src/components/Header/MobileHeader.tsx20
-rw-r--r--src/components/Header/SearchBar.tsx7
-rw-r--r--src/containers/Page/Page.tsx3
-rw-r--r--src/containers/Profile/MoreMenu.tsx72
-rw-r--r--src/containers/Profile/ProfileInfo.tsx2
8 files changed, 162 insertions, 92 deletions
diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx
new file mode 100644
index 0000000..9b416b0
--- /dev/null
+++ b/src/components/Drawer/Drawer.tsx
@@ -0,0 +1,90 @@
+import React, { useMemo, useEffect, useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
+import {
+ SwipeableDrawer,
+ List,
+ ListItem,
+ Typography,
+ Divider
+} from '@material-ui/core';
+import { ExitToApp as LogoutIcon, Info } from '@material-ui/icons';
+import { makeStyles } from '@material-ui/core/styles';
+
+import UserInfo from './UserInfo';
+import { useAuth } from '../../hooks/useAuth';
+
+interface PropTypes {
+ isOpen: boolean;
+ setIsOpen: (value: boolean) => void;
+}
+
+const useStyles = makeStyles(theme => ({
+ item: {
+ padding: theme.spacing(2, 14, 2, 2)
+ },
+ icon: {
+ marginRight: theme.spacing(1)
+ }
+}));
+
+
+const Drawer: React.FC<PropTypes> = React.memo(({ isOpen, setIsOpen }) => {
+ const classes = useStyles();
+ const history = useHistory();
+ const { user, logout } = useAuth();
+
+ const handleOpen = useCallback(() => {
+ setIsOpen(true);
+ }, [setIsOpen]);
+
+ const handleClose = useCallback(() => {
+ setIsOpen(false);
+ }, [setIsOpen]);
+
+ useEffect(() => {
+ // Close drawer on navigations
+ return history.listen(() => handleClose());
+ }, [history, handleClose]);
+
+ const handleLogout = useCallback(() => {
+ logout();
+ history.push('/login');
+ }, [logout, history]);
+
+ const handleAbout = useCallback(() => {
+ history.push('/');
+ }, [history]);
+
+ const iOS = useMemo(() => {
+ return /iPad|iPhone|iPod/.test(navigator.userAgent);
+ }, []);
+
+ return (
+ <SwipeableDrawer
+ anchor="left"
+ open={isOpen}
+ onOpen={handleOpen}
+ onClose={handleClose}
+ disableBackdropTransition={!iOS}
+ disableDiscovery={iOS}
+ >
+ {user && <UserInfo user={user} />}
+ <Divider />
+ <List>
+ {user && (
+ <ListItem button className={classes.item} onClick={handleLogout}>
+ <LogoutIcon className={classes.icon} />
+ <Typography>Logout</Typography>
+ </ListItem>
+ )}
+ <ListItem button className={classes.item} onClick={handleAbout}>
+ <Info className={classes.icon} />
+ <Typography>About</Typography>
+ </ListItem>
+ </List>
+ </SwipeableDrawer>
+ );
+});
+
+export default Drawer;
+
diff --git a/src/components/Drawer/UserInfo.tsx b/src/components/Drawer/UserInfo.tsx
new file mode 100644
index 0000000..027f076
--- /dev/null
+++ b/src/components/Drawer/UserInfo.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { Typography } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
+import { User } from 'which-types';
+
+import Avatar from '../Avatar/Avatar';
+
+interface PropTypes {
+ user: User;
+}
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ padding: theme.spacing(4, 10),
+ textAlign: 'center'
+ },
+ avatar: {
+ width: theme.spacing(14),
+ height: theme.spacing(14)
+ }
+}));
+
+
+const UserInfo: React.FC<PropTypes> = React.memo(({ user }) => {
+ const classes = useStyles();
+
+ return (
+ <div className={classes.root}>
+ <Avatar user={user} className={classes.avatar} />
+ <Typography variant="h5">
+ {user.username}
+ </Typography>
+ </div>
+ );
+});
+
+export default UserInfo;
+
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
index 5621dbf..224f6b0 100644
--- a/src/components/Header/Header.tsx
+++ b/src/components/Header/Header.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import {
IconButton,
@@ -9,8 +9,7 @@ import {
AccountCircle,
Notifications,
Home,
- Menu,
- Search
+ Menu
} from '@material-ui/icons';
import { makeStyles, useTheme } from '@material-ui/core/styles';
@@ -19,6 +18,7 @@ import MobileHeader from './MobileHeader';
import BottomBar from './BottomBar';
import BrowserHeader from './BrowserHeader';
import Avatar from '../Avatar/Avatar';
+import Drawer from '../Drawer/Drawer';
const useStyles = makeStyles(theme => ({
@@ -39,6 +39,7 @@ const Header: React.FC = React.memo(() => {
const theme = useTheme();
const history = useHistory();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
+ const [isDrawerOpen, setIsDrawerOpen] = useState<boolean>(false);
const handleHome = (): void => {
history.push('/');
@@ -57,6 +58,10 @@ const Header: React.FC = React.memo(() => {
history.push('/notifications');
};
+ const handleMenu = (): void => {
+ setIsDrawerOpen(true);
+ };
+
const feed = (
<IconButton onClick={handleFeed}>
<Home />
@@ -70,17 +75,11 @@ const Header: React.FC = React.memo(() => {
);
const menu = (
- <IconButton>
+ <IconButton onClick={handleMenu}>
<Menu />
</IconButton>
);
- const search = (
- <IconButton>
- <Search />
- </IconButton>
- );
-
const logo = (
<Typography variant="h5" className={classes.logo} onClick={handleHome}>
Which
@@ -99,8 +98,9 @@ const Header: React.FC = React.memo(() => {
return isMobile ? (
<>
- <MobileHeader logo={logo} menu={menu} search={search} />
+ <MobileHeader logo={logo} menu={menu} />
<BottomBar feed={feed} profile={profile} notifications={notifications} />
+ <Drawer isOpen={isDrawerOpen} setIsOpen={setIsDrawerOpen} />
</>
) : (
<BrowserHeader logo={logo} profile={profile} notifications={notifications} feed={feed} />
diff --git a/src/components/Header/MobileHeader.tsx b/src/components/Header/MobileHeader.tsx
index a20d54a..752e4bc 100644
--- a/src/components/Header/MobileHeader.tsx
+++ b/src/components/Header/MobileHeader.tsx
@@ -1,16 +1,18 @@
-import React from 'react';
+import React, { useState } from 'react';
import {
AppBar,
Toolbar,
+ IconButton,
useScrollTrigger,
Slide
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
+import { Search, Clear } from '@material-ui/icons';
+import SearchBar from './SearchBar';
interface PropTypes {
menu: JSX.Element;
logo: JSX.Element;
- search: JSX.Element;
}
const useStyles = makeStyles({
@@ -24,15 +26,23 @@ const useStyles = makeStyles({
const MobileHeader: React.FC<PropTypes> = React.memo(props => {
const classes = useStyles();
const trigger = useScrollTrigger();
- const { menu, search, logo } = props;
+ const [isSearchOpen, setIsSearchOpen] = useState<boolean>(false);
+
+ const { menu, logo } = props;
+
+ const handleToggle = () => {
+ setIsSearchOpen(value => !value);
+ };
return (
<Slide appear={false} direction="down" in={!trigger}>
<AppBar position="fixed">
<Toolbar className={classes.root}>
{menu}
- {logo}
- {search}
+ {isSearchOpen ? <SearchBar callback={handleToggle} /> : logo}
+ <IconButton onClick={handleToggle}>
+ {isSearchOpen ? <Clear /> : <Search />}
+ </IconButton>
</Toolbar>
</AppBar>
</Slide>
diff --git a/src/components/Header/SearchBar.tsx b/src/components/Header/SearchBar.tsx
index 8bfe0fb..ea6e6ac 100644
--- a/src/components/Header/SearchBar.tsx
+++ b/src/components/Header/SearchBar.tsx
@@ -15,6 +15,10 @@ import { User } from 'which-types';
import { get } from '../../requests';
import UserStrip from '../UserStrip/UserStrip';
+interface PropTypes {
+ callback?: () => void;
+}
+
const INTERVAL = 300;
const LIMIT = 7;
@@ -37,7 +41,7 @@ const useStyles = makeStyles(theme => ({
}
}));
-const SearchBar: React.FC = React.memo(() => {
+const SearchBar: React.FC<PropTypes> = React.memo(({ callback }) => {
const [results, setResults] = useState<User[]>([]);
const [query, setQuery] = useState<string>('');
const [debouncedQuery, setDebouncedQuery] = useState<string>(query);
@@ -72,6 +76,7 @@ const SearchBar: React.FC = React.memo(() => {
const { username } = results[index];
history.push(`/profile/${username}`);
handleClose();
+ if (callback) callback();
};
const SearchResults = (
diff --git a/src/containers/Page/Page.tsx b/src/containers/Page/Page.tsx
index f6a0aa5..d1171e6 100644
--- a/src/containers/Page/Page.tsx
+++ b/src/containers/Page/Page.tsx
@@ -16,7 +16,7 @@ const Notifications = React.lazy(() => import('../Notifications/Notifications'))
const useStyles = makeStyles(theme => ({
root: {
[theme.breakpoints.down('sm')]: {
- margin: theme.spacing(15, 0, 12, 0)
+ margin: theme.spacing(12, 0, 12, 0)
},
[theme.breakpoints.up('md')]: {
margin: theme.spacing(15, 5, 8, 5)
@@ -32,6 +32,7 @@ const Page: React.FC = () => {
return (
<SnackbarProvider
+ preventDuplicate
maxSnack={isMobile ? 1 : 3}
anchorOrigin={{
vertical: isMobile ? 'top' : 'bottom',
diff --git a/src/containers/Profile/MoreMenu.tsx b/src/containers/Profile/MoreMenu.tsx
deleted file mode 100644
index 1f41879..0000000
--- a/src/containers/Profile/MoreMenu.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-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/Profile/ProfileInfo.tsx b/src/containers/Profile/ProfileInfo.tsx
index a01c222..87af99d 100644
--- a/src/containers/Profile/ProfileInfo.tsx
+++ b/src/containers/Profile/ProfileInfo.tsx
@@ -5,7 +5,6 @@ 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 Avatar from '../../components/Avatar/Avatar';
@@ -110,7 +109,6 @@ const ProfileInfo: React.FC<PropTypes> = ({
: userInfo?._id === user?._id
? (
<div>
- <MoreMenu />
<div className={classes.avatarContainer}>
<Badge
overlap="circle"