diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.js | 93 | ||||
| -rw-r--r-- | src/App.test.js | 8 | ||||
| -rw-r--r-- | src/CustomThemeProvider.js | 45 | ||||
| -rw-r--r-- | src/components/NavBar/index.js | 100 | ||||
| -rw-r--r-- | src/hooks/useLoginState.js | 33 | ||||
| -rw-r--r-- | src/pages/Login/index.js | 52 | ||||
| -rw-r--r-- | src/pages/NewSurvey/index.js | 3 | ||||
| -rw-r--r-- | src/pages/SurveyAssignees/index.js | 3 | ||||
| -rw-r--r-- | src/pages/SurveyResults/index.js | 3 | ||||
| -rw-r--r-- | src/pages/Surveys/index.js | 3 | ||||
| -rw-r--r-- | src/pages/Users/index.js | 3 | 
11 files changed, 319 insertions, 27 deletions
| @@ -1,25 +1,80 @@ -import logo from './logo.svg';  import './App.css'; +import { RouterProvider, createBrowserRouter, Navigate } from 'react-router-dom'; +import Login from './pages/Login'; +import Surveys from './pages/Surveys'; +import NewSurvey from './pages/NewSurvey'; +import SurveyResults from './pages/SurveyResults'; +import SurveyAssignees from './pages/SurveyAssignees'; +import Users from './pages/Users'; +import NavBar from './components/NavBar'; +import useLoginState from './hooks/useLoginState'; +import CssBaseline from '@mui/material/CssBaseline'; +import CustomThemeProvider from './CustomThemeProvider'; -function App() { +function routes({ login, logout, isLoggedIn }) { +   +  function withNavBar(component) { +    const navbarLinks = [ +      { label: 'Surveys', link: '/surveys' }, +      { label: 'New Survey', link: '/surveys/new' }, +      { label: 'Users', link: '/users' }, +    ]; +    return (<> +      <NavBar isLoggedIn={isLoggedIn} pages={navbarLinks} logout={logout} /> +      {component} +    </>); +  } + +  if (!isLoggedIn) { +    return ([ +      { +        path: '*', +        element: <Login login={login}/>, +      }, +    ]); +  } else { +    return ([ +      { +        path: '/', +        element: <Navigate to={{ pathname: '/surveys' }} />, +      }, +      { +        path: '/surveys', +        element: withNavBar(<Surveys />), +      }, +      { +        path: '/surveys/new', +        element: withNavBar(<NewSurvey />), +      }, +      { +        path: '/surveys/:surveyId/results', +        element: withNavBar(<SurveyResults />), +      }, +      { +        path: '/surveys/:surveyId/assignees', +        element: withNavBar(<SurveyAssignees />), +      }, +      { +        path: '/users', +        element: withNavBar(<Users />), +      }, +    ]); +  } + +} + +export default function App() { +  const { login, logout, isLoggedIn } = useLoginState(); +  const currentRoutes = routes({isLoggedIn, logout, login});    return ( -    <div className="App"> -      <header className="App-header"> -        <img src={logo} className="App-logo" alt="logo" /> -        <p> -          Edit <code>src/App.js</code> and save to reload. -        </p> -        <a -          className="App-link" -          href="https://reactjs.org" -          target="_blank" -          rel="noopener noreferrer" -        > -          Learn React -        </a> -      </header> -    </div> +    <> +      <CssBaseline /> +      <CustomThemeProvider> +        <RouterProvider router={ +          createBrowserRouter(currentRoutes) +        }/> +      </CustomThemeProvider> +    </>    );  } -export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { -  render(<App />); -  const linkElement = screen.getByText(/learn react/i); -  expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/CustomThemeProvider.js b/src/CustomThemeProvider.js new file mode 100644 index 0000000..f3f4a26 --- /dev/null +++ b/src/CustomThemeProvider.js @@ -0,0 +1,45 @@ +import {ThemeProvider} from '@mui/material'; +import {createTheme} from '@mui/material'; + +export default function CustomThemeProvider({children}) { +  console.log('theme', theme); +  return <ThemeProvider theme={theme}> +    { children } +  </ThemeProvider>; +} + +const themeOptions = { +  palette: { +    mode: 'light', +    primary: { +      main: '#0b0b14', +    }, +    secondary: { +      main: '#9c27b0', +    }, +  }, +  overrides: { +    MuiAppBar: { +      colorInherit: { +        backgroundColor: '#689f38', +        color: '#fff', +      }, +    }, +    MuiButton: { +      root: { +        background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)', +        border: 0, +        borderRadius: 3, +        boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)', +        color: 'white', +        height: 48, +        padding: '0 30px', +      }, +    }, +  }, +}; + + +const theme = createTheme(themeOptions); + + diff --git a/src/components/NavBar/index.js b/src/components/NavBar/index.js new file mode 100644 index 0000000..0bbc550 --- /dev/null +++ b/src/components/NavBar/index.js @@ -0,0 +1,100 @@ +import {useState} from 'react'; +import { Link } from 'react-router-dom'; +import {AppBar, Box, Button, Drawer, IconButton, List, ListItem, ListItemButton, ListItemText, Toolbar} from '@mui/material'; +import {Menu} from '@mui/icons-material'; + +export default function NavBar({ isLoggedIn, pages, logout }) { +  const drawerWidth = 200; + +  const [mobileOpen, setMobileOpen] = useState(false); + +  const handleDrawerToggle = () => { +    setMobileOpen(!mobileOpen); +  }; + +  return isLoggedIn && +    <Box sx={{ display: 'flex' }}> +      <AppBar +        component='nav' +        sx={{position: 'initial'}}> +        <Toolbar> +          <IconButton +            color="inherit" +            aria-label="open drawer" +            edge="start" +            onClick={handleDrawerToggle} +            sx={{ mr: 2, display: { sm: 'none' } }} +          > +            <Menu /> +          </IconButton> +          <Box sx={{ display: { xs: 'none', sm: 'block' } }}> +            {pages.map(p =>  +              p.hidden ||  +<Link key={p.link} to={p.link} style={{textDecorationLine:'none'}}> +  <Button key={p.label} sx={{ textDecorationLine:'none', color: '#ffffff' }}> +    {p.label} +  </Button> +</Link> +            )} +            <Button onClick={logout} sx={{color:'#ffffff'}}> +            Logout +            </Button> +          </Box> +        </Toolbar> +      </AppBar> + + +      <Box component="nav"> +        <Drawer +          sx={{ +            width: drawerWidth, +            flexShrink: 0, +            '& .MuiDrawer-paper': { +              width: drawerWidth, +              boxSizing: 'border-box', +            }, + +            display: { xs: 'block', sm: 'none' }, +          }} +          variant='temporary' +          anchor='left' +          open={mobileOpen} +          onClose={handleDrawerToggle} +          ModalProps={{ +            keepMounted:true, +          }} +        > +          <Box onClick={handleDrawerToggle} sx={{ textAlign: 'center' }}> +            <List> +              {pages.map(({label, link, hidden}) => ( +                hidden ? <div key={link}></div> : +                  <ListItem key={link} disablePadding> + +                    <Link to={link} style={{textDecorationLine:'none', width: '100%'}}> +                      <ListItemButton +                        key={label} +                        sx={{ +                          width:'100%', +                          color: 'primary.main', +                          textAlign: 'left', +                        }} +                      > +                        <ListItemText primary={label} sx={{ width:'100%'}} /> +                      </ListItemButton> +                    </Link> +                  </ListItem> +              ))} +              <ListItem disablePadding> +                <ListItemButton +                  onClick={logout} +                  sx={{width:'100%', color: 'primary.main' }} +                > +                  <ListItemText primary={'Logout'}/> +                </ListItemButton> +              </ListItem> +            </List> +          </Box> +        </Drawer> +      </Box> +    </Box>; +} diff --git a/src/hooks/useLoginState.js b/src/hooks/useLoginState.js new file mode 100644 index 0000000..b7c7221 --- /dev/null +++ b/src/hooks/useLoginState.js @@ -0,0 +1,33 @@ +import {useState} from 'react'; + +export default function useLoginState() { +  const [userInfo, setUserInfoState] = useState( +    localStorage.userInfo ? +      JSON.parse(localStorage.userInfo) : +      {} +  ); + +  function setUserInfo(userInfo) { +    setUserInfoState(userInfo); +    localStorage.userInfo = JSON.stringify(userInfo || {}); +  } + +  async function login(username, password) { +    console.log(`logging in: ${username}, ${password}`); +    // const userInfo = await api.login() +    const userInfo = { +      username +    }; +    console.log('Login success'); +    setUserInfo(userInfo); +    return userInfo; +  }  + +  function logout() { +    setUserInfo({}); +  } + +  const isLoggedIn = !!userInfo?.username; +   +  return {userInfo, isLoggedIn, login, logout}; +} diff --git a/src/pages/Login/index.js b/src/pages/Login/index.js new file mode 100644 index 0000000..1735d8e --- /dev/null +++ b/src/pages/Login/index.js @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import {Stack} from '@mui/system'; + +export default function Login({ login }) { +  const [user, setUser] = useState(''); +  const [password, setPassword] = useState(''); +  const [loginFailed, setLoginFailed] = useState(false); + +  const getApiKey = () => { +    login(user, password) +      .then(ok => { +        setLoginFailed(!ok); +      } +      ) +      .catch(console.error); +  }; + +  return ( +    <Box +      component="form" +      sx={{ +        '& > :not(style)': { m: 1 }, +        flexGrow: 1, +      }} +      noValidate +      autoComplete="off" +    > +      <Stack spacing={1} maxWidth='20em'> +        <h2>Repeated Surveyer</h2> +        <h3>Login</h3> +        <TextField +          label='Email Address' +          type='text' +          value={user} +          onChange={e => setUser(e.target.value.trim())} +        /> +        <TextField +          label='Password' +          type='password' +          value={password} +          onChange={e => setPassword(e.target.value)} +        /> +        <Button onClick={() => getApiKey()}>Login</Button> +        {loginFailed && <b>Login Failed</b>} +      </Stack> +    </Box> +  ); +} + diff --git a/src/pages/NewSurvey/index.js b/src/pages/NewSurvey/index.js new file mode 100644 index 0000000..a4b711a --- /dev/null +++ b/src/pages/NewSurvey/index.js @@ -0,0 +1,3 @@ +export default function NewSurvey() { +  return <>NewSurvey</>; +} diff --git a/src/pages/SurveyAssignees/index.js b/src/pages/SurveyAssignees/index.js new file mode 100644 index 0000000..3623217 --- /dev/null +++ b/src/pages/SurveyAssignees/index.js @@ -0,0 +1,3 @@ +export default function SurveyAssignees() { +  return <>SurveyAssignees</>; +} diff --git a/src/pages/SurveyResults/index.js b/src/pages/SurveyResults/index.js new file mode 100644 index 0000000..82d41c6 --- /dev/null +++ b/src/pages/SurveyResults/index.js @@ -0,0 +1,3 @@ +export default function SurveyResults() { +  return <>SurveyResults</>; +} diff --git a/src/pages/Surveys/index.js b/src/pages/Surveys/index.js new file mode 100644 index 0000000..69e854f --- /dev/null +++ b/src/pages/Surveys/index.js @@ -0,0 +1,3 @@ +export default function Surveys() { +  return <>Surveys</>; +} diff --git a/src/pages/Users/index.js b/src/pages/Users/index.js new file mode 100644 index 0000000..43afe5d --- /dev/null +++ b/src/pages/Users/index.js @@ -0,0 +1,3 @@ +export default function Users() { +  return <>Users</>; +} | 
