diff options
author | dan <[email protected]> | 2023-08-28 10:55:50 -0400 |
---|---|---|
committer | dan <[email protected]> | 2023-08-28 10:55:50 -0400 |
commit | 7c9345f41d9c86142019cce05ca0495408b01730 (patch) | |
tree | 45da541a80047da04191c48accee490447f8a234 | |
parent | f8cf6c21d238fea96192ad3f418ab7489092355f (diff) | |
download | draggable-form-demo-7c9345f41d9c86142019cce05ca0495408b01730.tar.gz draggable-form-demo-7c9345f41d9c86142019cce05ca0495408b01730.tar.bz2 draggable-form-demo-7c9345f41d9c86142019cce05ca0495408b01730.zip |
feat: skeleton of app
-rw-r--r-- | package-lock.json | 12 | ||||
-rw-r--r-- | package.json | 18 | ||||
-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 |
13 files changed, 349 insertions, 27 deletions
diff --git a/package-lock.json b/package-lock.json index ccbce15..c75f8f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,25 @@ "name": "repeated-surveyer-frontend", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.14.6", + "@mui/material": "^5.14.6", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.15.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "eslint": "^8.48.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.33.2", + "prettier": "^3.0.2" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index 1fc68ff..6ca7e08 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,16 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.14.6", + "@mui/material": "^5.14.6", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.15.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -23,6 +28,12 @@ "react-app/jest" ] }, + "prettier": { + "singleQuote": true, + "semi": true, + "tabWidth": 2, + "useTabs": false + }, "browserslist": { "production": [ ">0.2%", @@ -34,5 +45,12 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "eslint": "^8.48.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.33.2", + "prettier": "^3.0.2" } } @@ -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</>; +} |