pull signin

This commit is contained in:
Ruidy Nemausat 2020-05-05 17:20:05 +02:00
commit 1f76be84e8
99 changed files with 999 additions and 792 deletions

View file

@ -13,7 +13,7 @@ using TicketManager.Resources;
namespace TicketManager.Controllers namespace TicketManager.Controllers
{ {
// [Authorize] [Authorize]
[Produces("application/json")] [Produces("application/json")]
[Route("api/v1/users")] [Route("api/v1/users")]
[ApiController] [ApiController]
@ -64,7 +64,7 @@ namespace TicketManager.Controllers
[HttpGet("{id}")] [HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<AppUserDTO>> GetUser(Guid id) public async Task<ActionResult<AppUserDTO>> GetUser(string id)
{ {
var user = await _context.AppUsers var user = await _context.AppUsers
.Include(u => u.Assignments) .Include(u => u.Assignments)
@ -103,7 +103,7 @@ namespace TicketManager.Controllers
[HttpPut("{id}")] [HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> PutUser(Guid id, AppUser user) public async Task<IActionResult> PutUser(string id, AppUser user)
{ {
if (id != user.Id) if (id != user.Id)
{ {
@ -159,6 +159,7 @@ namespace TicketManager.Controllers
var user = new AppUser() var user = new AppUser()
{ {
Id = userDto.Id,
FirstName = userDto.FirstName, FirstName = userDto.FirstName,
LastName = userDto.LastName, LastName = userDto.LastName,
Presentation = userDto.Presentation, Presentation = userDto.Presentation,
@ -202,7 +203,7 @@ namespace TicketManager.Controllers
} }
[HttpGet("{id}/projects")] [HttpGet("{id}/projects")]
public async Task<ActionResult<IEnumerable<ProjectDTORead>>> GetAppUserProjects(Guid id) public async Task<ActionResult<IEnumerable<ProjectDTORequest>>> GetAppUserProjects(string id)
{ {
var user = await _context.AppUsers var user = await _context.AppUsers
.Include(u => u.Assignments) .Include(u => u.Assignments)
@ -218,7 +219,7 @@ namespace TicketManager.Controllers
} }
[HttpGet("{id}/tickets/")] [HttpGet("{id}/tickets/")]
public async Task<ActionResult<IEnumerable<TicketDTORead>>> GetAppUserTickets(Guid id) public async Task<ActionResult<IEnumerable<TicketDTORead>>> GetAppUserTickets(string id)
{ {
var user = await _context.AppUsers var user = await _context.AppUsers
.Include(u => u.Assignments) .Include(u => u.Assignments)
@ -233,7 +234,7 @@ namespace TicketManager.Controllers
return user.GetTickets().Select(t => new TicketDTORead(t)).ToList(); return user.GetTickets().Select(t => new TicketDTORead(t)).ToList();
} }
private bool UserExists(Guid id) private bool UserExists(string id)
{ {
return _context.AppUsers.Any(e => e.Id == id); return _context.AppUsers.Any(e => e.Id == id);
} }

View file

@ -13,7 +13,7 @@ using System;
namespace TicketManager.Controllers namespace TicketManager.Controllers
{ {
// [Authorize(Roles = "Admin")] // [Authorize(Roles = "Admin")]
// [Authorize] [Authorize]
[Produces("application/json")] [Produces("application/json")]
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
[ApiController] [ApiController]
@ -180,11 +180,15 @@ namespace TicketManager.Controllers
EndingDate = projectDto.EndingDate, EndingDate = projectDto.EndingDate,
Manager = await _context.AppUsers.FindAsync(projectDto.ManagerId) Manager = await _context.AppUsers.FindAsync(projectDto.ManagerId)
}; };
// project.LogAction(
// $"{project.Title} has been created by {project.Manager.FullName}.",
// ActivityType.StartTask);
_context.Projects.Add(project); _context.Projects.Add(project);
_context.Assignments.Add(new Assignment()
{
Project = project,
ProjectId = project.Id,
User = project.Manager,
UserId = project.Manager.Id
});
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
var dto = new ProjectDTO(project); var dto = new ProjectDTO(project);
return CreatedAtAction("GetProject", new { id = project.Id }, dto); return CreatedAtAction("GetProject", new { id = project.Id }, dto);
@ -271,7 +275,7 @@ namespace TicketManager.Controllers
[HttpPatch("{id}/members")] [HttpPatch("{id}/members")]
public async Task<ActionResult<Project>> SetProjectMembers( public async Task<ActionResult<Project>> SetProjectMembers(
[FromRoute] int id, [FromRoute] int id,
[FromBody] Guid[] membersId) [FromBody] string[] membersId)
{ {
Project project = await _context.Projects Project project = await _context.Projects
.Include(p => p.Assignments) .Include(p => p.Assignments)

View file

@ -10,7 +10,7 @@ using TicketManager.Models;
namespace TicketManager.Controllers namespace TicketManager.Controllers
{ {
// [Authorize] [Authorize]
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
[ApiController] [ApiController]
public class TicketsController : ControllerBase public class TicketsController : ControllerBase
@ -105,6 +105,9 @@ namespace TicketManager.Controllers
Description = ticketDto.Description, Description = ticketDto.Description,
EndingDate = ticketDto.EndingDate, EndingDate = ticketDto.EndingDate,
CreatorId = ticketDto.CreatorId, CreatorId = ticketDto.CreatorId,
Category = (Category)ticketDto.Category,
Impact = (Impact)ticketDto.Impact,
Difficulty = (Difficulty)ticketDto.Difficulty,
Project = await _context.Projects.FindAsync(ticketDto.ProjectId) Project = await _context.Projects.FindAsync(ticketDto.ProjectId)
}; };

View file

@ -8,7 +8,7 @@ namespace TicketManager.Models
{ {
public class AppUser public class AppUser
{ {
public Guid Id { get; set; } public string Id { get; set; }
[Required] [Required]
[StringLength(50)] [StringLength(50)]

View file

@ -5,7 +5,7 @@ namespace TicketManager.Models
public class Assignment public class Assignment
{ {
public AppUser User { get; set; } public AppUser User { get; set; }
public Guid UserId { get; set; } public string UserId { get; set; }
public Project Project { get; set; } public Project Project { get; set; }
public int ProjectId { get; set; } public int ProjectId { get; set; }
} }

View file

@ -28,7 +28,7 @@ namespace TicketManager.Models
public Impact Impact { get; set; } = Impact.Undefined; public Impact Impact { get; set; } = Impact.Undefined;
public Difficulty Difficulty { get; set; } = Difficulty.Undefined; public Difficulty Difficulty { get; set; } = Difficulty.Undefined;
public Category Category { get; set; } = Category.Undefined; public Category Category { get; set; } = Category.Undefined;
public Guid CreatorId { get; set; } public string CreatorId { get; set; }
[Display(Name = "Project")] [Display(Name = "Project")]
public Project Project { get; set; } public Project Project { get; set; }

View file

@ -60,3 +60,6 @@
- [ ] Query progression info in UserPage - [ ] Query progression info in UserPage
- [x] Add info fields in New Ticket Form - [x] Add info fields in New Ticket Form
- [ ] Filter users in Users Modal - [ ] Filter users in Users Modal
- [ ] EditForms for Project and Ticket
- [ ] Admin Page
- [ ] Use auth0 user info to create appUser account

View file

@ -23,7 +23,7 @@ namespace TicketManager.Resources
Tickets = user.GetTickets().Select(u => new TicketDTORead(u)).ToList(); Tickets = user.GetTickets().Select(u => new TicketDTORead(u)).ToList();
} }
public Guid Id { get; set; } public string Id { get; set; }
public string FirstName { get; set; } public string FirstName { get; set; }

View file

@ -20,7 +20,7 @@ namespace TicketManager.Resources
Picture = user.Picture; Picture = user.Picture;
} }
public Guid Id { get; set; } public string Id { get; set; }
public string FirstName { get; set; } public string FirstName { get; set; }

View file

@ -4,6 +4,8 @@ namespace TicketManager.Resources
{ {
public class NewAppUserDTO public class NewAppUserDTO
{ {
public string Id { get; set; }
[Required] [Required]
public string FirstName { get; set; } public string FirstName { get; set; }
public string LastName { get; set; } public string LastName { get; set; }

View file

@ -11,6 +11,6 @@ namespace TicketManager.Resources
[Required] [Required]
public DateTime EndingDate { get; set; } public DateTime EndingDate { get; set; }
[Required] [Required]
public Guid ManagerId { get; set; } public string ManagerId { get; set; }
} }
} }

View file

@ -16,13 +16,13 @@ namespace TicketManager.Resources
[DataType(DataType.Date)] [DataType(DataType.Date)]
public DateTime EndingDate { get; set; } public DateTime EndingDate { get; set; }
public string Impact { get; set; } public int Impact { get; set; }
public string Difficulty { get; set; } public int Difficulty { get; set; }
public string Category { get; set; } public int Category { get; set; }
[Required] [Required]
public Guid CreatorId { get; set; } public string CreatorId { get; set; }
[Required] [Required]
public int ProjectId { get; set; } public int ProjectId { get; set; }
} }

View file

@ -47,7 +47,7 @@ namespace TicketManager.Resources
public string Category { get; set; } public string Category { get; set; }
public Guid CreatorId { get; set; } public string CreatorId { get; set; }
public ProjectDTORead Project { get; set; } public ProjectDTORead Project { get; set; }

View file

@ -44,7 +44,7 @@ namespace TicketManager.Resources
public string Category { get; set; } public string Category { get; set; }
public Guid CreatorId { get; set; } public string CreatorId { get; set; }
public List<Note> Notes { get; set; } = new List<Note>(); public List<Note> Notes { get; set; } = new List<Note>();
public List<File> Files { get; set; } = new List<File>(); public List<File> Files { get; set; } = new List<File>();

BIN
app.db

Binary file not shown.

View file

@ -1288,22 +1288,35 @@
} }
}, },
"@material-ui/core": { "@material-ui/core": {
"version": "4.9.8", "version": "4.9.13",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.8.tgz", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.13.tgz",
"integrity": "sha512-4cslpG6oLoPWUfwPkX+hvbak4hAGiOfgXOu/UIYeeMrtsTEebC0Mirjoby7zhS4ny86YI3rXEFW6EZDmlj5n5w==", "integrity": "sha512-GEXNwUr+laZ0N+F1efmHB64Fyg+uQIRXLqbSejg3ebSXgLYNpIjnMOPRfWdu4rICq0dAIgvvNXGkKDMcf3AMpA==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@material-ui/styles": "^4.9.6", "@material-ui/react-transition-group": "^4.3.0",
"@material-ui/system": "^4.9.6", "@material-ui/styles": "^4.9.13",
"@material-ui/types": "^5.0.0", "@material-ui/system": "^4.9.13",
"@material-ui/utils": "^4.9.6", "@material-ui/types": "^5.0.1",
"@material-ui/utils": "^4.9.12",
"@types/react-transition-group": "^4.2.0", "@types/react-transition-group": "^4.2.0",
"clsx": "^1.0.2", "clsx": "^1.0.4",
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"popper.js": "^1.14.1", "popper.js": "^1.16.1-lts",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-is": "^16.8.0", "react-is": "^16.8.0",
"react-transition-group": "^4.3.0" "react-transition-group": "^4.3.0"
},
"dependencies": {
"@material-ui/utils": {
"version": "4.9.12",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.9.12.tgz",
"integrity": "sha512-/0rgZPEOcZq5CFA4+4n6Q6zk7fi8skHhH2Bcra8R3epoJEYy5PL55LuMazPtPH1oKeRausDV/Omz4BbgFsn1HQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0"
}
}
} }
}, },
"@material-ui/icons": { "@material-ui/icons": {
@ -1326,16 +1339,27 @@
"react-is": "^16.8.0" "react-is": "^16.8.0"
} }
}, },
"@material-ui/react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@material-ui/react-transition-group/-/react-transition-group-4.3.0.tgz",
"integrity": "sha512-CwQ0aXrlUynUTY6sh3UvKuvye1o92en20VGAs6TORnSxUYeRmkX8YeTUN3lAkGiBX1z222FxLFO36WWh6q73rQ==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
},
"@material-ui/styles": { "@material-ui/styles": {
"version": "4.9.6", "version": "4.9.13",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.6.tgz", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.13.tgz",
"integrity": "sha512-ijgwStEkw1OZ6gCz18hkjycpr/3lKs1hYPi88O/AUn4vMuuGEGAIrqKVFq/lADmZUNF3DOFIk8LDkp7zmjPxtA==", "integrity": "sha512-lWlXJanBdHQ18jW/yphedRokHcvZD1GdGzUF/wQxKDsHwDDfO45ZkAxuSBI202dG+r1Ph483Z3pFykO2obeSRA==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.8.0", "@emotion/hash": "^0.8.0",
"@material-ui/types": "^5.0.0", "@material-ui/types": "^5.0.1",
"@material-ui/utils": "^4.9.6", "@material-ui/utils": "^4.9.6",
"clsx": "^1.0.2", "clsx": "^1.0.4",
"csstype": "^2.5.2", "csstype": "^2.5.2",
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"jss": "^10.0.3", "jss": "^10.0.3",
@ -1350,9 +1374,9 @@
} }
}, },
"@material-ui/system": { "@material-ui/system": {
"version": "4.9.6", "version": "4.9.13",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.6.tgz", "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.13.tgz",
"integrity": "sha512-QtfoAePyqXoZ2HUVSwGb1Ro0kucMCvVjbI0CdYIR21t0Opgfm1Oer6ni9P5lfeXA39xSt0wCierw37j+YES48Q==", "integrity": "sha512-6AlpvdW6KJJ5bF1Xo2OD13sCN8k+nlL36412/bWnWZOKIfIMo/Lb8c8d1DOIaT/RKWxTEUaWnKZjabVnA3eZjA==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.9.6", "@material-ui/utils": "^4.9.6",
@ -1360,9 +1384,9 @@
} }
}, },
"@material-ui/types": { "@material-ui/types": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz", "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.1.tgz",
"integrity": "sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg==" "integrity": "sha512-wURPSY7/3+MAtng3i26g+WKwwNE3HEeqa/trDBR5+zWKmcjO+u9t7Npu/J1r+3dmIa/OeziN9D/18IrBKvKffw=="
}, },
"@material-ui/utils": { "@material-ui/utils": {
"version": "4.9.6", "version": "4.9.6",
@ -3779,11 +3803,11 @@
"integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=" "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY="
}, },
"css-vendor": { "css-vendor": {
"version": "2.0.7", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
"integrity": "sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==", "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
"requires": { "requires": {
"@babel/runtime": "^7.6.2", "@babel/runtime": "^7.8.3",
"is-in-browser": "^1.0.2" "is-in-browser": "^1.0.2"
} }
}, },
@ -4223,9 +4247,9 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": { "@babel/runtime": {
"version": "7.9.2", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==",
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
@ -12926,9 +12950,10 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
}, },
"typescript": { "typescript": {
"version": "3.7.5", "version": "3.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==" "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
"dev": true
}, },
"underscore": { "underscore": {
"version": "1.9.2", "version": "1.9.2",

View file

@ -4,7 +4,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@auth0/auth0-spa-js": "^1.6.4", "@auth0/auth0-spa-js": "^1.6.4",
"@material-ui/core": "^4.9.8", "@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.47",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
@ -24,7 +24,7 @@
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.3.1", "react-scripts": "3.3.1",
"react-swipeable-views": "^0.13.9", "react-swipeable-views": "^0.13.9",
"typescript": "^3.7.5", "typescript": "^3.8.3",
"underscore": "^1.9.2" "underscore": "^1.9.2"
}, },
"scripts": { "scripts": {
@ -48,4 +48,4 @@
"last 1 safari version" "last 1 safari version"
] ]
} }
} }

View file

@ -11,12 +11,6 @@
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- <link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> -->
<link <link
rel="stylesheet" rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"

View file

@ -1,6 +1,6 @@
{ {
"short_name": "React App", "short_name": "BugBuster",
"name": "Create React App Sample", "name": "BugBuster | Project Management",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon.ico",
@ -22,4 +22,4 @@
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#ffffff" "background_color": "#ffffff"
} }

View file

@ -1,9 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View file

@ -1,21 +1,17 @@
import React from "react"; import React from "react";
import { Router } from "react-router-dom"; import { Router } from "react-router-dom";
import { useAuth0 } from "./authentication/auth0"; import { useAuth0 } from "./authentication/auth0";
import * as createHistory from "history";
// import history from "./utils/history";
import MainLayout from "./layouts/MainLayout"; import MainLayout from "./layouts/MainLayout";
import { AppRouter } from "./utils/router"; import AppRouter from "./routes/AppRouter";
import history from "./utils/history";
export const history = createHistory.createBrowserHistory(); import Preloader from "./components/Preloader";
export default function App() { export default function App() {
const { loading } = useAuth0(); const { loading } = useAuth0();
if (loading) { return loading ? (
return <div>Loading...</div>; <Preloader />
} ) : (
return (
<Router history={history}> <Router history={history}>
<MainLayout> <MainLayout>
<AppRouter /> <AppRouter />

View file

@ -1,9 +1,9 @@
import { Ticket } from "../types/Ticket"; import Activity from "../types/Activity";
import { Project } from "../types/Project"; import AppFile from "../types/AppFile";
import { AppFile } from "../types/AppFile"; import Project from "../types/Project";
import { Activity } from "../types/Activity"; import Ticket from "../types/Ticket";
import { User } from "../types/User"; import User from "../types/User";
import { getRemainingdays } from "../utils/methods"; import getRemainingdays from "../utils/methods";
export default class ProjectVM { export default class ProjectVM {
public id: number; public id: number;
@ -47,7 +47,7 @@ export default class ProjectVM {
this.ticketsDone = this.ticketsDone =
this.tickets === undefined this.tickets === undefined
? 0 ? 0
: this.tickets.filter(t => t.status === "Done").length; : this.tickets.filter((t) => t.status === "Done").length;
this.remainingDays = getRemainingdays(project.endingDate); this.remainingDays = getRemainingdays(project.endingDate);
this.allProjects = allProjects; this.allProjects = allProjects;
} }

View file

@ -1,8 +1,8 @@
import { Ticket } from "../types/Ticket"; import Project from "../types/Project";
import { Project } from "../types/Project"; import Ticket from "../types/Ticket";
import { User } from "../types/User"; import User from "../types/User";
export class TicketVM { export default class TicketVM {
public id: number; public id: number;
public title: string; public title: string;
public description: string; public description: string;

View file

@ -1,7 +1,7 @@
import { Project } from "../types/Project"; import Activity from "../types/Activity";
import { Ticket } from "../types/Ticket"; import Project from "../types/Project";
import { User } from "../types/User"; import Ticket from "../types/Ticket";
import { Activity } from "../types/Activity"; import User from "../types/User";
export class UserVM { export class UserVM {
public id: string; public id: string;
@ -16,9 +16,8 @@ export class UserVM {
public projects: Project[]; public projects: Project[];
public tickets: Ticket[]; public tickets: Ticket[];
public activities: Activity[]; public activities: Activity[];
public allUsers: User[];
public constructor(user: User, allUsers: User[]) { public constructor(user: User) {
this.id = user.id; this.id = user.id;
this.firstName = user.firstName; this.firstName = user.firstName;
this.lastName = user.lastName; this.lastName = user.lastName;
@ -31,6 +30,5 @@ export class UserVM {
this.projects = user.projects; this.projects = user.projects;
this.tickets = user.tickets; this.tickets = user.tickets;
this.activities = user.activities; this.activities = user.activities;
this.allUsers = allUsers;
} }
} }

View file

@ -1,4 +1,3 @@
// src/react-auth0-spa.js
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "@auth0/auth0-spa-js"; import createAuth0Client from "@auth0/auth0-spa-js";
@ -68,6 +67,7 @@ export const Auth0Provider = ({
setIsAuthenticated(true); setIsAuthenticated(true);
setUser(user); setUser(user);
}; };
return ( return (
<Auth0Context.Provider <Auth0Context.Provider
value={{ value={{
@ -81,7 +81,7 @@ export const Auth0Provider = ({
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p), loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p), getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p), getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p) logout: (...p) => auth0Client.logout(...p),
}} }}
> >
{children} {children}

View file

@ -0,0 +1,9 @@
/**
* retrieve userId
* @param user Auth0 user object
*/
export const getUID = (user: any) => {
const { sub } = user;
const uid = sub.split("|")[1];
return uid;
};

View file

@ -1,12 +1,12 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Activity } from "../types/Activity"; import Activity from "../types/Activity";
type IProps = { type IProps = {
activities: Activity[]; activities: Activity[];
filterText: string; filterText: string;
}; };
export const ActivityCollection: FC<IProps> = ({ activities, filterText }) => { const ActivityCollection: FC<IProps> = ({ activities, filterText }) => {
return activities === undefined ? ( return activities === undefined ? (
<></> <></>
) : ( ) : (
@ -17,7 +17,7 @@ export const ActivityCollection: FC<IProps> = ({ activities, filterText }) => {
) : ( ) : (
activities activities
.filter( .filter(
a => (a) =>
a.description a.description
.toLowerCase() .toLowerCase()
.includes(filterText.toLowerCase()) || .includes(filterText.toLowerCase()) ||
@ -67,3 +67,4 @@ export const ActivityEntry: FC<IFProps> = ({ activity }) => {
</> </>
); );
}; };
export default ActivityCollection;

View file

@ -1,9 +1,8 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import Avatar from "@material-ui/core/Avatar"; import { makeStyles, Theme, createStyles, Avatar } from "@material-ui/core";
import AvatarGroup from "@material-ui/lab/AvatarGroup"; import AvatarGroup from "@material-ui/lab/AvatarGroup";
import { User } from "../../types/User"; import User from "../../types/User";
import { makeStyles, Theme, createStyles } from "@material-ui/core";
interface AvatarListProps { interface AvatarListProps {
users: User[]; users: User[];
@ -34,3 +33,5 @@ export const AvatarList: FC<AvatarListProps> = ({ users }) => {
</div> </div>
); );
}; };
export default AvatarList;

View file

@ -12,17 +12,17 @@ const useStyles = makeStyles((theme: Theme) =>
root: { root: {
display: "flex", display: "flex",
"& > *": { "& > *": {
margin: theme.spacing(1) margin: theme.spacing(1),
} },
}, },
small: { small: {
width: theme.spacing(3), width: theme.spacing(3),
height: theme.spacing(3) height: theme.spacing(3),
}, },
large: { large: {
width: theme.spacing(10), width: theme.spacing(10),
height: theme.spacing(10) height: theme.spacing(10),
} },
}) })
); );
@ -35,3 +35,5 @@ export const UserAvatar: FC<IProps> = ({ picture, alt }) => {
</div> </div>
); );
}; };
export default UserAvatar;

View file

@ -1,30 +1,36 @@
import React from "react"; import React from "react";
import { Link } from "react-router-dom";
import {
AppBar,
Button,
IconButton,
Toolbar,
Typography,
Avatar,
} from "@material-ui/core";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import MenuIcon from "@material-ui/icons/Menu"; import MenuIcon from "@material-ui/icons/Menu";
import * as ROUTES from "../constants/routes";
import { useAuth0 } from "../authentication/auth0"; import { useAuth0 } from "../authentication/auth0";
import { getUID } from "../authentication/helpers";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
root: { root: {
flexGrow: 1 flexGrow: 1,
}, },
menuButton: { menuButton: {
marginRight: theme.spacing(2) marginRight: theme.spacing(2),
}, },
title: { title: {
flexGrow: 1 flexGrow: 1,
} },
}) })
); );
export default function ButtonAppBar() { export default function ButtonAppBar() {
const classes = useStyles(); const classes = useStyles();
const { isAuthenticated, loginWithRedirect, logout } = useAuth0(); const { isAuthenticated, loginWithRedirect, logout, user } = useAuth0();
return ( return (
<div className={classes.root}> <div className={classes.root}>
@ -39,18 +45,31 @@ export default function ButtonAppBar() {
<MenuIcon /> <MenuIcon />
</IconButton> </IconButton>
<Typography variant="h6" className={classes.title}> <Typography variant="h6" className={classes.title}>
<Button color="inherit" href="/"> <Button color="inherit" component={Link} to={ROUTES.HOME}>
BugBuster BugBuster
</Button> </Button>
</Typography> </Typography>
{!isAuthenticated ? ( {!isAuthenticated ? (
<Button color="inherit" onClick={() => loginWithRedirect({})}> <Button
color="secondary"
variant="contained"
onClick={() => loginWithRedirect({})}
>
Log in Log in
</Button> </Button>
) : ( ) : (
<Button color="inherit" onClick={() => logout()}> <>
Log out <Button
</Button> color="inherit"
component={Link}
to={`${ROUTES.USERS}/${getUID(user)}`}
>
<Avatar src={user.picture} />
</Button>
<Button color="inherit" onClick={() => logout()}>
Log out
</Button>
</>
)} )}
</Toolbar> </Toolbar>
</AppBar> </AppBar>

View file

@ -9,12 +9,12 @@ interface IProps {
onClick?: (e: MouseEvent) => void; onClick?: (e: MouseEvent) => void;
} }
export const Button: FC<IProps> = ({ const Button: FC<IProps> = ({
size = "small", size = "small",
shape = "", shape = "",
color, color,
onClick, onClick,
children children,
}) => { }) => {
return ( return (
<button <button
@ -25,3 +25,5 @@ export const Button: FC<IProps> = ({
</button> </button>
); );
}; };
export default Button;

View file

@ -10,13 +10,7 @@ interface IProps {
text?: string; text?: string;
} }
export const FloatingButton: FC<IProps> = ({ const FloatingButton: FC<IProps> = ({ color, icon, size, text, onClick }) => {
color,
icon,
size,
text,
onClick
}) => {
return ( return (
<Fab color={color} aria-label={icon} size={size} onClick={onClick}> <Fab color={color} aria-label={icon} size={size} onClick={onClick}>
<AddIcon /> <AddIcon />
@ -24,3 +18,5 @@ export const FloatingButton: FC<IProps> = ({
</Fab> </Fab>
); );
}; };
export default FloatingButton;

View file

@ -1,11 +1,8 @@
import React, { FC, ReactNode } from "react"; import React, { FC, ReactNode } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Card from "@material-ui/core/Card"; import { Card, CardActions, CardContent, Typography } from "@material-ui/core";
import CardActions from "@material-ui/core/CardActions"; import ProgressBar from "../Progress/ProgressBar";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import { ProgressBar } from "../Progress/ProgressBar";
interface IProps { interface IProps {
title?: string; title?: string;
@ -22,7 +19,7 @@ const useStyles = makeStyles({
}, },
}); });
export const HorizontalCard: FC<IProps> = ({ const HorizontalCard: FC<IProps> = ({
title, title,
link = "#", link = "#",
content, content,
@ -46,3 +43,5 @@ export const HorizontalCard: FC<IProps> = ({
</Card> </Card>
); );
}; };
export default HorizontalCard;

View file

@ -1,10 +1,10 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { HorizontalCard } from "./HorizontalCard";
import { makeStyles, Theme, createStyles } from "@material-ui/core"; import { makeStyles, Theme, createStyles } from "@material-ui/core";
import { AvatarList } from "../Avatars/AvatarList"; import HorizontalCard from "./HorizontalCard";
import { ProgressInfo } from "../Progress/ProgressInfo"; import AvatarList from "../Avatars/AvatarList";
import { User } from "../../types/User"; import ProgressInfo from "../Progress/ProgressInfo";
import { getRemainingdays } from "../../utils/methods"; import User from "../../types/User";
import getRemainingdays from "../../utils/methods";
interface IProps { interface IProps {
title?: string; title?: string;

View file

@ -1,9 +1,9 @@
import React, { FC, MouseEvent } from "react"; import React, { FC, MouseEvent } from "react";
import { Button, Typography, Grid } from "@material-ui/core"; import { Button, Typography, Grid } from "@material-ui/core";
import { HorizontalCard } from "./HorizontalCard"; import HorizontalCard from "./HorizontalCard";
import TicketChipsArray from "./TicketChipsArray"; import TicketChipsArray from "./TicketChipsArray";
import { Ticket } from "../../types/Ticket"; import Ticket from "../../types/Ticket";
import { getRemainingdays } from "../../utils/methods"; import getRemainingdays from "../../utils/methods";
interface IProps { interface IProps {
ticket?: Ticket; ticket?: Ticket;

View file

@ -1,12 +1,14 @@
import React, { FC } from "react"; import React, { FC } from "react";
import {
Avatar,
ListItemAvatar,
List,
ListItemText,
ListItem,
} from "@material-ui/core";
import { createStyles, Theme, makeStyles } from "@material-ui/core/styles"; import { createStyles, Theme, makeStyles } from "@material-ui/core/styles";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
import Avatar from "@material-ui/core/Avatar";
import WorkIcon from "@material-ui/icons/Work"; import WorkIcon from "@material-ui/icons/Work";
import { AppFile } from "../types/AppFile"; import AppFile from "../types/AppFile";
type IProps = { type IProps = {
files: AppFile[]; files: AppFile[];
@ -18,12 +20,12 @@ const useStyles = makeStyles((theme: Theme) =>
root: { root: {
width: "100%", width: "100%",
maxWidth: 360, maxWidth: 360,
backgroundColor: theme.palette.background.paper backgroundColor: theme.palette.background.paper,
} },
}) })
); );
export const FileCollection: FC<IProps> = ({ files, filterText }) => { const FileCollection: FC<IProps> = ({ files, filterText }) => {
const classes = useStyles(); const classes = useStyles();
return ( return (
<List className={classes.root}> <List className={classes.root}>
@ -32,7 +34,7 @@ export const FileCollection: FC<IProps> = ({ files, filterText }) => {
) : ( ) : (
files files
.filter( .filter(
f => (f) =>
f.name.toLowerCase().includes(filterText.toLowerCase()) || f.name.toLowerCase().includes(filterText.toLowerCase()) ||
f.format.toLowerCase().includes(filterText.toLowerCase()) f.format.toLowerCase().includes(filterText.toLowerCase())
) )
@ -61,3 +63,4 @@ export const FileEntry: FC<IFProps> = ({ file }) => {
</ListItem> </ListItem>
); );
}; };
export default FileCollection;

View file

@ -1,9 +1,7 @@
import React, { FC, ChangeEvent, MouseEvent } from "react"; import React, { FC, ChangeEvent, MouseEvent } from "react";
import { useRouteMatch } from "react-router-dom"; import { useRouteMatch } from "react-router-dom";
import { Grid, TextField } from "@material-ui/core";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import { Grid } from "@material-ui/core";
// import { AccountCircle, FilterList, FilterListSharp } from "@material-ui/icons";
type IProps = { type IProps = {
filterText: string; filterText: string;
@ -28,7 +26,7 @@ const useStyles = makeStyles((theme: Theme) =>
}) })
); );
export const FilterBar: FC<IProps> = ({ const FilterBar: FC<IProps> = ({
filterText, filterText,
handleChange, handleChange,
// clearFilterText // clearFilterText
@ -55,3 +53,4 @@ export const FilterBar: FC<IProps> = ({
</div> </div>
); );
}; };
export default FilterBar;

View file

@ -1,8 +1,7 @@
import React, { FC } from "react"; import React, { FC } from "react";
import Typography from "@material-ui/core/Typography"; import { Link as RouterLink } from "react-router-dom";
import { Container, Typography, Link } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";
import Link from "@material-ui/core/Link";
interface IProps { interface IProps {
brand: string; brand: string;
@ -11,14 +10,14 @@ interface IProps {
const copyParams: IProps = { const copyParams: IProps = {
brand: "BugBuster", brand: "BugBuster",
text: "Made with 🔥" text: "Made with 🔥",
}; };
const Copyright: FC<IProps> = ({ brand, text }) => { const Copyright: FC<IProps> = ({ brand, text }) => {
return ( return (
<Typography variant="body2" color="textSecondary"> <Typography variant="body2" color="textSecondary">
{"© "} {"© "}
<Link color="inherit" href="/"> <Link color="inherit" component={RouterLink} to="/">
{brand} {brand}
</Link>{" "} </Link>{" "}
{new Date().getFullYear()} {new Date().getFullYear()}
@ -27,15 +26,15 @@ const Copyright: FC<IProps> = ({ brand, text }) => {
); );
}; };
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles((theme) => ({
footer: { footer: {
padding: theme.spacing(3, 2), padding: theme.spacing(3, 2),
marginTop: "auto", marginTop: "auto",
backgroundColor: backgroundColor:
theme.palette.type === "light" theme.palette.type === "light"
? theme.palette.grey[200] ? theme.palette.grey[200]
: theme.palette.grey[800] : theme.palette.grey[800],
} },
})); }));
export default function Footer() { export default function Footer() {

View file

@ -6,7 +6,7 @@ type HeaderProps = {
description: string; description: string;
}; };
export const Header: FC<HeaderProps> = ({ title, description }) => { const Header: FC<HeaderProps> = ({ title, description }) => {
return ( return (
<Box> <Box>
<Typography variant="h2" component="h2"> <Typography variant="h2" component="h2">
@ -18,3 +18,5 @@ export const Header: FC<HeaderProps> = ({ title, description }) => {
</Box> </Box>
); );
}; };
export default Header;

View file

@ -1,6 +1,6 @@
import React, { FC } from "react"; import React, { FC } from "react";
export const InputField: FC = () => { const InputField: FC = () => {
return ( return (
<div className="input-field"> <div className="input-field">
<input id="email" type="text" className="validate" /> <input id="email" type="text" className="validate" />
@ -8,3 +8,5 @@ export const InputField: FC = () => {
</div> </div>
); );
}; };
export default InputField;

View file

@ -3,22 +3,20 @@ import { CloudUpload } from "@material-ui/icons";
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles"; import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
export const InputFile: FC = () => { const InputFile: FC = () => {
return ( return (
<> <form action="/upload">
<form action="/upload"> <div className="file-field input-field">
<div className="file-field input-field"> <UploadButton>
<UploadButton> <CloudUpload />
<CloudUpload /> <input
<input type="file"
type="file" multiple
multiple accept=".doc,.docx,.pdf,.md,.gdoc,.zip,image/*"
accept=".doc,.docx,.pdf,.md,.gdoc,.zip,image/*" />
/> </UploadButton>
</UploadButton> </div>
</div> </form>
</form>
</>
); );
}; };
@ -26,12 +24,12 @@ const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
root: { root: {
"& > *": { "& > *": {
margin: theme.spacing(1) margin: theme.spacing(1),
} },
}, },
input: { input: {
display: "none" display: "none",
} },
}) })
); );
@ -60,3 +58,5 @@ const UploadButton: FC = () => {
</div> </div>
); );
}; };
export default InputFile;

View file

@ -1,13 +1,13 @@
import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
import { ActivityCollection } from "../ActivityCollection"; import ActivityCollection from "../ActivityCollection";
import { Activity } from "../../types/Activity"; import FilterBar from "../FilterBar";
import { FilterBar } from "../FilterBar"; import Activity from "../../types/Activity";
type IProps = { type IProps = {
activities: Activity[]; activities: Activity[];
}; };
export const ActivityList: FC<IProps> = ({ activities }) => { const ActivityList: FC<IProps> = ({ activities }) => {
const [filterText, setFilterText] = useState<string>(""); const [filterText, setFilterText] = useState<string>("");
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
setFilterText(""); setFilterText("");
@ -30,3 +30,5 @@ export const ActivityList: FC<IProps> = ({ activities }) => {
</> </>
); );
}; };
export default ActivityList;

View file

@ -1,15 +1,15 @@
import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
import { AppFile } from "../../types/AppFile";
import { FileCollection } from "../FileCollection";
import { InputFile } from "../InputFile";
import { FilterBar } from "../FilterBar";
import { Grid, Typography } from "@material-ui/core"; import { Grid, Typography } from "@material-ui/core";
import FileCollection from "../FileCollection";
import FilterBar from "../FilterBar";
import InputFile from "../InputFile";
import AppFile from "../../types/AppFile";
type IProps = { type IProps = {
files: AppFile[]; files: AppFile[];
}; };
export const FileList: FC<IProps> = ({ files }) => { const FileList: FC<IProps> = ({ files }) => {
const [filterText, setFilterText] = useState<string>(""); const [filterText, setFilterText] = useState<string>("");
const clearFilterText = (e: MouseEvent): void => { const clearFilterText = (e: MouseEvent): void => {
setFilterText(""); setFilterText("");
@ -38,3 +38,5 @@ export const FileList: FC<IProps> = ({ files }) => {
</> </>
); );
}; };
export default FileList;

View file

@ -1,13 +1,13 @@
import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
import { UsersModalEntry } from "../Modals/UsersModalEntry"; import FilterBar from "../FilterBar";
import { FilterBar } from "../FilterBar"; import UsersModalEntry from "../Modals/UsersModalEntry";
import { User } from "../../types/User"; import User from "../../types/User";
interface IProps { interface IProps {
users: User[]; users: User[];
} }
export const MemberList: FC<IProps> = ({ users }) => { const MemberList: FC<IProps> = ({ users }) => {
const [members, setMembers] = useState<User[]>([]); const [members, setMembers] = useState<User[]>([]);
const [filterText, setFilterText] = useState<string>(""); const [filterText, setFilterText] = useState<string>("");
const clearFilterText = (e: MouseEvent): void => { const clearFilterText = (e: MouseEvent): void => {
@ -37,3 +37,5 @@ export const MemberList: FC<IProps> = ({ users }) => {
</> </>
); );
}; };
export default MemberList;

View file

@ -6,12 +6,11 @@ import {
createStyles, createStyles,
Theme, Theme,
} from "@material-ui/core"; } from "@material-ui/core";
import { FilterBar } from "../FilterBar"; import FilterBar from "../FilterBar";
import ProjectCard from "../Cards/ProjectCard"; import ProjectCard from "../Cards/ProjectCard";
import { FloatingButton } from "../Buttons/FloatingButton"; import FloatingButton from "../Buttons/FloatingButton";
import { NewProjectModal } from "../Modals/NewProjectModal"; import NewProjectModal from "../Modals/NewProjectModal";
import { Project } from "../../types/Project"; import Project from "../../types/Project";
import { User } from "../../types/User";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -27,10 +26,9 @@ const useStyles = makeStyles((theme: Theme) =>
type IProps = { type IProps = {
projects: Project[]; projects: Project[];
allUsers: User[];
}; };
export const ProjectList: FC<IProps> = ({ projects, allUsers }) => { const ProjectList: FC<IProps> = ({ projects }) => {
const [filterText, setFilterText] = useState<string>(""); const [filterText, setFilterText] = useState<string>("");
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
setFilterText(""); setFilterText("");
@ -59,7 +57,6 @@ export const ProjectList: FC<IProps> = ({ projects, allUsers }) => {
setShowNew(false); setShowNew(false);
}} }}
show={showNew} show={showNew}
allUsers={allUsers}
/> />
<Grid container> <Grid container>
<Grid <Grid
@ -109,3 +106,5 @@ export const ProjectList: FC<IProps> = ({ projects, allUsers }) => {
</> </>
); );
}; };
export default ProjectList;

View file

@ -6,15 +6,14 @@ import {
Theme, Theme,
createStyles, createStyles,
} from "@material-ui/core"; } from "@material-ui/core";
import { FloatingButton } from "../Buttons/FloatingButton"; import FloatingButton from "../Buttons/FloatingButton";
import { FilterBar } from "../FilterBar"; import FilterBar from "../FilterBar";
import { HttpResponse } from "../../types/HttpResponse";
import { Ticket } from "../../types/Ticket";
import { NewTicketModal } from "../Modals/NewTicketModal";
import { Project } from "../../types/Project";
import { put } from "../../utils/http";
import { Constants } from "../../utils/Constants";
import TicketCard from "../Cards/TicketCard"; import TicketCard from "../Cards/TicketCard";
import NewTicketModal from "../Modals/NewTicketModal";
import Ticket from "../../types/Ticket";
import Project from "../../types/Project";
import { useAuth0 } from "../../authentication/auth0";
import { TicketService } from "../../services";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -34,7 +33,7 @@ type TicketListProps = {
addButton?: Boolean; addButton?: Boolean;
}; };
export const TicketList: FC<TicketListProps> = ({ const TicketList: FC<TicketListProps> = ({
tickets, tickets,
allProjects, allProjects,
addButton = true, addButton = true,
@ -59,6 +58,14 @@ export const TicketList: FC<TicketListProps> = ({
t.title.toLowerCase().includes(filterText.toLowerCase()) t.title.toLowerCase().includes(filterText.toLowerCase())
); );
const { getTokenSilently } = useAuth0();
const handleValidate = async (id: number) => {
const token = await getTokenSilently();
const Tickets = new TicketService(token);
await Tickets.close(id.toString());
};
const classes = useStyles(); const classes = useStyles();
return ( return (
@ -99,28 +106,24 @@ export const TicketList: FC<TicketListProps> = ({
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<div className="col s12 grey lighten-1"> {filteredTickets.length === 0 ? (
{filteredTickets.length === 0 ? ( <TicketCard />
<TicketCard /> ) : (
) : ( filteredTickets.map((t: Ticket) => (
filteredTickets.map((t: Ticket) => ( <TicketCard
<TicketCard key={t.id}
key={t.id} ticket={t}
ticket={t} link={`/tickets/${t.id}`}
link={`/tickets/${t.id}`} validateTicket={() => {
validateTicket={async (e: MouseEvent) => { handleValidate(t.id);
e.preventDefault(); }}
await put<HttpResponse<Ticket>>( />
`${Constants.ticketsURI}/${t.id}/closed`, ))
{} )}
);
}}
/>
))
)}
</div>
</Grid> </Grid>
</Grid> </Grid>
</> </>
); );
}; };
export default TicketList;

View file

@ -1,7 +1,7 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { InputField } from "./InputField"; import InputField from "./InputField";
import { PasswordField } from "./PasswordField"; import PasswordField from "./PasswordField";
import { Button } from "./Buttons/Button"; import Button from "./Buttons/Button";
export const LogInForm: FC = () => { export const LogInForm: FC = () => {
return ( return (

View file

@ -1,6 +1,6 @@
import React, { FC } from "react"; import React, { FC } from "react";
import Dialog from "@material-ui/core/Dialog";
import { import {
Dialog,
DialogTitle, DialogTitle,
Typography, Typography,
IconButton, IconButton,
@ -36,7 +36,7 @@ const useStyles = makeStyles((theme: Theme) =>
}) })
); );
export const Modal: FC<IProps> = ({ const Modal: FC<IProps> = ({
handleClose, handleClose,
show, show,
action, action,
@ -77,3 +77,5 @@ export const Modal: FC<IProps> = ({
</Dialog> </Dialog>
); );
}; };
export default Modal;

View file

@ -1,36 +1,48 @@
import React, { FC, useState, FormEvent } from "react"; import React, { FC, useState, FormEvent } from "react";
import { TextField } from "@material-ui/core"; import { TextField } from "@material-ui/core";
import { Modal } from "./Modal"; import Modal from "./Modal";
import { Project } from "../../types/Project"; import Preloader from "../Preloader";
import { User } from "../../types/User"; import { useAuth0 } from "../../authentication/auth0";
import { post } from "../../utils/http"; import { ProjectService } from "../../services";
import { Constants } from "../../utils/Constants"; import { getUID } from "../../authentication/helpers";
import { today } from "../../utils/methods";
interface IProps { interface IProps {
show: boolean; show: boolean;
handleClose: () => void; handleClose: () => void;
allUsers: User[];
} }
export const NewProjectModal: FC<IProps> = ({ show, handleClose }) => { const NewProjectModal: FC<IProps> = ({ show, handleClose }) => {
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [endingDate, setEndingDate] = useState(""); const [endingDate, setEndingDate] = useState(today());
const [loading, setLoading] = useState(false);
const { getTokenSilently, user } = useAuth0();
const handleSubmit = async (e: FormEvent) => { const handleSubmit = async (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
setLoading(true);
let newProject = { let newProject = {
title: title, title: title,
description: description, description: description,
endingDate: new Date(endingDate).toISOString(), endingDate: new Date(endingDate).toISOString(),
managerId: "cd179eb7-3a54-4060-b22c-3e947bdffcbc", // get current User id managerId: getUID(user),
}; };
await post<Project>(`${Constants.projectsURI}`, newProject); const token = await getTokenSilently();
const Projects = new ProjectService(token);
Projects.add(newProject).catch((err) => console.error(err));
setLoading(false);
setTitle("");
setDescription("");
setEndingDate(today());
handleClose(); handleClose();
}; };
return ( return loading ? (
<Preloader />
) : (
<Modal <Modal
name="New Project" name="New Project"
show={show} show={show}
@ -88,3 +100,5 @@ export const NewProjectModal: FC<IProps> = ({ show, handleClose }) => {
</Modal> </Modal>
); );
}; };
export default NewProjectModal;

View file

@ -7,14 +7,16 @@ import {
makeStyles, makeStyles,
Theme, Theme,
} from "@material-ui/core"; } from "@material-ui/core";
import { Modal } from "./Modal"; import Modal from "./Modal";
import { Ticket } from "../../types/Ticket"; import Project from "../../types/Project";
import { Project } from "../../types/Project";
import { post } from "../../utils/http";
import { Constants } from "../../utils/Constants";
import Category from "../../types/enums/category"; import Category from "../../types/enums/category";
import Impact from "../../types/enums/impact"; import Impact from "../../types/enums/impact";
import Difficulty from "../../types/enums/difficulty"; import Difficulty from "../../types/enums/difficulty";
import { TicketService } from "../../services";
import { useAuth0 } from "../../authentication/auth0";
import { getUID } from "../../authentication/helpers";
import { today } from "../../utils/methods";
import Preloader from "../Preloader";
interface IProps { interface IProps {
show: boolean; show: boolean;
@ -28,42 +30,50 @@ const useStyles = makeStyles((theme: Theme) => ({
}, },
})); }));
export const NewTicketModal: FC<IProps> = ({ const NewTicketModal: FC<IProps> = ({ show, handleClose, allProjects }) => {
show, const { getTokenSilently, user } = useAuth0();
handleClose,
allProjects,
}) => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [endingDate, setEndingDate] = useState("");
const { url } = useRouteMatch(); const { url } = useRouteMatch();
const id = url.split("/")[2]; const id = url.split("/")[2];
const [projectId, setProjectId] = useState(id); const [projectId, setProjectId] = useState(id);
const [categoryID, setCategoryID] = useState(0); const [title, setTitle] = useState("");
const [impactID, setImpactID] = useState(0); const [description, setDescription] = useState("");
const [difficultyID, setDifficultyID] = useState(0); const [endingDate, setEndingDate] = useState(today());
const [categoryID, setCategoryID] = useState(1);
const [impactID, setImpactID] = useState(1);
const [difficultyID, setDifficultyID] = useState(1);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: FormEvent) => { const handleSubmit = async (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
setLoading(true);
let newTicket = { let newTicket = {
title: title, title: title,
description: description, description: description,
endingDate: new Date(endingDate).toISOString(), endingDate: new Date(endingDate).toISOString(),
creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94", // get current User id creatorId: getUID(user),
projectId: parseInt(projectId), projectId: parseInt(projectId),
impact: impactID, impact: impactID,
difficulty: difficultyID, difficulty: difficultyID,
category: categoryID, category: categoryID,
}; };
// const response: HttpResponse<Ticket> = const token = await getTokenSilently();
await post<Ticket>(`${Constants.ticketsURI}`, newTicket); const Tickets = new TicketService(token);
Tickets.add(newTicket).catch((err) => console.error(err));
setLoading(false);
setTitle("");
setDescription("");
setEndingDate(today());
setCategoryID(1);
setImpactID(1);
setDifficultyID(1);
handleClose(); handleClose();
}; };
const classes = useStyles(); const classes = useStyles();
return ( return loading ? (
<Preloader />
) : (
<Modal <Modal
name="New Ticket" name="New Ticket"
show={show} show={show}
@ -160,7 +170,7 @@ export const NewTicketModal: FC<IProps> = ({
className={classes.select} className={classes.select}
> >
{Category.map((c: string, i: number) => ( {Category.map((c: string, i: number) => (
<MenuItem key={i} value={i}> <MenuItem key={i} value={i + 1}>
{c} {c}
</MenuItem> </MenuItem>
))} ))}
@ -181,7 +191,7 @@ export const NewTicketModal: FC<IProps> = ({
margin="normal" margin="normal"
> >
{Impact.map((c: string, i: number) => ( {Impact.map((c: string, i: number) => (
<MenuItem key={i} value={i}> <MenuItem key={i} value={i + 1}>
{c} {c}
</MenuItem> </MenuItem>
))} ))}
@ -202,7 +212,7 @@ export const NewTicketModal: FC<IProps> = ({
margin="normal" margin="normal"
> >
{Difficulty.map((c: string, i: number) => ( {Difficulty.map((c: string, i: number) => (
<MenuItem key={i} value={i}> <MenuItem key={i} value={i + 1}>
{c} {c}
</MenuItem> </MenuItem>
))} ))}
@ -211,3 +221,5 @@ export const NewTicketModal: FC<IProps> = ({
</Modal> </Modal>
); );
}; };
export default NewTicketModal;

View file

@ -1,20 +1,22 @@
import React, { FC, useState, ChangeEvent, FormEvent } from "react"; import React, { FC, useState, ChangeEvent, FormEvent } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { Grid } from "@material-ui/core"; import {
Avatar,
Checkbox,
Grid,
List,
ListItem,
ListItemSecondaryAction,
ListItemText,
ListItemAvatar,
} from "@material-ui/core";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import List from "@material-ui/core/List"; import AvatarList from "../Avatars/AvatarList";
import ListItem from "@material-ui/core/ListItem"; import FilterBar from "../FilterBar";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction"; import Modal from "./Modal";
import ListItemText from "@material-ui/core/ListItemText"; import User from "../../types/User";
import ListItemAvatar from "@material-ui/core/ListItemAvatar"; import { ProjectService } from "../../services";
import Checkbox from "@material-ui/core/Checkbox"; import { useAuth0 } from "../../authentication/auth0";
import Avatar from "@material-ui/core/Avatar";
import { Modal } from "./Modal";
import { AvatarList } from "../Avatars/AvatarList";
import { FilterBar } from "../FilterBar";
import { User } from "../../types/User";
import { patch } from "../../utils/http";
import { Constants } from "../../utils/Constants";
interface IProps { interface IProps {
show: boolean; show: boolean;
@ -33,12 +35,7 @@ const useStyles = makeStyles((theme: Theme) =>
}) })
); );
export const UsersModal: FC<IProps> = ({ const UsersModal: FC<IProps> = ({ show, handleClose, users, allUsers }) => {
show,
handleClose,
users,
allUsers,
}) => {
const { id } = useParams(); const { id } = useParams();
const [filterText, setFilterText] = useState<string>(""); const [filterText, setFilterText] = useState<string>("");
@ -62,12 +59,14 @@ export const UsersModal: FC<IProps> = ({
setMembers(newChecked); setMembers(newChecked);
}; };
const { getTokenSilently } = useAuth0();
const handleSubmit = async (e: FormEvent) => { const handleSubmit = async (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
await patch<User[]>( if (id !== undefined) {
`${Constants.projectsURI}/${id}/members`, const token = await getTokenSilently();
members //.map((m) => m.id) const Projects = new ProjectService(token);
); await Projects.setMembers(id, members);
}
handleClose(); handleClose();
}; };
@ -112,3 +111,5 @@ export const UsersModal: FC<IProps> = ({
</Modal> </Modal>
); );
}; };
export default UsersModal;

View file

@ -1,5 +1,5 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { User } from "../../types/User"; import User from "../../types/User";
interface IProps { interface IProps {
setMembers: React.Dispatch<React.SetStateAction<User[]>>; setMembers: React.Dispatch<React.SetStateAction<User[]>>;
@ -7,7 +7,7 @@ interface IProps {
user: User; user: User;
} }
export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => { const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
const match: (id: string) => boolean = (id: string) => { const match: (id: string) => boolean = (id: string) => {
return Boolean(members.find((m) => m.id === id)); return Boolean(members.find((m) => m.id === id));
}; };
@ -40,3 +40,5 @@ export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
</div> </div>
); );
}; };
export default UsersModalEntry;

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useAuth0 } from "../authentication/auth0"; import { useAuth0 } from "../authentication/auth0";
export const NavBar = () => { const NavBar = () => {
const { isAuthenticated, loginWithRedirect, logout } = useAuth0(); const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
return ( return (
@ -14,3 +14,5 @@ export const NavBar = () => {
</div> </div>
); );
}; };
export default NavBar;

View file

@ -1,67 +0,0 @@
import React, { FC } from "react";
import { TextField, MenuItem } from "@material-ui/core";
import { Project } from "../types/Project";
interface IProps {
title: string;
setTitle: React.Dispatch<React.SetStateAction<string>>;
description: string;
setDescription: React.Dispatch<React.SetStateAction<string>>;
endingDate: string;
setEndingDate: React.Dispatch<React.SetStateAction<string>>;
allProjects: Project[];
projectId: string;
setProjectId: React.Dispatch<React.SetStateAction<string>>;
}
export const NewTicketForm: FC<IProps> = ({
title,
setTitle,
description,
setDescription,
endingDate,
setEndingDate,
allProjects,
projectId,
setProjectId,
}) => {
return (
<>
{/* <div className="row">
<div className="input-field">
<i className="material-icons prefix">date_range</i>
<input
id="Due Date"
type="text"
className="datepicker"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEndingDate(e.target.value)
}
/>
<label htmlFor="Due Date">Due Date</label>
</div>
<div className="input-field">
<select
id="project"
className="browser-default"
value={projectId}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
e.preventDefault();
setProjectId(e.target.value);
}}
>
<option value={0} disabled>
Project
</option>
{allProjects.map((p) => (
<option key={p.id} value={p.id}>
{p.title}
</option>
))}
</select>
</div>
</div> */}
</>
);
};

View file

@ -1,16 +1,11 @@
import React, { FC, useState, ReactNode } from "react"; import React, { FC, useState, ReactNode } from "react";
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box";
import SwipeableViews from "react-swipeable-views"; import SwipeableViews from "react-swipeable-views";
import { Ticket } from "../../types/Ticket"; import { makeStyles, Theme, useTheme } from "@material-ui/core/styles";
import { Project } from "../../types/Project"; import { AppBar, Box, Tab, Tabs, Typography } from "@material-ui/core";
import { TicketList } from "../Lists/TicketList"; import Ticket from "../../types/Ticket";
// import { FileList } from "./AppFileList"; import Project from "../../types/Project";
import { AppFile } from "../../types/AppFile"; import TicketList from "../Lists/TicketList";
import AppFile from "../../types/AppFile";
interface TabProps { interface TabProps {
children?: ReactNode; children?: ReactNode;
@ -59,7 +54,7 @@ interface IProps {
allProjects: Project[]; allProjects: Project[];
} }
export const ProjectTabPanel: FC<IProps> = ({ const ProjectTabPanel: FC<IProps> = ({
tickets, tickets,
tabNames, tabNames,
files, files,
@ -116,3 +111,5 @@ export const ProjectTabPanel: FC<IProps> = ({
</div> </div>
); );
}; };
export default ProjectTabPanel;

View file

@ -1,12 +1,12 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Route, useRouteMatch, Redirect } from "react-router-dom"; import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { TabRouterHeader } from "./TabRouterHeader"; import TabRouterHeader from "./TabRouterHeader";
import { TicketList } from "../Lists/TicketList"; import TicketList from "../Lists/TicketList";
import { FileList } from "../Lists/AppFileList"; import FileList from "../Lists/AppFileList";
import { Ticket } from "../../types/Ticket"; import Ticket from "../../types/Ticket";
import { AppFile } from "../../types/AppFile"; import AppFile from "../../types/AppFile";
import { Activity } from "../../types/Activity"; import Activity from "../../types/Activity";
import { Project } from "../../types/Project"; import Project from "../../types/Project";
interface IProps { interface IProps {
tickets: Ticket[]; tickets: Ticket[];
@ -17,7 +17,7 @@ interface IProps {
allProjects: Project[]; allProjects: Project[];
} }
export const TabRouter: FC<IProps> = ({ const TabRouter: FC<IProps> = ({
tickets, tickets,
tabNames, tabNames,
files, files,
@ -48,3 +48,5 @@ export const TabRouter: FC<IProps> = ({
</> </>
); );
}; };
export default TabRouter;

View file

@ -6,9 +6,9 @@ interface IProps {
tabNames: string[]; tabNames: string[];
} }
export const TabRouterHeader: FC<IProps> = ({ const TabRouterHeader: FC<IProps> = ({
tabNames, tabNames,
tabClass = `tab col s${12 / tabNames.length}` tabClass = `tab col s${12 / tabNames.length}`,
}) => { }) => {
const [isActive, setIsActive] = useState(0); const [isActive, setIsActive] = useState(0);
const nTabs = tabNames.length; const nTabs = tabNames.length;
@ -31,7 +31,7 @@ export const TabRouterHeader: FC<IProps> = ({
className="indicator indigo lighten-2" className="indicator indigo lighten-2"
style={{ style={{
left: `${(isActive / nTabs) * 100}%`, left: `${(isActive / nTabs) * 100}%`,
right: `${(1 - (isActive + 1) / nTabs) * 100}%` right: `${(1 - (isActive + 1) / nTabs) * 100}%`,
}} }}
></li> ></li>
</ul> </ul>
@ -54,7 +54,7 @@ const TabUnit: FC<TabUnitProps> = ({
setIsActive, setIsActive,
text, text,
value, value,
nTabs nTabs,
}) => { }) => {
const { url } = useRouteMatch(); const { url } = useRouteMatch();
return ( return (
@ -63,7 +63,7 @@ const TabUnit: FC<TabUnitProps> = ({
key={value} key={value}
style={{ style={{
left: `${(isActive / nTabs) * 100}%`, left: `${(isActive / nTabs) * 100}%`,
right: `${(1 - (isActive + 1) / nTabs) * 100}%` right: `${(1 - (isActive + 1) / nTabs) * 100}%`,
}} }}
> >
<Link <Link
@ -81,3 +81,5 @@ const TabUnit: FC<TabUnitProps> = ({
</li> </li>
); );
}; };
export default TabRouterHeader;

View file

@ -1,16 +1,11 @@
import React, { FC, useState, ReactNode } from "react"; import React, { FC, useState, ReactNode } from "react";
import SwipeableViews from "react-swipeable-views"; import SwipeableViews from "react-swipeable-views";
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles"; import { makeStyles, Theme, useTheme } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar"; import { AppBar, Box, Tab, Tabs, Typography } from "@material-ui/core";
import Tabs from "@material-ui/core/Tabs"; import ProjectList from "../Lists/ProjectList";
import Tab from "@material-ui/core/Tab"; import TicketList from "../Lists/TicketList";
import Typography from "@material-ui/core/Typography"; import Ticket from "../../types/Ticket";
import Box from "@material-ui/core/Box"; import Project from "../../types/Project";
import { Ticket } from "../../types/Ticket";
import { Project } from "../../types/Project";
import { ProjectList } from "../Lists/ProjectList";
import { TicketList } from "../Lists/TicketList";
import { User } from "../../types/User";
interface TabProps { interface TabProps {
children?: ReactNode; children?: ReactNode;
@ -55,15 +50,9 @@ interface IProps {
tabNames: string[]; tabNames: string[];
tickets: Ticket[]; tickets: Ticket[];
projects: Project[]; projects: Project[];
allUsers: User[];
} }
export const UserTabPanel: FC<IProps> = ({ const UserTabPanel: FC<IProps> = ({ tickets, tabNames, projects }) => {
tickets,
tabNames,
projects,
allUsers,
}) => {
const classes = useStyles(); const classes = useStyles();
const theme = useTheme(); const theme = useTheme();
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
@ -98,7 +87,7 @@ export const UserTabPanel: FC<IProps> = ({
onChangeIndex={handleChangeIndex} onChangeIndex={handleChangeIndex}
> >
<TabPanel value={value} index={0} dir={theme.direction}> <TabPanel value={value} index={0} dir={theme.direction}>
<ProjectList projects={projects} allUsers={allUsers} /> <ProjectList projects={projects} />
</TabPanel> </TabPanel>
<TabPanel value={value} index={1} dir={theme.direction}> <TabPanel value={value} index={1} dir={theme.direction}>
<TicketList tickets={tickets} allProjects={[]} addButton={false} /> <TicketList tickets={tickets} allProjects={[]} addButton={false} />
@ -107,3 +96,4 @@ export const UserTabPanel: FC<IProps> = ({
</div> </div>
); );
}; };
export default UserTabPanel;

View file

@ -1,10 +1,9 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Route, useRouteMatch, Redirect } from "react-router-dom"; import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { TabRouterHeader } from "./TabRouterHeader"; import TabRouterHeader from "./TabRouterHeader";
import { ProjectList } from "../Lists/ProjectList"; import TicketList from "../Lists/TicketList";
import { Ticket } from "../../types/Ticket"; import Ticket from "../../types/Ticket";
import { Project } from "../../types/Project"; import Project from "../../types/Project";
import { TicketList } from "../Lists/TicketList";
interface IProps { interface IProps {
tabNames: string[]; tabNames: string[];
@ -33,3 +32,5 @@ export const UserTabRouter: FC<IProps> = ({ tickets, tabNames, projects }) => {
</> </>
); );
}; };
export default UserTabRouter;

View file

@ -1,6 +1,6 @@
import React, { FC } from "react"; import React, { FC } from "react";
export const PasswordField: FC = () => { const PasswordField: FC = () => {
return ( return (
<div className="input-field"> <div className="input-field">
<input id="password" type="password" className="validate" /> <input id="password" type="password" className="validate" />
@ -8,3 +8,5 @@ export const PasswordField: FC = () => {
</div> </div>
); );
}; };
export default PasswordField;

View file

@ -1,55 +1,24 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Backdrop, CircularProgress } from "@material-ui/core";
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: "#fff",
},
})
);
const Preloader: FC = () => {
const classes = useStyles();
export const Preloader: FC = () => {
return ( return (
<div className="preloader-wrapper big active"> <Backdrop className={classes.backdrop} open={true}>
<div className="spinner-layer spinner-blue"> <CircularProgress color="inherit" />
<div className="circle-clipper left"> </Backdrop>
<div className="circle"></div>
</div>
<div className="gap-patch">
<div className="circle"></div>
</div>
<div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
<div className="spinner-layer spinner-red">
<div className="circle-clipper left">
<div className="circle"></div>
</div>
<div className="gap-patch">
<div className="circle"></div>
</div>
<div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
<div className="spinner-layer spinner-yellow">
<div className="circle-clipper left">
<div className="circle"></div>
</div>
<div className="gap-patch">
<div className="circle"></div>
</div>
<div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
<div className="spinner-layer spinner-green">
<div className="circle-clipper left">
<div className="circle"></div>
</div>
<div className="gap-patch">
<div className="circle"></div>
</div>
<div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
</div>
); );
}; };
export default Preloader;

View file

@ -1,8 +1,8 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { UserAvatar } from "./Avatars/UserAvatar";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { UserAvatar } from "./Avatars/UserAvatar";
export const ProfileSelector: FC = () => { const ProfileSelector: FC = () => {
return ( return (
<div className="section col s10 offset-s1 white z-depth-1"> <div className="section col s10 offset-s1 white z-depth-1">
<div className="row "> <div className="row ">
@ -20,3 +20,4 @@ export const ProfileSelector: FC = () => {
</div> </div>
); );
}; };
export default ProfileSelector;

View file

@ -1,7 +1,6 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Box, LinearProgress } from "@material-ui/core";
import { makeStyles, Theme, createStyles } from "@material-ui/core/styles"; import { makeStyles, Theme, createStyles } from "@material-ui/core/styles";
import LinearProgress from "@material-ui/core/LinearProgress";
import { Box } from "@material-ui/core";
type IProps = { type IProps = {
value: number; value: number;
@ -18,7 +17,7 @@ const useStyles = makeStyles((theme: Theme) =>
}) })
); );
export const ProgressBar: FC<IProps> = ({ value }) => { const ProgressBar: FC<IProps> = ({ value }) => {
// const styleString: CSSProperties = { width: `${value}%` }; // const styleString: CSSProperties = { width: `${value}%` };
// let barColor: string = "green"; // let barColor: string = "green";
@ -43,3 +42,5 @@ export const ProgressBar: FC<IProps> = ({ value }) => {
</Box> </Box>
); );
}; };
export default ProgressBar;

View file

@ -20,7 +20,7 @@ type IProps = {
// }) // })
// ); // );
export const ProgressInfo: FC<IProps> = ({ const ProgressInfo: FC<IProps> = ({
tasksDone, tasksDone,
tasksTotalCount, tasksTotalCount,
remainingDays, remainingDays,
@ -39,3 +39,5 @@ export const ProgressInfo: FC<IProps> = ({
</Box> </Box>
); );
}; };
export default ProgressInfo;

View file

@ -2,20 +2,17 @@ import React from "react";
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline"; import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
import Link from "@material-ui/core/Link";
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 LockOutlinedIcon from "@material-ui/icons/LockOutlined";
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";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
root: { root: {
height: "100vh" height: "100vh",
}, },
image: { image: {
backgroundImage: "url(https://source.unsplash.com/daily?dev)", backgroundImage: "url(https://source.unsplash.com/daily?dev)",
@ -25,30 +22,31 @@ const useStyles = makeStyles((theme: Theme) =>
? theme.palette.grey[50] ? theme.palette.grey[50]
: theme.palette.grey[900], : theme.palette.grey[900],
backgroundSize: "cover", backgroundSize: "cover",
backgroundPosition: "center" backgroundPosition: "center",
}, },
paper: { paper: {
margin: theme.spacing(8, 4), margin: theme.spacing(8, 4),
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center" alignItems: "center",
}, },
avatar: { avatar: {
margin: theme.spacing(1), margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main backgroundColor: theme.palette.secondary.main,
}, },
form: { form: {
width: "100%", width: "100%",
marginTop: theme.spacing(1) marginTop: theme.spacing(1),
}, },
submit: { submit: {
margin: theme.spacing(3, 0, 2) margin: theme.spacing(3, 0, 2),
} },
}) })
); );
export default function SignInSide() { export default function SignInSide() {
const classes = useStyles(); const classes = useStyles();
const { loginWithRedirect } = useAuth0();
return ( return (
<Grid container component="main" className={classes.root}> <Grid container component="main" className={classes.root}>
@ -63,53 +61,16 @@ export default function SignInSide() {
Sign in Sign in
</Typography> </Typography>
<form className={classes.form} noValidate> <form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button <Button
type="submit" type="submit"
fullWidth fullWidth
variant="contained" variant="contained"
color="primary" color="primary"
className={classes.submit} className={classes.submit}
onClick={() => loginWithRedirect({})}
> >
Sign In Sign In
</Button> </Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form> </form>
</div> </div>
</Grid> </Grid>

View file

@ -1,6 +1,6 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Header } from "../components/Header"; import Header from "../components/Header";
import { UserAvatar } from "./Avatars/UserAvatar"; import UserAvatar from "./Avatars/UserAvatar";
import { import {
Grid, Grid,
// makeStyles, Theme // makeStyles, Theme
@ -19,7 +19,7 @@ interface IProps {
// }, // },
// })); // }));
export const UserHeader: FC<IProps> = ({ fullName, presentation, picture }) => { const UserHeader: FC<IProps> = ({ fullName, presentation, picture }) => {
// const classes = useStyles(); // const classes = useStyles();
return ( return (
// <div className={classes.root}> // <div className={classes.root}>
@ -34,3 +34,5 @@ export const UserHeader: FC<IProps> = ({ fullName, presentation, picture }) => {
// </div> // </div>
); );
}; };
export default UserHeader;

View file

@ -0,0 +1,7 @@
export const HOME: string = "/";
export const PROJECTS: string = "/projects";
export const TICKETS: string = "/tickets";
export const USERS: string = "/users";
export const SIGN_IN: string = "/signin";
export const NOT_FOUND: string = "/404";
export const TEST: string = "/test";

View file

@ -5,7 +5,7 @@ interface IProps {
error: string; error: string;
} }
export const ErrorController: FC<IProps> = ({ error }) => { const ErrorController: FC<IProps> = ({ error }) => {
switch (error) { switch (error) {
case "Bad Request": case "Bad Request":
return <Redirect to="/400" />; return <Redirect to="/400" />;
@ -20,3 +20,5 @@ export const ErrorController: FC<IProps> = ({ error }) => {
return <Redirect to="/404" />; return <Redirect to="/404" />;
} }
}; };
export default ErrorController;

View file

@ -1,6 +1,8 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { HomePage } from "../pages/HomePage"; import HomePage from "../pages/HomePage";
export const HomeController: FC = () => { const HomeController: FC = () => {
return <HomePage />; return <HomePage />;
}; };
export default HomeController;

View file

@ -1,16 +1,15 @@
import React, { FC, useState, useEffect } from "react"; import React, { FC, useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ErrorController } from "./ErrorController"; import ErrorController from "./ErrorController";
import { ProjectPage } from "../pages/ProjectPage"; import ProjectPage from "../pages/ProjectPage";
import ProjectVM from "../VM/ProjectVM"; import ProjectVM from "../VM/ProjectVM";
import { Project } from "../types/Project"; import Project from "../types/Project";
import { HttpResponse } from "../types/HttpResponse"; import User from "../types/User";
import { Preloader } from "../components/Preloader"; import Preloader from "../components/Preloader";
import { Constants } from "../utils/Constants"; import { ProjectService, UserService } from "../services";
import { get } from "../utils/http"; import { useAuth0 } from "../authentication/auth0";
import { User } from "../types/User";
export const ProjectController: FC = () => { const ProjectController: FC = () => {
const [project, setProject] = useState<Project>({} as Project); const [project, setProject] = useState<Project>({} as Project);
const [allUsers, setAllUsers] = useState<User[]>([]); const [allUsers, setAllUsers] = useState<User[]>([]);
const [allProjects, setAllProjects] = useState<Project[]>([]); const [allProjects, setAllProjects] = useState<Project[]>([]);
@ -18,61 +17,62 @@ export const ProjectController: FC = () => {
const [hasError, setHasError] = useState(false); const [hasError, setHasError] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const { id } = useParams(); const { id } = useParams();
const { getTokenSilently } = useAuth0();
async function httpGetProjects(id: string): Promise<void> {
try {
const response: HttpResponse<Project> = await get<Project>(
`${Constants.projectsURI}/${id}`
);
if (response.parsedBody !== undefined) {
setProject(response.parsedBody);
setIsLoading(false);
}
} catch (ex) {
console.error(ex);
setHasError(true);
setError(ex);
}
}
async function httpGetAllUsers(): Promise<void> {
try {
const response: HttpResponse<User> = await get<User>(
`${Constants.usersURI}`
);
if (response.parsedBody !== undefined) {
setAllUsers((response.parsedBody as unknown) as User[]);
}
} catch (ex) {
setHasError(true);
setError(ex);
}
}
async function httpGetAllProjects(): Promise<void> {
try {
const response: HttpResponse<Project> = await get<Project>(
`${Constants.projectsURI}`
);
if (response.parsedBody !== undefined) {
setAllProjects((response.parsedBody as unknown) as Project[]);
}
} catch (ex) {
setHasError(true);
setError(ex);
}
}
useEffect(() => { useEffect(() => {
const getProject = async (id: string): Promise<void> => {
const token = await getTokenSilently();
try {
const Projects = new ProjectService(token);
const project: Project = await Projects.get(id);
if (project !== undefined) {
setProject(project);
setIsLoading(false);
}
} catch (ex) {
setHasError(true);
setError(ex);
}
};
const getAllUsers = async (): Promise<void> => {
const token = await getTokenSilently();
try {
const Users = new UserService(token);
const response: User[] = await Users.all();
if (response !== undefined) {
setAllUsers(response);
setIsLoading(false);
}
} catch (ex) {
setHasError(true);
setError(ex);
}
};
const getAllProjects = async (): Promise<void> => {
const token = await getTokenSilently();
try {
const Projects = new ProjectService(token);
const response: Project[] = await Projects.all();
if (response !== undefined) {
setAllProjects(response);
}
} catch (ex) {
setHasError(true);
setError(ex);
}
};
if (id !== undefined) { if (id !== undefined) {
httpGetProjects(id); getProject(id);
httpGetAllUsers(); getAllUsers();
httpGetAllProjects(); getAllProjects();
} else { } else {
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} />;
@ -81,3 +81,5 @@ export const ProjectController: FC = () => {
const viewModel = new ProjectVM(project, allUsers, allProjects); const viewModel = new ProjectVM(project, allUsers, allProjects);
return isLoading ? <Preloader /> : <ProjectPage viewModel={viewModel} />; return isLoading ? <Preloader /> : <ProjectPage viewModel={viewModel} />;
}; };
export default ProjectController;

View file

@ -1,45 +1,44 @@
import React, { FC, useState, useEffect } from "react"; import React, { FC, useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { TicketPage } from "../pages/TicketPage"; import ErrorController from "./ErrorController";
import { ErrorController } from "./ErrorController"; import TicketPage from "../pages/TicketPage";
import { HttpResponse } from "../types/HttpResponse"; import TicketVM from "../VM/TicketVM";
import { Preloader } from "../components/Preloader"; import Ticket from "../types/Ticket";
import { get } from "../utils/http"; import Preloader from "../components/Preloader";
import { Constants } from "../utils/Constants"; import { useAuth0 } from "../authentication/auth0";
import { Ticket } from "../types/Ticket"; import { TicketService } from "../services";
import { TicketVM } from "../VM/TicketVM";
export const TicketController: FC = () => { const TicketController: FC = () => {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [ticket, setTicket] = useState<Ticket>({} as Ticket); const [ticket, setTicket] = useState<Ticket>({} as Ticket);
const [hasError, setHasError] = useState(false); const [hasError, setHasError] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const { id } = useParams(); const { id } = useParams();
const { getTokenSilently } = useAuth0();
async function httpGetTicket(id: string): Promise<void> {
try {
const response: HttpResponse<Ticket> = await get<Ticket>(
`${Constants.ticketsURI}/${id}`
);
if (response.parsedBody !== undefined) {
setTicket(response.parsedBody);
setIsLoading(false);
}
} catch (ex) {
console.error(ex);
setHasError(true);
setError(ex);
}
}
useEffect(() => { useEffect(() => {
const getTicket = async (id: string): Promise<void> => {
const token = await getTokenSilently();
try {
const Tickets = new TicketService(token);
const response: Ticket = await Tickets.get(id);
if (response !== undefined) {
setTicket(response);
setIsLoading(false);
}
} catch (ex) {
setHasError(true);
setError(ex);
}
};
if (id !== undefined) { if (id !== undefined) {
httpGetTicket(id); getTicket(id);
} else { } else {
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} />;
@ -48,3 +47,5 @@ export const TicketController: FC = () => {
const viewModel = new TicketVM(ticket); const viewModel = new TicketVM(ticket);
return isLoading ? <Preloader /> : <TicketPage viewModel={viewModel} />; return isLoading ? <Preloader /> : <TicketPage viewModel={viewModel} />;
}; };
export default TicketController;

View file

@ -1,66 +1,75 @@
import React, { FC, useState, useEffect } from "react"; import React, { FC, useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { UserPage } from "../pages/UserPage"; import ErrorController from "./ErrorController";
import UserPage from "../pages/UserPage";
import { UserVM } from "../VM/UserVM"; import { UserVM } from "../VM/UserVM";
import { User } from "../types/User"; import User from "../types/User";
import { HttpResponse } from "../types/HttpResponse"; import Preloader from "../components/Preloader";
import { Preloader } from "../components/Preloader"; import { UserService } from "../services";
import { get } from "../utils/http"; import { useAuth0 } from "../authentication/auth0";
import { Constants } from "../utils/Constants"; import { getUID } from "../authentication/helpers";
import { ErrorController } from "./ErrorController";
export const UserController: FC = () => { const UserController: FC = () => {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState<User>({} as User); const [account, setAccount] = useState<User>({} as User);
const [hasError, setHasError] = useState(false); const [hasError, setHasError] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [allUsers, setAllUsers] = useState<User[]>([]);
const { id } = useParams(); const { id } = useParams();
const { getTokenSilently, user } = useAuth0();
async function httpGetUser(id: string): Promise<void> {
try {
const response: HttpResponse<User> = await get<User>(
`${Constants.usersURI}/${id}`
);
if (response.parsedBody !== undefined) {
setUser(response.parsedBody);
setIsLoading(false);
}
} catch (ex) {
console.error(ex);
setHasError(true);
setError(ex);
}
}
async function httpGetAllUsers(): Promise<void> {
try {
const response: HttpResponse<User> = await get<User>(
`${Constants.usersURI}`
);
if (response.parsedBody !== undefined) {
setAllUsers((response.parsedBody as unknown) as User[]);
}
} catch (ex) {
setHasError(true);
setError(ex);
}
}
useEffect(() => { useEffect(() => {
const getUser = async (id: string): Promise<void> => {
const token = await getTokenSilently();
const Users = new UserService(token);
let response: User | undefined;
try {
response = await Users.get(id);
} catch (ex) {
if (ex === "Not Found") {
// create user
const { given_name, family_name, email, nickname, picture } = user;
const newUser: User = {
id: getUID(user),
firstName: given_name,
lastName: family_name,
fullName: `${given_name} ${family_name}`,
email,
presentation: nickname,
picture,
phone: "",
creationDate: Date.now().toLocaleString(),
activities: [],
projects: [],
tickets: [],
};
response = await Users.add(newUser);
} else {
setHasError(true);
setError(ex);
}
} finally {
if (response !== undefined) {
setAccount(response);
setIsLoading(false);
}
}
};
if (id !== undefined) { if (id !== undefined) {
httpGetUser(id); getUser(id);
httpGetAllUsers();
} else { } else {
setHasError(true); setHasError(true);
setError("Bad Request"); setError("Bad Request");
} }
}, [id]); }, [id, getTokenSilently, user]);
if (hasError) { if (hasError) {
return <ErrorController error={error} />; return <ErrorController error={error} />;
} }
const viewModel = new UserVM(user, allUsers); const viewModel = new UserVM(account);
return isLoading ? <Preloader /> : <UserPage viewModel={viewModel} />; return isLoading ? <Preloader /> : <UserPage viewModel={viewModel} />;
}; };
export default UserController;

View file

@ -6,7 +6,7 @@ import { Auth0Provider } from "./authentication/auth0";
import config from "./authentication/config.json"; import config from "./authentication/config.json";
import history from "./utils/history"; import history from "./utils/history";
const onRedirectCallback = appState => { const onRedirectCallback = (appState) => {
history.push( history.push(
appState && appState.targetUrl appState && appState.targetUrl
? appState.targetUrl ? appState.targetUrl
@ -18,6 +18,7 @@ ReactDOM.render(
<Auth0Provider <Auth0Provider
domain={config.domain} domain={config.domain}
client_id={config.clientId} client_id={config.clientId}
audience={config.audience}
redirect_uri={window.location.origin} redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback} onRedirectCallback={onRedirectCallback}
> >

View file

@ -1,23 +1,7 @@
import React from "react"; import React, { FC } from "react";
// import { LogInForm } from "../components/LogInForm";
// import { ProfileSelector } from "../components/ProfileSelector";
import SignInSide from "../components/SignInSide";
export const HomePage: React.FC = () => { const HomePage: FC = () => {
return ( return <div>HomePage</div>;
// <div className="section">
// <div className="container center">
// <h1 className="center">Ticket Manager</h1>
// <div className="row">
// <div className="col s6">
// <ProfileSelector />
// </div>
// <div className="col s6">
// <LogInForm />
// </div>
// </div>
// </div>
// </div>
<SignInSide />
);
}; };
export default HomePage;

View file

@ -1,9 +1,8 @@
import React, { FC } from "react"; import React, { FC } from "react";
import PageLayout from "../layouts/PageLayout"; import PageLayout from "../layouts/PageLayout";
import { Header } from "../components/Header"; import Header from "../components/Header";
interface IProps {} const NotFoundPage: FC = () => {
export const NotFoundPage: FC<IProps> = () => {
return ( return (
<PageLayout <PageLayout
header={<Header title="Error page" description="Something went wrong" />} header={<Header title="Error page" description="Something went wrong" />}
@ -11,3 +10,5 @@ export const NotFoundPage: FC<IProps> = () => {
/> />
); );
}; };
export default NotFoundPage;

View file

@ -1,14 +1,14 @@
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import { Grid, makeStyles, Theme } from "@material-ui/core"; import { Grid, makeStyles, Theme } from "@material-ui/core";
import { Header } from "../components/Header"; import Header from "../components/Header";
import { AvatarList } from "../components/Avatars/AvatarList"; import AvatarList from "../components/Avatars/AvatarList";
import { ProgressBar } from "../components/Progress/ProgressBar"; import ProgressBar from "../components/Progress/ProgressBar";
import { FloatingButton } from "../components/Buttons/FloatingButton"; import FloatingButton from "../components/Buttons/FloatingButton";
import { UsersModal } from "../components/Modals/UsersModal"; import UsersModal from "../components/Modals/UsersModal";
import { ProjectTabPanel } from "../components/Panels/ProjectTabPanel"; import ProjectTabPanel from "../components/Panels/ProjectTabPanel";
import ProgressInfo from "../components/Progress/ProgressInfo";
import ProjectVM from "../VM/ProjectVM"; import ProjectVM from "../VM/ProjectVM";
import PageLayout from "../layouts/PageLayout"; import PageLayout from "../layouts/PageLayout";
import { ProgressInfo } from "../components/Progress/ProgressInfo";
interface IProps { interface IProps {
viewModel: ProjectVM; viewModel: ProjectVM;
@ -22,7 +22,7 @@ const useStyles = makeStyles((theme: Theme) => ({
}, },
})); }));
export const ProjectPage: FC<IProps> = ({ viewModel }) => { const ProjectPage: FC<IProps> = ({ viewModel }) => {
const { const {
// id, // id,
title, title,
@ -97,3 +97,5 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
/> />
); );
}; };
export default ProjectPage;

View file

@ -0,0 +1,20 @@
import React, { FC } from "react";
import { Redirect } from "react-router-dom";
import SignInSide from "../components/SignInSide";
import { useAuth0 } from "../authentication/auth0";
import { getUID } from "../authentication/helpers";
import * as ROUTES from "../constants/routes";
const SigninPage: FC = () => {
const { isAuthenticated, user } = useAuth0();
if (isAuthenticated) {
// retrieve userId
const uid = getUID(user);
return <Redirect to={`${ROUTES.USERS}/${uid}`} />;
} else {
return <SignInSide />;
}
};
export default SigninPage;

View file

@ -2,12 +2,12 @@ import React, { FC } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { makeStyles, Theme, Grid, Typography } from "@material-ui/core"; import { makeStyles, Theme, Grid, Typography } from "@material-ui/core";
import { Timer } from "@material-ui/icons"; import { Timer } from "@material-ui/icons";
import TicketVM from "../VM/TicketVM";
import PageLayout from "../layouts/PageLayout"; import PageLayout from "../layouts/PageLayout";
import { TicketVM } from "../VM/TicketVM"; import Header from "../components/Header";
import { Header } from "../components/Header"; import AvatarList from "../components/Avatars/AvatarList";
import { AvatarList } from "../components/Avatars/AvatarList";
import TicketChipsArray from "../components/Cards/TicketChipsArray"; import TicketChipsArray from "../components/Cards/TicketChipsArray";
import { getRemainingdays } from "../utils/methods"; import getRemainingdays from "../utils/methods";
interface IProps { interface IProps {
viewModel: TicketVM; viewModel: TicketVM;
@ -16,7 +16,6 @@ interface IProps {
const useStyles = makeStyles((theme: Theme) => ({ const useStyles = makeStyles((theme: Theme) => ({
root: { root: {
margin: theme.spacing(1), margin: theme.spacing(1),
// flexGrow: 1,
}, },
table: { table: {
margin: "auto", margin: "auto",
@ -28,7 +27,7 @@ const useStyles = makeStyles((theme: Theme) => ({
}, },
})); }));
export const TicketPage: FC<IProps> = ({ viewModel }) => { const TicketPage: FC<IProps> = ({ viewModel }) => {
const { const {
title, title,
description, description,
@ -133,3 +132,5 @@ export const TicketPage: FC<IProps> = ({ viewModel }) => {
// </TableContainer> // </TableContainer>
// ); // );
// }; // };
export default TicketPage;

View file

@ -1,22 +1,15 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { UserVM } from "../VM/UserVM"; import { UserVM } from "../VM/UserVM";
import { UserHeader } from "../components/UserHeader"; import UserHeader from "../components/UserHeader";
import { UserTabPanel } from "../components/Panels/UserTabPanel"; import UserTabPanel from "../components/Panels/UserTabPanel";
import PageLayout from "../layouts/PageLayout"; import PageLayout from "../layouts/PageLayout";
interface IProps { interface IProps {
viewModel: UserVM; viewModel: UserVM;
} }
export const UserPage: FC<IProps> = ({ viewModel }) => { const UserPage: FC<IProps> = ({ viewModel }) => {
const { const { fullName, presentation, picture, projects, tickets } = viewModel;
fullName,
presentation,
picture,
projects,
tickets,
allUsers,
} = viewModel;
const tabNames: string[] = ["Projects", "Tickets"]; const tabNames: string[] = ["Projects", "Tickets"];
return ( return (
@ -33,9 +26,10 @@ export const UserPage: FC<IProps> = ({ viewModel }) => {
tabNames={tabNames} tabNames={tabNames}
projects={projects} projects={projects}
tickets={tickets} tickets={tickets}
allUsers={allUsers}
/> />
} }
/> />
); );
}; };
export default UserPage;

View file

@ -0,0 +1,33 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import PrivateRoute from "./PrivateRoute";
import HomeController from "../controllers/HomeController";
import ProjectController from "../controllers/ProjectController";
import UserController from "../controllers/UserController";
import TicketController from "../controllers/TicketController";
import NotFoundPage from "../pages/NotFoundPage";
import TestPage from "../pages/TestPage";
import SigninPage from "../pages/SigninPage";
import * as ROUTES from "../constants/routes";
const AppRouter = () => {
return (
<Switch>
<PrivateRoute path={ROUTES.TEST} component={TestPage} />
<Route exact path={ROUTES.HOME} component={HomeController} />
<Route path={ROUTES.SIGN_IN} component={SigninPage} />
<PrivateRoute
path={`${ROUTES.PROJECTS}/:id`}
component={ProjectController}
/>
<PrivateRoute
path={`${ROUTES.TICKETS}/:id`}
component={TicketController}
/>
<PrivateRoute path={`${ROUTES.USERS}/:id`} component={UserController} />
<Route path={ROUTES.NOT_FOUND} component={NotFoundPage} />
</Switch>
);
};
export default AppRouter;

View file

@ -2,7 +2,7 @@ import React, { useEffect } from "react";
import { Route } from "react-router-dom"; import { Route } from "react-router-dom";
import { useAuth0 } from "../authentication/auth0"; import { useAuth0 } from "../authentication/auth0";
export const PrivateRoute = ({ component: Component, path, ...rest }) => { const PrivateRoute = ({ component: Component, path, ...rest }) => {
const { loading, isAuthenticated, loginWithRedirect } = useAuth0(); const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
useEffect(() => { useEffect(() => {
@ -11,14 +11,16 @@ export const PrivateRoute = ({ component: Component, path, ...rest }) => {
} }
const fn = async () => { const fn = async () => {
await loginWithRedirect({ await loginWithRedirect({
appState: { targetUrl: window.location.pathname } appState: { targetUrl: window.location.pathname },
}); });
}; };
fn(); fn();
}, [loading, isAuthenticated, loginWithRedirect, path]); }, [loading, isAuthenticated, loginWithRedirect, path]);
const render = props => const render = (props) =>
isAuthenticated === true ? <Component {...props} /> : null; isAuthenticated === true ? <Component {...props} /> : null;
return <Route path={path} render={render} {...rest} />; return <Route path={path} render={render} {...rest} />;
}; };
export default PrivateRoute;

View file

@ -0,0 +1,72 @@
import HttpResponse from "../types/HttpResponse";
export default class HttpHandler<T> {
private newHeaders = async (token: string) => {
// const { getTokenSilently } = useAuth0();
// const token = await getTokenSilently();
return new Headers({
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
});
};
private execute = async (request: RequestInfo): Promise<HttpResponse<T>> => {
const response: HttpResponse<T> = await fetch(request);
try {
response.parsedBody = await response.json();
} catch (ex) {}
if (!response.ok) {
throw response.statusText;
}
return response;
};
get = async (path: string, token: string): Promise<HttpResponse<T>> => {
const args: RequestInit = {
method: "get",
headers: await this.newHeaders(token),
};
return await this.execute(new Request(path, args));
};
post = async (
path: string,
body: any,
token: string
): Promise<HttpResponse<T>> => {
const args: RequestInit = {
method: "post",
headers: await this.newHeaders(token),
body: JSON.stringify(body),
};
return await this.execute(new Request(path, args));
};
put = async (
path: string,
body: any,
token: string
): Promise<HttpResponse<T>> => {
const args: RequestInit = {
method: "put",
headers: await this.newHeaders(token),
body: JSON.stringify(body),
};
return await this.execute(new Request(path, args));
};
patch = async (
path: string,
body: any,
token: string
): Promise<HttpResponse<T>> => {
const args: RequestInit = {
method: "patch",
headers: await this.newHeaders(token),
body: JSON.stringify(body),
};
return await this.execute(new Request(path, args));
};
}

View file

@ -0,0 +1,13 @@
import ProjectService from "./project";
import TicketService from "./ticket";
import UserService from "./user";
export default interface IService<T> {
all(): Promise<T[]>;
get(id: string): Promise<T>;
add(item: any): Promise<T>;
update(id: string, item: T): Promise<void>;
delete(id: string): Promise<void>;
}
export { ProjectService, TicketService, UserService };

View file

@ -0,0 +1,44 @@
import IService from ".";
import Project from "../types/Project";
import HttpHandler from "./http";
interface NewProject {
title: string;
description: string;
endingDate: string;
managerId: string;
}
export default class ProjectService implements IService<Project> {
constructor(private key: string) {}
private http = new HttpHandler<Project>();
private path: string = "/api/v1/projects";
all = async (): Promise<Project[]> => {
const response = await this.http.get(this.path, this.key);
return (response.parsedBody as unknown) as Project[];
};
get = async (id: string): Promise<Project> => {
const response = await this.http.get(`${this.path}/${id}`, this.key);
const body = response.parsedBody;
return body ?? ({} as Project);
};
add = async (item: NewProject): Promise<Project> => {
const response = await this.http.post(this.path, item, this.key);
const body = response.parsedBody;
return body ?? ({} as Project);
};
update(id: string, item: Project): Promise<void> {
throw new Error("Method not implemented.");
}
delete(id: string): Promise<void> {
throw new Error("Method not implemented.");
}
setMembers = async (id: string, members: string[]): Promise<void> => {
await this.http.patch(`${this.path}/${id}/members`, members, this.key);
};
}

View file

@ -0,0 +1,50 @@
import IService from ".";
import Ticket from "../types/Ticket";
import HttpHandler from "./http";
interface NewTicket {
title: string;
description: string;
endingDate: string;
creatorId: string;
projectId: number;
impact: number;
difficulty: number;
category: number;
}
export default class TicketService implements IService<Ticket> {
constructor(private key: string) {}
private http = new HttpHandler<Ticket>();
private path: string = "/api/v1/tickets";
all = async (): Promise<Ticket[]> => {
const response = await this.http.get(this.path, this.key);
return (response.parsedBody as unknown) as Ticket[];
};
get = async (id: string): Promise<Ticket> => {
const response = await this.http.get(`${this.path}/${id}`, this.key);
const body = response.parsedBody;
return body ?? ({} as Ticket);
};
add = async (item: NewTicket): Promise<Ticket> => {
const response = await this.http.post(this.path, item, this.key);
const body = response.parsedBody;
return body ?? ({} as Ticket);
};
update(id: string, item: Ticket): Promise<void> {
throw new Error("Method not implemented.");
}
delete(id: string): Promise<void> {
throw new Error("Method not implemented.");
}
close = async (id: string): Promise<void> => {
await this.http.put(`${this.path}/${id}/closed`, {}, this.key);
};
}

View file

@ -0,0 +1,35 @@
import IService from ".";
import User from "../types/User";
import HttpHandler from "./http";
export default class UserService implements IService<User> {
constructor(private key: string) {}
private http = new HttpHandler<User>();
private path: string = "/api/v1/users";
all = async (): Promise<User[]> => {
const response = await this.http.get(this.path, this.key);
return (response.parsedBody as unknown) as User[];
};
get = async (id: string): Promise<User> => {
const response = await this.http.get(`${this.path}/${id}`, this.key);
const body = response.parsedBody;
return body ?? ({} as User);
};
add = async (item: User): Promise<User> => {
const response = await this.http.post(this.path, item, this.key);
const body = response.parsedBody;
return body ?? ({} as User);
};
update = async (id: string, item: User): Promise<void> => {
throw new Error("Method not implemented.");
};
delete = async (id: string): Promise<void> => {
throw new Error("Method not implemented.");
};
}

View file

@ -1,7 +1,7 @@
import { User } from "./User"; import User from "./User";
import { Ticket } from "./Ticket"; import Ticket from "./Ticket";
export interface Activity { export default interface Activity {
id: number; id: number;
description: string; description: string;
date: Date; date: Date;

View file

@ -1,6 +1,6 @@
import { User } from "./User"; import User from "./User";
export interface AppFile { export default interface AppFile {
id: number; id: number;
name: string; name: string;
description: string; description: string;

View file

@ -1,3 +1,3 @@
export interface HttpResponse<T> extends Response { export default interface HttpResponse<T> extends Response {
parsedBody?: T; parsedBody?: T;
} }

View file

@ -1,3 +1,3 @@
export interface Note { export default interface Note {
Id: number; Id: number;
} }

View file

@ -1,9 +1,9 @@
import { Ticket } from "./Ticket"; import AppFile from "./AppFile";
import { User } from "./User"; import Activity from "./Activity";
import { AppFile } from "./AppFile"; import Ticket from "./Ticket";
import { Activity } from "./Activity"; import User from "./User";
export interface Project { export default interface Project {
id: number; id: number;
title: string; title: string;
description: string; description: string;

View file

@ -1,7 +1,7 @@
import { Project } from "./Project"; import Project from "./Project";
import { User } from "./User"; import User from "./User";
export interface Ticket { export default interface Ticket {
id: number; id: number;
title: string; title: string;
description: string; description: string;

View file

@ -1,8 +1,8 @@
import { Activity } from "./Activity"; import Activity from "./Activity";
import { Project } from "./Project"; import Project from "./Project";
import { Ticket } from "./Ticket"; import Ticket from "./Ticket";
export interface User { export default interface User {
id: string; id: string;
firstName: string; firstName: string;
lastName: string; lastName: string;

View file

@ -1,4 +1,4 @@
export class Constants { export default class Constants {
static projectsURI: string = "/api/v1/projects"; static projectsURI: string = "/api/v1/projects";
static ticketsURI: string = "/api/v1/tickets"; static ticketsURI: string = "/api/v1/tickets";
static usersURI: string = "/api/v1/users"; static usersURI: string = "/api/v1/users";

View file

@ -1,2 +1,4 @@
import { createBrowserHistory } from "history"; import * as createHistory from "history";
export default createBrowserHistory; const history = createHistory.createBrowserHistory();
export default history;

View file

@ -1,4 +1,4 @@
import { HttpResponse } from "../types/HttpResponse"; import HttpResponse from "../types/HttpResponse";
export async function http<T>(request: RequestInfo): Promise<HttpResponse<T>> { export async function http<T>(request: RequestInfo): Promise<HttpResponse<T>> {
const response: HttpResponse<T> = await fetch(request); const response: HttpResponse<T> = await fetch(request);
@ -24,7 +24,7 @@ export async function post<T>(
args: RequestInit = { args: RequestInit = {
method: "post", method: "post",
headers: headers, headers: headers,
body: JSON.stringify(body) body: JSON.stringify(body),
} }
): Promise<HttpResponse<T>> { ): Promise<HttpResponse<T>> {
return await http<T>(new Request(path, args)); return await http<T>(new Request(path, args));
@ -36,7 +36,7 @@ export async function put<T>(
args: RequestInit = { args: RequestInit = {
method: "put", method: "put",
headers: headers, headers: headers,
body: JSON.stringify(body) body: JSON.stringify(body),
} }
): Promise<HttpResponse<T>> { ): Promise<HttpResponse<T>> {
return await http<T>(new Request(path, args)); return await http<T>(new Request(path, args));
@ -48,7 +48,7 @@ export async function patch<T>(
args: RequestInit = { args: RequestInit = {
method: "patch", method: "patch",
headers: headers, headers: headers,
body: JSON.stringify(body) body: JSON.stringify(body),
} }
): Promise<HttpResponse<T>> { ): Promise<HttpResponse<T>> {
return await http<T>(new Request(path, args)); return await http<T>(new Request(path, args));
@ -58,5 +58,5 @@ const headers: Headers = new Headers({
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: Authorization:
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4NDE5ODQ4MCwiZXhwIjoxNTg0Mjg0ODgwLCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.I1D49ILGBLhnq9biIA0y6Ra93zTKRDJI_rfGvU05MtT1zkI1ZliX9P-7LyKeWBv8tPonB6gT12lJiai_GHBET8kKbXNqwfVvDJ3eqYK-TtTqfL65RfWL9tQfQybHbfuF9M0oiXMqWMqmsc5Umpp4a3bLTQgwkUEKxcdMm84L7zoaqMycns4mFojWpQJKfPa64oZFDIXYy6hPDXcX50Djuk1m-aqMhtpmqkZvPfwEjvtEtGGCTOJHV7uugn3r8Wk4HX02ShrV676GICE1Yw7eHufAbY7yvHz3ImZ1cfEVrRbbijPA2vogXd5RmqNyindDDlT1Y_C80U0DyvhS7P7apQ" "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4NDE5ODQ4MCwiZXhwIjoxNTg0Mjg0ODgwLCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.I1D49ILGBLhnq9biIA0y6Ra93zTKRDJI_rfGvU05MtT1zkI1ZliX9P-7LyKeWBv8tPonB6gT12lJiai_GHBET8kKbXNqwfVvDJ3eqYK-TtTqfL65RfWL9tQfQybHbfuF9M0oiXMqWMqmsc5Umpp4a3bLTQgwkUEKxcdMm84L7zoaqMycns4mFojWpQJKfPa64oZFDIXYy6hPDXcX50Djuk1m-aqMhtpmqkZvPfwEjvtEtGGCTOJHV7uugn3r8Wk4HX02ShrV676GICE1Yw7eHufAbY7yvHz3ImZ1cfEVrRbbijPA2vogXd5RmqNyindDDlT1Y_C80U0DyvhS7P7apQ",
}); });

View file

@ -1,7 +1,12 @@
export const getRemainingdays: (endDate: string) => number = ( const getRemainingdays: (endDate: string) => number = (endDate: string) => {
endDate: string
) => {
let endingDate: Date = new Date(endDate); let endingDate: Date = new Date(endDate);
let today: Date = new Date(); let today: Date = new Date();
return Math.abs(endingDate.getDate() - today.getDate()); return Math.abs(endingDate.getDate() - today.getDate());
}; };
export default getRemainingdays;
/**
* get today date
*/
export const today = (): string => new Date().toISOString().split("T")[0];

View file

@ -1,37 +0,0 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import { HomeController } from "../controllers/HomeController";
import { ProjectController } from "../controllers/ProjectController";
import { UserController } from "../controllers/UserController";
import { TicketController } from "../controllers/TicketController";
import { NotFoundPage } from "../pages/NotFoundPage";
export const AppRouter = () => {
return (
<Switch>
{/* <PrivateRoute exact path="/test">
<TestPage />
</PrivateRoute> */}
<Route exact path="/">
<HomeController />
</Route>
<Route path="/users/:id">
<UserController />
</Route>
<Route path="/projects/:id">
<ProjectController />
</Route>
<Route path="/tickets/:id">
<TicketController />
</Route>
<Route path="/404">
<NotFoundPage />
</Route>
</Switch>
);
};

View file

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es6",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
@ -13,7 +13,14 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react" "jsx": "react",
"sourceMap": true,
"declaration": false,
"emitDecoratorMetadata": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"importHelpers": true,
"typeRoots": ["node_modules/@types"]
}, },
"include": ["src"] "include": ["src"]
} }