diff --git a/Birdmap.API/ClientApp/package.json b/Birdmap.API/ClientApp/package.json index 31aa884..e5fe9fd 100644 --- a/Birdmap.API/ClientApp/package.json +++ b/Birdmap.API/ClientApp/package.json @@ -12,7 +12,7 @@ "jquery": "^3.5.1", "merge": "1.2.1", "popper.js": "^1.16.0", - "react": "16.11.0", + "react": "^16.11.0", "react-dom": "16.11.0", "react-redux": "7.1.1", "react-router": "5.1.2", diff --git a/Birdmap.API/ClientApp/src/App.tsx b/Birdmap.API/ClientApp/src/App.tsx index b810209..62012d5 100644 --- a/Birdmap.API/ClientApp/src/App.tsx +++ b/Birdmap.API/ClientApp/src/App.tsx @@ -1,4 +1,4 @@ -import { Box, Container, IconButton, Menu, MenuItem } from '@material-ui/core'; +import { Box, Container, IconButton, Menu, MenuItem, MenuList, Paper, Grow, Popper } from '@material-ui/core'; import AccountCircle from '@material-ui/icons/AccountCircle'; import AppBar from '@material-ui/core/AppBar'; import blue from '@material-ui/core/colors/blue'; @@ -7,11 +7,12 @@ import { createMuiTheme, createStyles, makeStyles, Theme } from '@material-ui/co import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; import { ThemeProvider } from '@material-ui/styles'; -import React, { useState } from 'react'; -import { BrowserRouter, NavLink, Redirect, Route, Switch } from 'react-router-dom'; +import React, { useState, } from 'react'; +import { BrowserRouter, NavLink, Redirect, Route, Switch, Link } from 'react-router-dom'; import BirdmapTitle from './common/components/BirdmapTitle'; import Auth from './components/auth/Auth'; import AuthService from './components/auth/AuthService'; +import { ClickAwayListener } from '@material-ui/core'; const theme = createMuiTheme({ @@ -87,17 +88,47 @@ const PrivateRoute = ({ component: Component, authenticated: Authenticated, ...r const DefaultLayout = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => { const classes = useDefaultLayoutStyles(); - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); + const [open, setOpen] = React.useState(false); + const anchorRef = React.useRef(null); - const handleMenu = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); }; - const handleClose = () => { - setAnchorEl(null); + const handleClose = (event: React.MouseEvent) => { + if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) { + return; + } + + setOpen(false); }; + const handleLogout = (event: React.MouseEvent) => { + if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) { + return; + } + + AuthService.logout(); + setOpen(false); + }; + + function handleListKeyDown(event: React.KeyboardEvent) { + if (event.key === 'Tab') { + event.preventDefault(); + setOpen(false); + } + } + + const prevOpen = React.useRef(open); + React.useEffect(() => { + if (prevOpen.current === true && open === false) { + anchorRef.current!.focus(); + } + + prevOpen.current = open; + }, [open]); + + const renderNavLinks = () => { return Authenticated ? @@ -105,28 +136,28 @@ const DefaultLayout = ({ component: Component, authenticated: Authenticated, ... Devices Heatmap + onClick={handleToggle}> - - Logout - + + {({ TransitionProps, placement }) => ( + + + + + Logout + + + + + )} + : null; }; diff --git a/Birdmap.API/ClientApp/src/components/auth/Auth.tsx b/Birdmap.API/ClientApp/src/components/auth/Auth.tsx index b73418f..0c73a15 100644 --- a/Birdmap.API/ClientApp/src/components/auth/Auth.tsx +++ b/Birdmap.API/ClientApp/src/components/auth/Auth.tsx @@ -5,6 +5,7 @@ import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; import AuthService from './AuthService'; export default function Auth(props: any) { + props.onAuthenticated(); const history = useHistory(); const classes = useStyles(); @@ -36,7 +37,6 @@ export default function Auth(props: any) { }; const onLoginClicked = () => { - setIsLoggingIn(true); if (!username) { setShowError(true); @@ -52,6 +52,7 @@ export default function Auth(props: any) { return; } + setIsLoggingIn(true); AuthService.login(username, password) .then(() => { props.onAuthenticated(); @@ -86,7 +87,7 @@ export default function Auth(props: any) { - + diff --git a/Birdmap.API/ClientApp/src/components/auth/AuthService.js b/Birdmap.API/ClientApp/src/components/auth/AuthService.js index f4f876c..14969ad 100644 --- a/Birdmap.API/ClientApp/src/components/auth/AuthService.js +++ b/Birdmap.API/ClientApp/src/components/auth/AuthService.js @@ -9,6 +9,10 @@ exports.default = { isAdmin: function () { return sessionStorage.getItem('role') === 'Admin'; }, + logout: function () { + sessionStorage.removeItem('user'); + sessionStorage.removeItem('role'); + }, login: function (username, password) { var body = { username: username, diff --git a/Birdmap.API/ClientApp/src/components/auth/AuthService.js.map b/Birdmap.API/ClientApp/src/components/auth/AuthService.js.map index e4e1839..b25ff20 100644 --- a/Birdmap.API/ClientApp/src/components/auth/AuthService.js.map +++ b/Birdmap.API/ClientApp/src/components/auth/AuthService.js.map @@ -1 +1 @@ -{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["AuthService.ts"],"names":[],"mappings":";;AAAA,wDAAmD;AAEnD,IAAM,SAAS,GAAG,uBAAuB,CAAC;AAE1C,kBAAe;IACX,eAAe;QACX,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACnD,CAAC;IAED,OAAO;QACH,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IACtD,CAAC;IAED,KAAK,EAAL,UAAM,QAAgB,EAAE,QAAgB;QACpC,IAAI,IAAI,GAAG;YACP,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;SACrB,CAAC;QACF,IAAI,OAAO,GAAG;YACV,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACL,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC7B,CAAC;QAEF,OAAO,qBAAW,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC;aAC7C,IAAI,CAAC,UAAA,QAAQ;YACV,cAAc,CAAC,OAAO,CAAC,MAAM,EAAK,QAAQ,CAAC,UAAU,SAAI,QAAQ,CAAC,YAAc,CAAC,CAAC;YAClF,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC9C,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACX,CAAC;CACJ,CAAA"} \ No newline at end of file +{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["AuthService.ts"],"names":[],"mappings":";;AAAA,wDAAmD;AAEnD,IAAM,SAAS,GAAG,uBAAuB,CAAC;AAE1C,kBAAe;IACX,eAAe;QACX,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACnD,CAAC;IAED,OAAO;QACH,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IACtD,CAAC;IAED,MAAM;QACF,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,EAAL,UAAM,QAAgB,EAAE,QAAgB;QACpC,IAAI,IAAI,GAAG;YACP,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;SACrB,CAAC;QACF,IAAI,OAAO,GAAG;YACV,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACL,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC7B,CAAC;QAEF,OAAO,qBAAW,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC;aAC7C,IAAI,CAAC,UAAA,QAAQ;YACV,cAAc,CAAC,OAAO,CAAC,MAAM,EAAK,QAAQ,CAAC,UAAU,SAAI,QAAQ,CAAC,YAAc,CAAC,CAAC;YAClF,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC9C,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACX,CAAC;CACJ,CAAA"} \ No newline at end of file diff --git a/Birdmap.API/ClientApp/src/components/auth/AuthService.ts b/Birdmap.API/ClientApp/src/components/auth/AuthService.ts index 0b51cd8..d9227fc 100644 --- a/Birdmap.API/ClientApp/src/components/auth/AuthService.ts +++ b/Birdmap.API/ClientApp/src/components/auth/AuthService.ts @@ -11,6 +11,11 @@ export default { return sessionStorage.getItem('role') === 'Admin'; }, + logout() { + sessionStorage.removeItem('user'); + sessionStorage.removeItem('role'); + }, + login(username: string, password: string) { let body = { username: username, diff --git a/Birdmap.API/appsettings.json b/Birdmap.API/appsettings.json index ca56e51..f774618 100644 --- a/Birdmap.API/appsettings.json +++ b/Birdmap.API/appsettings.json @@ -8,7 +8,8 @@ }, "AllowedHosts": "*", "Secret": "7vj.3KW.hYE!}4u6", - "LocalDbConnectionString": "Data Source=DESKTOP-A6JQ6B5\\SQLEXPRESS;Initial Catalog=Birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", + // "LocalDbConnectionString": "Data Source=DESKTOP-3600\\SQLEXPRESS;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", + "LocalDbConnectionString": "Data Source=DESKTOP-3600;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", "Defaults": { "Services": { "Local Database": "https://localhost:44331/health", @@ -18,7 +19,7 @@ { "Name": "admin", "Password": "pass", - "Role": "Admin" + "Role": "Admin" }, { "Name": "user",