mirror of
https://github.com/rjNemo/ticket_manager
synced 2026-06-06 00:36:39 +00:00
Account (#5)
* build client * build client * update .gitignore * update signin logo * created profile popover in navbar * add accountcontroller to retrieve userinfo and accountpage * set account page layout * account form updates user profile * update react scripts
This commit is contained in:
parent
913dbbf0a3
commit
f4b94a2162
35 changed files with 11814 additions and 37 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -11,4 +11,4 @@ Tests/TicketManager.Tests/obj/
|
||||||
client/node_modules/
|
client/node_modules/
|
||||||
client/src/authentication/config.json
|
client/src/authentication/config.json
|
||||||
client/src/pages/TestPage.tsx
|
client/src/pages/TestPage.tsx
|
||||||
|
client/build
|
||||||
|
|
@ -8,9 +8,11 @@ using TicketManager.Data;
|
||||||
using TicketManager.Resources;
|
using TicketManager.Resources;
|
||||||
using TicketManager.Models;
|
using TicketManager.Models;
|
||||||
|
|
||||||
|
|
||||||
namespace TicketManager.Controllers
|
namespace TicketManager.Controllers
|
||||||
{
|
{
|
||||||
[Authorize]
|
[Authorize]
|
||||||
|
[Produces("application/json")]
|
||||||
[Route("api/v1/[controller]")]
|
[Route("api/v1/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class TicketsController : ControllerBase
|
public class TicketsController : ControllerBase
|
||||||
|
|
|
||||||
BIN
app.db
BIN
app.db
Binary file not shown.
19
client/build/asset-manifest.json
Normal file
19
client/build/asset-manifest.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"main.js": "/static/js/main.2d8aca08.chunk.js",
|
||||||
|
"main.js.map": "/static/js/main.2d8aca08.chunk.js.map",
|
||||||
|
"runtime-main.js": "/static/js/runtime-main.cea588d5.js",
|
||||||
|
"runtime-main.js.map": "/static/js/runtime-main.cea588d5.js.map",
|
||||||
|
"static/js/2.f3326ec1.chunk.js": "/static/js/2.f3326ec1.chunk.js",
|
||||||
|
"static/js/2.f3326ec1.chunk.js.map": "/static/js/2.f3326ec1.chunk.js.map",
|
||||||
|
"index.html": "/index.html",
|
||||||
|
"precache-manifest.805f1d41ceb289334c4067299de2458e.js": "/precache-manifest.805f1d41ceb289334c4067299de2458e.js",
|
||||||
|
"service-worker.js": "/service-worker.js",
|
||||||
|
"static/js/2.f3326ec1.chunk.js.LICENSE.txt": "/static/js/2.f3326ec1.chunk.js.LICENSE.txt"
|
||||||
|
},
|
||||||
|
"entrypoints": [
|
||||||
|
"static/js/runtime-main.cea588d5.js",
|
||||||
|
"static/js/2.f3326ec1.chunk.js",
|
||||||
|
"static/js/main.2d8aca08.chunk.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
client/build/favicon.ico
Normal file
BIN
client/build/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
1
client/build/index.html
Normal file
1
client/build/index.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="Ticket Manager App" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/><title>Ticket Manager App</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function t(t){for(var n,l,i=t[0],f=t[1],a=t[2],p=0,s=[];p<i.length;p++)l=i[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,i=1;i<r.length;i++){var f=r[i];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=l(l.s=r[0]))}return e}var n={},o={1:0},u=[];function l(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=e,l.c=n,l.d=function(e,t,r){l.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,t){if(1&t&&(e=l(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(l.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)l.d(r,n,function(t){return e[t]}.bind(null,n));return r},l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,"a",t),t},l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},l.p="/";var i=this.webpackJsonpclient=this.webpackJsonpclient||[],f=i.push.bind(i);i.push=t,i=i.slice();for(var a=0;a<i.length;a++)t(i[a]);var c=f;r()}([])</script><script src="/static/js/2.f3326ec1.chunk.js"></script><script src="/static/js/main.2d8aca08.chunk.js"></script></body></html>
|
||||||
BIN
client/build/logo192.png
Normal file
BIN
client/build/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
client/build/logo512.png
Normal file
BIN
client/build/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
client/build/manifest.json
Normal file
25
client/build/manifest.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"short_name": "BugBuster",
|
||||||
|
"name": "BugBuster | Project Management",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||||
|
{
|
||||||
|
"revision": "ed78f568f574416d38907d84d79ef158",
|
||||||
|
"url": "/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "66f5a9b6e84ae5628df4",
|
||||||
|
"url": "/static/js/2.f3326ec1.chunk.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "3adc01bea76e7956dc3633ee898f6936",
|
||||||
|
"url": "/static/js/2.f3326ec1.chunk.js.LICENSE.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "1c7010ac218763e7f78b",
|
||||||
|
"url": "/static/js/main.2d8aca08.chunk.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "5d93c6b2d15332364552",
|
||||||
|
"url": "/static/js/runtime-main.cea588d5.js"
|
||||||
|
}
|
||||||
|
]);
|
||||||
2
client/build/robots.txt
Normal file
2
client/build/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
39
client/build/service-worker.js
Normal file
39
client/build/service-worker.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* Welcome to your Workbox-powered service worker!
|
||||||
|
*
|
||||||
|
* You'll need to register this file in your web app and you should
|
||||||
|
* disable HTTP caching for this file too.
|
||||||
|
* See https://goo.gl/nhQhGp
|
||||||
|
*
|
||||||
|
* The rest of the code is auto-generated. Please don't update this file
|
||||||
|
* directly; instead, make changes to your Workbox build configuration
|
||||||
|
* and re-run your build process.
|
||||||
|
* See https://goo.gl/2aRDsh
|
||||||
|
*/
|
||||||
|
|
||||||
|
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||||
|
|
||||||
|
importScripts(
|
||||||
|
"/precache-manifest.805f1d41ceb289334c4067299de2458e.js"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.addEventListener('message', (event) => {
|
||||||
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
|
self.skipWaiting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
workbox.core.clientsClaim();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
|
||||||
|
* requests for URLs in the manifest.
|
||||||
|
* See https://goo.gl/S9QRab
|
||||||
|
*/
|
||||||
|
self.__precacheManifest = [].concat(self.__precacheManifest || []);
|
||||||
|
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
||||||
|
|
||||||
|
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
|
||||||
|
|
||||||
|
blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
|
||||||
|
});
|
||||||
3
client/build/static/js/2.f3326ec1.chunk.js
Normal file
3
client/build/static/js/2.f3326ec1.chunk.js
Normal file
File diff suppressed because one or more lines are too long
49
client/build/static/js/2.f3326ec1.chunk.js.LICENSE.txt
Normal file
49
client/build/static/js/2.f3326ec1.chunk.js.LICENSE.txt
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
object-assign
|
||||||
|
(c) Sindre Sorhus
|
||||||
|
@license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A better abstraction over CSS.
|
||||||
|
*
|
||||||
|
* @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present
|
||||||
|
* @website https://github.com/cssinjs/jss
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @license React v0.18.0
|
||||||
|
* scheduler.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @license React v16.12.0
|
||||||
|
* react-dom.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @license React v16.12.0
|
||||||
|
* react-is.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @license React v16.12.0
|
||||||
|
* react.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
1
client/build/static/js/2.f3326ec1.chunk.js.map
Normal file
1
client/build/static/js/2.f3326ec1.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
client/build/static/js/main.2d8aca08.chunk.js
Normal file
2
client/build/static/js/main.2d8aca08.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
client/build/static/js/main.2d8aca08.chunk.js.map
Normal file
1
client/build/static/js/main.2d8aca08.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
client/build/static/js/runtime-main.cea588d5.js
Normal file
2
client/build/static/js/runtime-main.cea588d5.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
!function(e){function t(t){for(var n,l,i=t[0],f=t[1],a=t[2],p=0,s=[];p<i.length;p++)l=i[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,i=1;i<r.length;i++){var f=r[i];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=l(l.s=r[0]))}return e}var n={},o={1:0},u=[];function l(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=e,l.c=n,l.d=function(e,t,r){l.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},l.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,t){if(1&t&&(e=l(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(l.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)l.d(r,n,function(t){return e[t]}.bind(null,n));return r},l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,"a",t),t},l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},l.p="/";var i=this.webpackJsonpclient=this.webpackJsonpclient||[],f=i.push.bind(i);i.push=t,i=i.slice();for(var a=0;a<i.length;a++)t(i[a]);var c=f;r()}([]);
|
||||||
|
//# sourceMappingURL=runtime-main.cea588d5.js.map
|
||||||
1
client/build/static/js/runtime-main.cea588d5.js.map
Normal file
1
client/build/static/js/runtime-main.cea588d5.js.map
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +6,7 @@
|
||||||
"@auth0/auth0-spa-js": "^1.6.4",
|
"@auth0/auth0-spa-js": "^1.6.4",
|
||||||
"@material-ui/core": "^4.9.13",
|
"@material-ui/core": "^4.9.13",
|
||||||
"@material-ui/icons": "^4.9.1",
|
"@material-ui/icons": "^4.9.1",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.47",
|
"@material-ui/lab": "^4.0.0-alpha.52",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.4.0",
|
"@testing-library/react": "^9.4.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-scripts": "3.3.1",
|
"react-scripts": "^3.4.1",
|
||||||
"react-swipeable-views": "^0.13.9",
|
"react-swipeable-views": "^0.13.9",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
"underscore": "^1.9.2"
|
"underscore": "^1.9.2"
|
||||||
|
|
@ -48,4 +48,4 @@
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
AppBar,
|
AppBar,
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
// IconButton,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
Typography,
|
Typography,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Popover,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||||
import MenuIcon from "@material-ui/icons/Menu";
|
// import MenuIcon from "@material-ui/icons/Menu";
|
||||||
|
import BugReportIcon from "@material-ui/icons/BugReport";
|
||||||
import * as ROUTES from "../constants/routes";
|
import * as ROUTES from "../constants/routes";
|
||||||
import { useAuth0 } from "../authentication/auth0";
|
import { useAuth0 } from "../authentication/auth0";
|
||||||
import { getUID } from "../authentication/helpers";
|
import { getUID } from "../authentication/helpers";
|
||||||
|
|
@ -25,6 +29,9 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||||
title: {
|
title: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
},
|
},
|
||||||
|
typography: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -32,20 +39,35 @@ export default function ButtonAppBar() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { isAuthenticated, loginWithRedirect, logout, user } = useAuth0();
|
const { isAuthenticated, loginWithRedirect, logout, user } = useAuth0();
|
||||||
|
|
||||||
|
const [anchor, setAnchor] = useState<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) =>
|
||||||
|
setAnchor(e.currentTarget);
|
||||||
|
|
||||||
|
const handleClose = () => setAnchor(null);
|
||||||
|
|
||||||
|
const open: boolean = !!anchor;
|
||||||
|
const id = open ? "profile-popover" : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton
|
{/* <IconButton
|
||||||
edge="start"
|
edge="start"
|
||||||
className={classes.menuButton}
|
className={classes.menuButton}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
aria-label="menu"
|
aria-label="menu"
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton> */}
|
||||||
<Typography variant="h6" className={classes.title}>
|
<Typography variant="h6" className={classes.title}>
|
||||||
<Button color="inherit" component={Link} to={ROUTES.HOME}>
|
<Button
|
||||||
|
color="inherit"
|
||||||
|
component={Link}
|
||||||
|
to={ROUTES.HOME}
|
||||||
|
startIcon={<BugReportIcon />}
|
||||||
|
>
|
||||||
BugBuster
|
BugBuster
|
||||||
</Button>
|
</Button>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
@ -60,15 +82,54 @@ export default function ButtonAppBar() {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
color="inherit"
|
aria-describedby={id}
|
||||||
component={Link}
|
color="primary"
|
||||||
to={`${ROUTES.USERS}/${getUID(user)}`}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Avatar src={user.picture} />
|
<Avatar src={user.picture} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="inherit" onClick={() => logout()}>
|
<Popover
|
||||||
Log out
|
id={id}
|
||||||
</Button>
|
open={open}
|
||||||
|
anchorEl={anchor}
|
||||||
|
onClose={handleClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "bottom",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<Button
|
||||||
|
color="inherit"
|
||||||
|
component={Link}
|
||||||
|
to={`${ROUTES.USERS}/${getUID(user)}`}
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
Profile
|
||||||
|
</Button>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Button
|
||||||
|
color="inherit"
|
||||||
|
component={Link}
|
||||||
|
to={ROUTES.ACCOUNT}
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
Edit Profile
|
||||||
|
</Button>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Button color="inherit" onClick={() => logout()}>
|
||||||
|
Log out
|
||||||
|
</Button>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Popover>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,43 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
import { TextField } from "@material-ui/core";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
label: string;
|
||||||
|
type?: string;
|
||||||
|
state: string;
|
||||||
|
className?: string;
|
||||||
|
multiline?: boolean;
|
||||||
|
setState: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputField: FC<IProps> = ({
|
||||||
|
label,
|
||||||
|
type = "text",
|
||||||
|
multiline = false,
|
||||||
|
state,
|
||||||
|
setState,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
// update text after user input
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setState(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
const InputField: FC = () => {
|
|
||||||
return (
|
return (
|
||||||
<div className="input-field">
|
<TextField
|
||||||
<input id="email" type="text" className="validate" />
|
label={label}
|
||||||
<label htmlFor="email">Email</label>
|
value={state}
|
||||||
</div>
|
onChange={handleChange}
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
type={type}
|
||||||
|
multiline={multiline}
|
||||||
|
size="small"
|
||||||
|
// autoFocus
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import InputField from "./InputField";
|
|
||||||
import PasswordField from "./PasswordField";
|
import PasswordField from "./PasswordField";
|
||||||
import Button from "./Buttons/Button";
|
import Button from "./Buttons/Button";
|
||||||
|
|
||||||
|
|
@ -10,7 +10,8 @@ export const LogInForm: FC = () => {
|
||||||
<div className="center ">
|
<div className="center ">
|
||||||
<h4>Login</h4>
|
<h4>Login</h4>
|
||||||
<form className="col s10 offset-s1">
|
<form className="col s10 offset-s1">
|
||||||
<InputField />
|
<></>
|
||||||
|
{/* <InputField /> */}
|
||||||
<PasswordField />
|
<PasswordField />
|
||||||
<Button color="indigo" size="large">
|
<Button color="indigo" size="large">
|
||||||
Submit
|
Submit
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Button from "@material-ui/core/Button";
|
||||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper";
|
||||||
import Grid from "@material-ui/core/Grid";
|
import Grid from "@material-ui/core/Grid";
|
||||||
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
|
import BugReportOutlinedIcon from "@material-ui/icons/BugReportOutlined";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
||||||
import { useAuth0 } from "../authentication/auth0";
|
import { useAuth0 } from "../authentication/auth0";
|
||||||
|
|
@ -58,7 +58,7 @@ export default function SignInSide() {
|
||||||
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
|
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<Avatar className={classes.avatar}>
|
<Avatar className={classes.avatar}>
|
||||||
<LockOutlinedIcon />
|
<BugReportOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h3">
|
<Typography component="h1" variant="h3">
|
||||||
BugBuster
|
BugBuster
|
||||||
|
|
|
||||||
5
client/src/constants/api.ts
Normal file
5
client/src/constants/api.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const API: string = "/api/v1";
|
||||||
|
|
||||||
|
export const PROJECTS: string = API + "/projects";
|
||||||
|
export const TICKETS: string = API + "/tickets";
|
||||||
|
export const USERS: string = API + "/users";
|
||||||
|
|
@ -3,5 +3,6 @@ export const PROJECTS: string = "/projects";
|
||||||
export const TICKETS: string = "/tickets";
|
export const TICKETS: string = "/tickets";
|
||||||
export const USERS: string = "/users";
|
export const USERS: string = "/users";
|
||||||
export const SIGN_IN: string = "/signin";
|
export const SIGN_IN: string = "/signin";
|
||||||
|
export const ACCOUNT: string = "/account";
|
||||||
export const NOT_FOUND: string = "/404";
|
export const NOT_FOUND: string = "/404";
|
||||||
export const TEST: string = "/test";
|
export const TEST: string = "/test";
|
||||||
|
|
|
||||||
40
client/src/controllers/AccountController.tsx
Normal file
40
client/src/controllers/AccountController.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React, { FC, useState, useEffect, useRef } from "react";
|
||||||
|
import AccountPage from "../pages/AccountPage";
|
||||||
|
import Preloader from "../components/Preloader";
|
||||||
|
import User from "../types/User";
|
||||||
|
import { UserService } from "../services";
|
||||||
|
import { useAuth0 } from "../authentication/auth0";
|
||||||
|
import { getUID } from "../authentication/helpers";
|
||||||
|
|
||||||
|
const AccountController: FC = () => {
|
||||||
|
const { getTokenSilently, user } = useAuth0();
|
||||||
|
const [account, setAccount] = useState<User>();
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const token = useRef<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getUserInfo = async () => {
|
||||||
|
// fetch current user data
|
||||||
|
token.current = await getTokenSilently();
|
||||||
|
const Users = new UserService(token.current);
|
||||||
|
const uid: string = getUID(user);
|
||||||
|
|
||||||
|
Users.get(uid)
|
||||||
|
.then((authUser) => setAccount(authUser))
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserInfo().then(() => setLoading(false));
|
||||||
|
}, [getTokenSilently, user]);
|
||||||
|
|
||||||
|
return loading ? (
|
||||||
|
// display preloader until data is fetched
|
||||||
|
<Preloader />
|
||||||
|
) : // don't render page until data is fetched
|
||||||
|
!!account ? (
|
||||||
|
<AccountPage account={account} token={token.current} />
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountController;
|
||||||
|
|
@ -73,7 +73,7 @@ const ProjectController: FC = () => {
|
||||||
setHasError(true);
|
setHasError(true);
|
||||||
setError("Bad Request");
|
setError("Bad Request");
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id, getTokenSilently]);
|
||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
return <ErrorController error={error} />;
|
return <ErrorController error={error} />;
|
||||||
|
|
|
||||||
117
client/src/pages/AccountPage.tsx
Normal file
117
client/src/pages/AccountPage.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import React, { FC, useState } from "react";
|
||||||
|
import { Button, Snackbar } from "@material-ui/core";
|
||||||
|
import { makeStyles, createStyles } from "@material-ui/core/styles";
|
||||||
|
import Alert from "@material-ui/lab/Alert";
|
||||||
|
import User from "../types/User";
|
||||||
|
import InputField from "../components/InputField";
|
||||||
|
import PageLayout from "../layouts/PageLayout";
|
||||||
|
import UserHeader from "../components/UserHeader";
|
||||||
|
import { UserService } from "../services";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) =>
|
||||||
|
createStyles({
|
||||||
|
input: {
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
account: User;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountPage: FC<IProps> = ({ account, token }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const [firstName, setFirstname] = useState(account.firstName);
|
||||||
|
const [lastName, setLastname] = useState(account.lastName);
|
||||||
|
const [presentation, setPresentation] = useState(account.presentation);
|
||||||
|
const [phone, setPhone] = useState(account.phone);
|
||||||
|
|
||||||
|
// user should at least have a name
|
||||||
|
const isDisabled = firstName === "" && lastName === "";
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
// prevent default button behaviour
|
||||||
|
e.preventDefault();
|
||||||
|
// send data to API
|
||||||
|
const Users = new UserService(token);
|
||||||
|
const newUser: User = {
|
||||||
|
id: account.id,
|
||||||
|
firstName: firstName,
|
||||||
|
lastName: lastName,
|
||||||
|
fullName: `${firstName} ${lastName}`,
|
||||||
|
email: account.email,
|
||||||
|
presentation: presentation,
|
||||||
|
picture: account.picture,
|
||||||
|
phone,
|
||||||
|
creationDate: Date.now().toLocaleString(),
|
||||||
|
activities: account.activities,
|
||||||
|
projects: account.projects,
|
||||||
|
tickets: account.tickets,
|
||||||
|
};
|
||||||
|
Users.update(account.id, newUser)
|
||||||
|
.then(() => console.log("ok"))
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
// reinitialize inputfiled
|
||||||
|
// setText("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout
|
||||||
|
header={
|
||||||
|
<UserHeader
|
||||||
|
picture={account.picture}
|
||||||
|
fullName={account.fullName}
|
||||||
|
presentation={account.presentation}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<InputField
|
||||||
|
label="First Name"
|
||||||
|
state={firstName}
|
||||||
|
setState={setFirstname}
|
||||||
|
className={classes.input}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="Last Name"
|
||||||
|
state={lastName}
|
||||||
|
setState={setLastname}
|
||||||
|
className={classes.input}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="Presentation"
|
||||||
|
state={presentation}
|
||||||
|
setState={setPresentation}
|
||||||
|
multiline
|
||||||
|
className={classes.input}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="Phone"
|
||||||
|
state={phone}
|
||||||
|
setState={setPhone}
|
||||||
|
className={classes.input}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
Update Info
|
||||||
|
</Button>
|
||||||
|
<Snackbar open={isDisabled} autoHideDuration={6000}>
|
||||||
|
<Alert severity="warning">
|
||||||
|
User should have at least a first or last name!
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountPage;
|
||||||
|
|
@ -5,9 +5,9 @@ import HomeController from "../controllers/HomeController";
|
||||||
import ProjectController from "../controllers/ProjectController";
|
import ProjectController from "../controllers/ProjectController";
|
||||||
import UserController from "../controllers/UserController";
|
import UserController from "../controllers/UserController";
|
||||||
import TicketController from "../controllers/TicketController";
|
import TicketController from "../controllers/TicketController";
|
||||||
|
import AccountController from "../controllers/AccountController";
|
||||||
import NotFoundPage from "../pages/NotFoundPage";
|
import NotFoundPage from "../pages/NotFoundPage";
|
||||||
import TestPage from "../pages/TestPage";
|
import TestPage from "../pages/TestPage";
|
||||||
// import SigninPage from "../pages/SigninPage";
|
|
||||||
import * as ROUTES from "../constants/routes";
|
import * as ROUTES from "../constants/routes";
|
||||||
|
|
||||||
const AppRouter = () => {
|
const AppRouter = () => {
|
||||||
|
|
@ -15,7 +15,7 @@ const AppRouter = () => {
|
||||||
<Switch>
|
<Switch>
|
||||||
<PrivateRoute path={ROUTES.TEST} component={TestPage} />
|
<PrivateRoute path={ROUTES.TEST} component={TestPage} />
|
||||||
<Route exact path={ROUTES.HOME} component={HomeController} />
|
<Route exact path={ROUTES.HOME} component={HomeController} />
|
||||||
{/* <Route path={ROUTES.SIGN_IN} component={SigninPage} /> */}
|
<PrivateRoute path={ROUTES.ACCOUNT} component={AccountController} />
|
||||||
<PrivateRoute
|
<PrivateRoute
|
||||||
path={`${ROUTES.PROJECTS}/:id`}
|
path={`${ROUTES.PROJECTS}/:id`}
|
||||||
component={ProjectController}
|
component={ProjectController}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import IService from ".";
|
import IService from ".";
|
||||||
import Project from "../types/Project";
|
import Project from "../types/Project";
|
||||||
import HttpHandler from "./http";
|
import HttpHandler from "./http";
|
||||||
|
import * as API from "../constants/api";
|
||||||
|
|
||||||
interface NewProject {
|
interface NewProject {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -12,7 +13,7 @@ export default class ProjectService implements IService<Project> {
|
||||||
constructor(private key: string) {}
|
constructor(private key: string) {}
|
||||||
|
|
||||||
private http = new HttpHandler<Project>();
|
private http = new HttpHandler<Project>();
|
||||||
private path: string = "/api/v1/projects";
|
private path: string = API.PROJECTS;
|
||||||
|
|
||||||
all = async (): Promise<Project[]> => {
|
all = async (): Promise<Project[]> => {
|
||||||
const response = await this.http.get(this.path, this.key);
|
const response = await this.http.get(this.path, this.key);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import IService from ".";
|
import IService from ".";
|
||||||
import Ticket from "../types/Ticket";
|
import Ticket from "../types/Ticket";
|
||||||
import HttpHandler from "./http";
|
import HttpHandler from "./http";
|
||||||
|
import * as API from "../constants/api";
|
||||||
|
|
||||||
interface NewTicket {
|
interface NewTicket {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -17,7 +18,7 @@ export default class TicketService implements IService<Ticket> {
|
||||||
constructor(private key: string) {}
|
constructor(private key: string) {}
|
||||||
|
|
||||||
private http = new HttpHandler<Ticket>();
|
private http = new HttpHandler<Ticket>();
|
||||||
private path: string = "/api/v1/tickets";
|
private path: string = API.TICKETS;
|
||||||
|
|
||||||
all = async (): Promise<Ticket[]> => {
|
all = async (): Promise<Ticket[]> => {
|
||||||
const response = await this.http.get(this.path, this.key);
|
const response = await this.http.get(this.path, this.key);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import IService from ".";
|
import IService from ".";
|
||||||
import User from "../types/User";
|
import User from "../types/User";
|
||||||
import HttpHandler from "./http";
|
import HttpHandler from "./http";
|
||||||
|
import * as API from "../constants/api";
|
||||||
|
|
||||||
export default class UserService implements IService<User> {
|
export default class UserService implements IService<User> {
|
||||||
constructor(private key: string) {}
|
constructor(private key: string) {}
|
||||||
|
|
||||||
private http = new HttpHandler<User>();
|
private http = new HttpHandler<User>();
|
||||||
private path: string = "/api/v1/users";
|
private path: string = API.USERS;
|
||||||
|
|
||||||
all = async (): Promise<User[]> => {
|
all = async (): Promise<User[]> => {
|
||||||
const response = await this.http.get(this.path, this.key);
|
const response = await this.http.get(this.path, this.key);
|
||||||
|
|
@ -26,7 +27,10 @@ export default class UserService implements IService<User> {
|
||||||
};
|
};
|
||||||
|
|
||||||
update = async (id: string, item: User): Promise<void> => {
|
update = async (id: string, item: User): Promise<void> => {
|
||||||
throw new Error("Method not implemented.");
|
// const response =
|
||||||
|
await this.http.put(`${this.path}/${id}`, item, this.key);
|
||||||
|
// const body = response.parsedBody;
|
||||||
|
// return body ?? ({} as User);
|
||||||
};
|
};
|
||||||
|
|
||||||
delete = async (id: string): Promise<void> => {
|
delete = async (id: string): Promise<void> => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export default class Constants {
|
|
||||||
static projectsURI: string = "/api/v1/projects";
|
|
||||||
static ticketsURI: string = "/api/v1/tickets";
|
|
||||||
static usersURI: string = "/api/v1/users";
|
|
||||||
}
|
|
||||||
11350
client/yarn.lock
Normal file
11350
client/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue