Skip to content

Commit 79aa70c

Browse files
author
Emile Frey
committed
adds theme switcher and ability to nest subroutes
1 parent 1726b2e commit 79aa70c

File tree

12 files changed

+138
-52
lines changed

12 files changed

+138
-52
lines changed

frontend/src/components/Home.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, { useState, useContext } from 'react'
1+
import React, { useState, useContext, useRef } from 'react'
22
import axios, { AxiosRequestConfig } from 'axios';
33
import * as settings from '../settings';
44

55
import CssBaseline from '@material-ui/core/CssBaseline';
66
import { makeStyles } from '@material-ui/core/styles';
77
import { Container, Grid, Paper, Typography, Button, TextField, Tooltip, Switch, createStyles, Theme, withStyles, SwitchProps, SwitchClassKey } from '@material-ui/core';
8-
import { AuthProps } from '../App';
8+
import { AppProps } from '../App';
99
import { AlertContext } from '../contexts/AlertContext';
1010

1111
const useStyles = makeStyles((theme) => ({
@@ -104,12 +104,12 @@ const IOSSwitch = withStyles((theme: Theme) =>
104104
});
105105

106106

107-
function Home(props: AuthProps) {
107+
function Home(props: AppProps) {
108108

109109
const [name, setName] = useState("")
110110
const [helloName, setHelloName] = useState("")
111111
const [token, setToken] = useState(props.token)
112-
112+
const userInput = useRef<HTMLInputElement>(null)
113113
const { TriggerAlert } = useContext(AlertContext)
114114
const classes = useStyles()
115115

@@ -153,8 +153,7 @@ function Home(props: AuthProps) {
153153
autoComplete="name"
154154
autoFocus
155155
onChange={handleFormFieldChange}
156-
>
157-
</TextField>
156+
/>
158157
<Tooltip title={`Enter name and click submit to see a ${token === props.token ? 'successful' : 'failed'} request.`}>
159158
<Button variant="contained" color="primary" onClick={handleSubmit} disabled={name.length === 0}>
160159
Submit
@@ -173,7 +172,7 @@ function Home(props: AuthProps) {
173172
</Paper>
174173
</Grid>
175174
<Grid item xs={6}>
176-
<Paper className={classes.title}>
175+
<Paper className={classes.textInput}>
177176
<Typography variant="h6">
178177
Test Panel:
179178
</Typography>
@@ -193,6 +192,20 @@ function Home(props: AuthProps) {
193192
<Grid item>On</Grid>
194193
</Grid>
195194
</Typography>
195+
<Typography variant="subtitle2" style={{ marginTop: 20 }}>
196+
Pass Param to Nested Subroute: <span>&nbsp;</span>
197+
</Typography>
198+
<TextField
199+
margin="normal"
200+
fullWidth
201+
variant="outlined"
202+
id="userinput"
203+
label="User Input"
204+
name="User Input"
205+
inputRef={userInput} />
206+
<Button variant="contained" color="primary" onClick={() => props.history.push(`${props.location.pathname}/nestedsubroute/${userInput.current?.value}`)} >
207+
Submit
208+
</Button>
196209
</Paper>
197210
</Grid>
198211
</Grid>

frontend/src/components/Layout/DropdownMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default function DropdownMenu(props: DropdownMenuProps) {
5757
{props.dropdownButtonIcon}
5858
<ArrowDropDownIcon />
5959
</IconButton>
60-
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
60+
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal onClick={handleClose}>
6161
{({ TransitionProps, placement }) => (
6262
<Grow
6363
{...TransitionProps}

frontend/src/components/Layout/Layout.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import React from 'react';
22
import TopBar from "./TopBar"
33
import Footer from "./Footer"
44
import CssBaseline from '@material-ui/core/CssBaseline';
5-
import { AppProps } from '../../App'
65
import { Box, Tooltip, ListItem, Divider, Container, makeStyles, List } from '@material-ui/core';
76
import { useHistory, useLocation } from 'react-router-dom';
87
import { privateRoutes } from '../../routes/Routes'
8+
import { PrivateRouteProps } from '../../routes/PrivateRoute';
99

1010
const useStyles = makeStyles((theme) => ({
1111
body: {
@@ -48,7 +48,7 @@ const useStyles = makeStyles((theme) => ({
4848
}));
4949

5050

51-
function Layout(props: AppProps) {
51+
function Layout(props: PrivateRouteProps) {
5252
const classes = useStyles();
5353
let location = useLocation();
5454
let history = useHistory();
@@ -60,8 +60,8 @@ function Layout(props: AppProps) {
6060
.map((route, index) => {
6161
const { buttonTitle, pathname, Icon } = route;
6262
return (
63-
<Tooltip title={buttonTitle} aria-label={buttonTitle} key={index}>
64-
<ListItem className={classes.ListItem} button onClick={() => history.push(pathname)} selected={location.pathname === pathname}>
63+
<Tooltip title={buttonTitle ?? ""} aria-label={buttonTitle} key={index}>
64+
<ListItem className={classes.ListItem} button onClick={() => history.push(pathname)} selected={location.pathname.startsWith(pathname)}>
6565
{Icon && <Icon />}
6666
</ListItem>
6767
</Tooltip>
@@ -79,9 +79,11 @@ function Layout(props: AppProps) {
7979
<Divider />
8080
<main className={classes.content}>
8181
{props.isAuthenticated && <TopBar {...props} />}
82-
<Container maxWidth="xl" className={classes.container}>
83-
{props.children}
84-
</Container>
82+
{props.children &&
83+
<Container maxWidth="xl" className={classes.container}>
84+
{props.children}
85+
</Container>
86+
}
8587
<Footer />
8688
</main>
8789
</div>

frontend/src/components/Layout/TopBar.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import DropdownMenu from '../Layout/DropdownMenu';
88
import Brightness7Icon from '@material-ui/icons/Brightness7';
99
import Brightness4Icon from '@material-ui/icons/Brightness4';
1010
import { ThemeContext } from '../../contexts/ThemeContext';
11-
import { AppProps } from '../../App';
11+
import { useHistory } from 'react-router-dom';
12+
import { PrivateRouteProps } from '../../routes/PrivateRoute';
1213
const useStyles = makeStyles((theme) => ({
1314
root: {
1415
flexGrow: 1,
@@ -21,10 +22,10 @@ const useStyles = makeStyles((theme) => ({
2122
}
2223
}));
2324

24-
export default function TopBar(props: AppProps) {
25+
export default function TopBar(props: PrivateRouteProps) {
2526
const classes = useStyles();
2627
const { darkMode, setDarkMode } = useContext(ThemeContext);
27-
28+
const history = useHistory()
2829
return (
2930
<AppBar position="relative">
3031
<Toolbar className={props.isAuthenticated ? classes.authToolbar : undefined}>
@@ -39,7 +40,7 @@ export default function TopBar(props: AppProps) {
3940
{props.isAuthenticated && (
4041
<DropdownMenu dropdownButtonIcon={<AccountCircle />}>
4142
<div>
42-
<MenuItem component='a' href='/change_password'>Change Password</MenuItem>
43+
<MenuItem component='a' onClick={() => history.push('/change_password/')}>Change Password</MenuItem>
4344
<MenuItem onClick={() => props.logout()}>Logout</MenuItem>
4445
</div>
4546
</DropdownMenu>

frontend/src/components/Login/PasswordReset.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const PasswordReset = () => {
3535
setTokenInvalid(true)
3636
}
3737
})
38-
}, [])
38+
}, [token])
3939

4040
return tokenInvalid ? <Redirect from='/' to='/login' /> : <PasswordResetForm token={token} />
4141
}

frontend/src/components/Login/PasswordUpdate.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as settings from '../../settings';
55
import { makeStyles } from '@material-ui/core/styles';
66
import { Avatar, Button, Container, CssBaseline, LinearProgress, TextField, Typography } from '@material-ui/core';
77
import VpnKeyIcon from '@material-ui/icons/VpnKey';
8-
import { AuthProps } from '../../App';
8+
import { AppProps } from '../../App';
99
import { PasswordUpdateError } from '../../interfaces/axios/AxiosError';
1010
import ValidationMessages from '../../helpers/ValidationMessages'
1111

@@ -33,7 +33,7 @@ const useStyles = makeStyles((theme) => ({
3333
}));
3434

3535

36-
function PasswordUpdate(props: AuthProps) {
36+
function PasswordUpdate(props: AppProps) {
3737
const classes = useStyles();
3838
const [new_password1, setNewPassword1] = useState("");
3939
const [new_password2, setNewPassword2] = useState("");

frontend/src/contexts/ThemeContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { createContext, useState, useReducer, useEffect } from 'react';
1+
import React, { createContext, useState, useEffect } from 'react';
22

33
export type ThemeContextProps = {
44
darkMode: boolean

frontend/src/routes/AppRouter.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react'
2+
import { Redirect, Route, Switch } from 'react-router-dom';
3+
import { AppProps } from '../App';
4+
5+
6+
export const AppRouter: React.FC<AppProps> = (props: AppProps) => {
7+
const { route, path, match, location, history, ...rest } = props;
8+
return (
9+
<>
10+
{route?.subRoutes &&
11+
<Switch>
12+
{route.subRoutes.map((subRoute, i) => (
13+
<Route
14+
path={`${route.pathname}${subRoute.pathname}`}
15+
render={(subRouteProps: any) => <subRoute.component {...subRouteProps} {...rest} />}
16+
key={i}
17+
exact />
18+
))}
19+
<Route render={() => <Redirect to="/home" />} />
20+
</Switch>
21+
}
22+
</>
23+
)
24+
}

frontend/src/routes/Placeholder.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Button, Typography } from '@material-ui/core'
2+
import React from 'react'
3+
import { RouteComponentProps } from 'react-router-dom'
4+
import { AuthProps } from '../App'
5+
6+
interface MatchParams {
7+
userinput: string;
8+
}
9+
10+
interface MatchRouteProps extends AuthProps, RouteComponentProps<MatchParams> {
11+
}
12+
13+
const Placeholder = (props: MatchRouteProps) => {
14+
return (
15+
<div style={{ textAlign: "center" }}>
16+
<Typography variant="h5" style={{ margin: 20 }}>The user input detected by the route is: {props.match.params.userinput}</Typography>
17+
<Button variant="contained" color="primary" onClick={() => props.history.push('/home')}>Back Home</Button>
18+
</div>
19+
)
20+
}
21+
22+
export default Placeholder

frontend/src/routes/PrivateRoute.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
import React from "react";
2-
import { Route, Redirect } from "react-router-dom";
2+
import { Route, Redirect, RouteComponentProps } from "react-router-dom";
3+
import { AuthProps } from "../App";
34
import { Children } from "../interfaces/Children"
5+
import { routeInterface } from "./Routes";
46

5-
export interface PrivateRouteProps {
7+
export interface PrivateRouteProps extends AuthProps, RouteComponentProps {
68
isAuthenticated: boolean
7-
children: Children
9+
children?: Children
810
exact: boolean
911
path: string
12+
route: routeInterface
1013
}
1114

1215
// A wrapper for <Route> that redirects to the login screen if you're not yet authenticated.
13-
export default function PrivateRoute({ isAuthenticated, children, ...rest }: PrivateRouteProps) {
16+
export default function PrivateRoute({ isAuthenticated, route, ...rest }: PrivateRouteProps) {
1417
return (
1518
<Route
16-
{...rest}
17-
render={({ location }) =>
18-
isAuthenticated ? (
19-
children
20-
) : (
21-
<Redirect
22-
to={{
23-
pathname: "/login/",
24-
state: { from: location }
25-
}}
26-
/>
27-
)
19+
path={rest.path}
20+
render={(props: RouteComponentProps) =>
21+
isAuthenticated ? <route.component {...props} isAuthenticated={isAuthenticated} route={route} {...rest} /> : (
22+
<Redirect
23+
to={{
24+
pathname: "/login/",
25+
state: { from: props.location }
26+
}}
27+
/>
28+
)
2829
}
2930
/>
3031
);

0 commit comments

Comments
 (0)