aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordan <[email protected]>2023-08-28 10:55:50 -0400
committerdan <[email protected]>2023-08-28 10:55:50 -0400
commit7c9345f41d9c86142019cce05ca0495408b01730 (patch)
tree45da541a80047da04191c48accee490447f8a234
parentf8cf6c21d238fea96192ad3f418ab7489092355f (diff)
downloaddraggable-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.json12
-rw-r--r--package.json18
-rw-r--r--src/App.js93
-rw-r--r--src/App.test.js8
-rw-r--r--src/CustomThemeProvider.js45
-rw-r--r--src/components/NavBar/index.js100
-rw-r--r--src/hooks/useLoginState.js33
-rw-r--r--src/pages/Login/index.js52
-rw-r--r--src/pages/NewSurvey/index.js3
-rw-r--r--src/pages/SurveyAssignees/index.js3
-rw-r--r--src/pages/SurveyResults/index.js3
-rw-r--r--src/pages/Surveys/index.js3
-rw-r--r--src/pages/Users/index.js3
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"
}
}
diff --git a/src/App.js b/src/App.js
index 3784575..9d9ee58 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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</>;
+}