#7 Introduce react-router
This commit is contained in:
parent
d9471d7842
commit
720d8b5eed
|
@ -11,13 +11,26 @@ The app is written in typescript and react and uses apollo to query the backend
|
|||
|
||||
* Install `npm`
|
||||
* In this directory run `npm ci` to install all dependencies according to the package.json and package-lock.json.
|
||||
*
|
||||
|
||||
## Available Scripts
|
||||
### Develop locally
|
||||
|
||||
* In the parent directory run
|
||||
```shell script
|
||||
docker-compose up
|
||||
```
|
||||
which will start the whole setup including this app in a dockerfile.
|
||||
However, rebuilding and restarting this image can be cumbersome and is not necessary in the development setup.
|
||||
* Instead run
|
||||
```shell script
|
||||
npm start
|
||||
```
|
||||
and access the app at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
#### Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
##### `npm start`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
@ -28,12 +41,12 @@ You will also see any lint errors in the console.
|
|||
Running the app without the backend server makes little sense.
|
||||
Start it under [http://localhost:5000](http://localhost:5000) as specified in the Readme of the backend server (../backend)
|
||||
|
||||
### `npm test`
|
||||
##### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br>
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
##### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br>
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
@ -43,7 +56,7 @@ Your app is ready to be deployed!
|
|||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
##### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ WORKDIR /app
|
|||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
RUN npm ci
|
||||
|
||||
COPY public ./public
|
||||
COPY tsconfig.json logo.svg ./
|
||||
|
|
107
redaktions-app/package-lock.json
generated
107
redaktions-app/package-lock.json
generated
|
@ -1814,6 +1814,11 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/history": {
|
||||
"version": "4.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.7.tgz",
|
||||
"integrity": "sha512-2xtoL22/3Mv6a70i4+4RB7VgbDDORoWwjcqeNysojZA0R7NK17RbY5Gof/2QiFfJgX+KkWghbwJ+d/2SB8Ndzg=="
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
|
@ -1891,6 +1896,25 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
|
||||
"integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==",
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-router-dom": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz",
|
||||
"integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==",
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
|
||||
|
@ -6498,6 +6522,19 @@
|
|||
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
|
||||
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
|
||||
},
|
||||
"history": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^3.0.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0",
|
||||
"value-equal": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
|
@ -8470,6 +8507,15 @@
|
|||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
|
||||
},
|
||||
"mini-create-react-context": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz",
|
||||
"integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"tiny-warning": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"mini-css-extract-plugin": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz",
|
||||
|
@ -10862,6 +10908,52 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
||||
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"mini-create-react-context": "^0.4.0",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.6.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||
"requires": {
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
|
||||
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"history": "^4.9.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-router": "5.2.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-scripts": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.3.tgz",
|
||||
|
@ -11235,6 +11327,11 @@
|
|||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
|
||||
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
|
||||
},
|
||||
"resolve-pathname": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
|
||||
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
|
||||
},
|
||||
"resolve-url": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
||||
|
@ -12678,6 +12775,11 @@
|
|||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
|
||||
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
|
||||
},
|
||||
"tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
|
@ -13067,6 +13169,11 @@
|
|||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"value-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
|
@ -13,9 +13,11 @@
|
|||
"@types/node": "^12.12.54",
|
||||
"@types/react": "^16.9.46",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"graphql": "^15.3.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.3",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
|
|
|
@ -1,19 +1,39 @@
|
|||
import React from 'react';
|
||||
import './App.css';
|
||||
import SignIn from "./components/SignIn";
|
||||
import {ApolloProvider} from "@apollo/client";
|
||||
import {client} from "./backend-connection/helper";
|
||||
import React from "react";
|
||||
import Main from "./components/Main";
|
||||
import {Redirect, Route, Switch} from "react-router-dom";
|
||||
import SignIn from "./components/SignIn";
|
||||
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
{localStorage.getItem("token")
|
||||
? <Main />
|
||||
: <SignIn/>}
|
||||
</ApolloProvider>
|
||||
<Switch>
|
||||
<PrivateRoute exact path={"/"}><Main /></PrivateRoute>
|
||||
<Route path={"/login"}><SignIn /></Route>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
||||
export const isLoggedIn = () => !!localStorage.getItem("token")
|
||||
|
||||
// @ts-ignore
|
||||
function PrivateRoute({ children, ...rest }) {
|
||||
return (
|
||||
<Route
|
||||
{...rest}
|
||||
render={({ location }) =>
|
||||
isLoggedIn() ? (
|
||||
children
|
||||
) : (
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: "/login",
|
||||
state: { from: location }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import MenuIcon from '@material-ui/icons/Menu';
|
|||
import Menu from '@material-ui/core/Menu';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import {makeStyles} from "@material-ui/core/styles";
|
||||
import {useHistory} from 'react-router-dom';
|
||||
|
||||
|
||||
const useStyles = makeStyles({
|
||||
|
@ -20,6 +21,7 @@ function CustomAppBar() {
|
|||
const classes = useStyles()
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const history = useHistory();
|
||||
|
||||
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
|
@ -27,7 +29,7 @@ function CustomAppBar() {
|
|||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('token')
|
||||
window.location.href = window.location.origin
|
||||
history.replace("/login")
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import CustomAppBar from "./CustomAppBar";
|
||||
import {Box, Container, Grid, Paper} from "@material-ui/core";
|
||||
import {Container, Paper} from "@material-ui/core";
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import {makeStyles} from "@material-ui/core/styles";
|
||||
|
|
|
@ -14,6 +14,8 @@ import Container from '@material-ui/core/Container';
|
|||
import {gql, useMutation} from "@apollo/client";
|
||||
import ButtonWithSpinner from "./ButtonWithSpinner";
|
||||
import {Copyright} from "./Copyright";
|
||||
import {isLoggedIn} from "../App";
|
||||
import {useHistory} from 'react-router-dom';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
paper: {
|
||||
|
@ -42,21 +44,26 @@ const useStyles = makeStyles((theme) => ({
|
|||
export default function SignIn() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [login, { loading, error, data }] = useMutation(loginMutation);
|
||||
const [login, {loading, error, data}] = useMutation(loginMutation);
|
||||
const history = useHistory();
|
||||
const classes = useStyles();
|
||||
|
||||
if (isLoggedIn()) {
|
||||
history.replace("/")
|
||||
return <p>Already logged in, redirecting to main page</p>
|
||||
}
|
||||
if (data && data.authenticate.jwtToken) {
|
||||
localStorage.setItem("token", data.authenticate.jwtToken)
|
||||
window.location.reload()
|
||||
return <span>{`Success!`}</span>
|
||||
localStorage.setItem("token", data.authenticate.jwtToken)
|
||||
history.replace("/")
|
||||
return <span>{`Success!`}</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline />
|
||||
<CssBaseline/>
|
||||
<div className={classes.paper}>
|
||||
<Avatar className={classes.avatar}>
|
||||
<LockOutlinedIcon />
|
||||
<LockOutlinedIcon/>
|
||||
</Avatar>
|
||||
<Typography component="h1" variant="h5">
|
||||
Sign in
|
||||
|
@ -96,15 +103,17 @@ export default function SignIn() {
|
|||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={<Checkbox value="remember" color="primary" />}
|
||||
disabled={true}
|
||||
control={<Checkbox value="remember" color="primary"/>}
|
||||
label="Remember me"
|
||||
/>
|
||||
<ButtonWithSpinner
|
||||
handleClick={() => {}}
|
||||
handleClick={() => {
|
||||
}}
|
||||
loading={loading}
|
||||
type="submit"
|
||||
fullWidth
|
||||
>Sign In</ButtonWithSpinner>
|
||||
>Sign In</ButtonWithSpinner>
|
||||
<p className={classes.error}>
|
||||
{error
|
||||
? `Error while trying to log in: ${error.message}`
|
||||
|
@ -112,7 +121,7 @@ export default function SignIn() {
|
|||
</p>
|
||||
<Grid container>
|
||||
<Grid item xs>
|
||||
<Link href="#" variant="body2">
|
||||
<Link href="#" variant="body2" aria-disabled={true}>
|
||||
Forgot password?
|
||||
</Link>
|
||||
</Grid>
|
||||
|
@ -125,7 +134,7 @@ export default function SignIn() {
|
|||
</form>
|
||||
</div>
|
||||
<Box mt={8}>
|
||||
<Copyright />
|
||||
<Copyright/>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
|
|
|
@ -3,11 +3,16 @@ import ReactDOM from 'react-dom';
|
|||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import {ApolloProvider} from "@apollo/client";
|
||||
import {client} from "./backend-connection/helper";
|
||||
import {BrowserRouter as Router} from "react-router-dom";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
<ApolloProvider client={client}>
|
||||
<Router>
|
||||
<App/>
|
||||
</Router>
|
||||
</ApolloProvider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue