This commit is contained in:
Ruidy Nemausat 2020-03-09 09:37:33 +01:00
commit 5f33789dc2
31 changed files with 367 additions and 120 deletions

2
.gitignore vendored
View file

@ -35,3 +35,5 @@ client/.env.production.local
client/npm-debug.log*
client/yarn-debug.log*
client/yarn-error.log*
client/src/authentication/config.json

View file

@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

View file

@ -47,6 +47,7 @@
- [ ] error page redirect when offline.
- [x] ticket/files/activities list placeholders when empty
- [ ] think about public/private DTO's constructor, getters and setters
- [<span style="color:red">X</span>] write dtos without circular dependencies.
- [<span style="color:red">x</span>] write dtos without circular dependencies.
- [ ] use dtoRequest for PutProjects
- [ ] render avatarlist after UserModal Update
- [ ] Form validators

View file

@ -4,10 +4,11 @@ namespace TicketManager.Resources
{
public class NewAppUserDTO
{
[Required]
public string FirstName { get; set; }
public string LastName { get; set; }
public string Presentation { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }

View file

@ -8,7 +8,9 @@ namespace TicketManager.Resources
[Required]
public string Title { get; set; }
public string Description { get; set; }
[Required]
public DateTime EndingDate { get; set; }
[Required]
public Guid ManagerId { get; set; }
}
}

View file

@ -8,10 +8,11 @@ namespace TicketManager.Resources
{
public class NewTicketDTO
{
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime EndingDate { get; set; }
@ -20,8 +21,9 @@ namespace TicketManager.Resources
public string Difficulty { get; set; }
public string Category { get; set; }
[Required]
public Guid CreatorId { get; set; }
[Required]
public int ProjectId { get; set; }
}
}

View file

@ -4,6 +4,19 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@auth0/auth0-spa-js": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.6.4.tgz",
"integrity": "sha512-zKUfUxOHL/YwSedP1BkKKRRrB4AJ7whMKBVlf6M7dTvVzeCrRzTWEXUF+HB4/iG4skVRyr+U35qQzMYoFWCxhw==",
"requires": {
"browser-tabs-lock": "^1.2.1",
"core-js": "^3.2.1",
"es-cookie": "^1.2.0",
"fast-text-encoding": "^1.0.0",
"promise-polyfill": "^8.1.3",
"unfetch": "^4.1.0"
}
},
"@babel/code-frame": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
@ -1441,6 +1454,11 @@
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz",
"integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA=="
},
"@types/auth0": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@types/auth0/-/auth0-2.20.0.tgz",
"integrity": "sha512-dIb3VeVD6fSrGMUBWVHcb4N4trxC8BfSNkzqpafhhwsXDiJgeb4GQTqN4b+7Pafyq7vRVpeOoaS4gq9ovT+YwQ=="
},
"@types/babel__core": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
@ -2702,6 +2720,11 @@
}
}
},
"browser-tabs-lock": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.7.tgz",
"integrity": "sha512-2H3EX1YstZEI8mkmoKPo7cIUfc0lJe2nysFQjcY4wRk/Z6Mb6jnDPYEloQNP0LQuBe4J7M1T78+sFE0gZagHDw=="
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
@ -4315,6 +4338,11 @@
"string.prototype.trimright": "^2.1.1"
}
},
"es-cookie": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz",
"integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q=="
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
@ -5171,6 +5199,11 @@
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"fast-text-encoding": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz",
"integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ=="
},
"faye-websocket": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@ -10219,6 +10252,11 @@
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"promise-polyfill": {
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz",
"integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g=="
},
"prompts": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.0.tgz",
@ -12549,6 +12587,11 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz",
"integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ=="
},
"unfetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
"integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg=="
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",

View file

@ -3,9 +3,11 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@auth0/auth0-spa-js": "^1.6.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"@types/auth0": "^2.20.0",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.26",
"@types/react": "^16.9.19",

View file

@ -1,8 +1,19 @@
import React, { FC } from "react";
import Layout from "./pages/Layout";
import { useAuth0 } from "./authentication/auth0";
const App: FC = () => {
return <Layout />;
const { loading } = useAuth0();
if (loading) {
return <div>Loading...</div>;
}
return (
<div className="App">
<Layout />
</div>
);
};
export default App;

View file

@ -6,7 +6,7 @@ import { User } from "../types/User";
import { getRemainingdays } from "../utils/methods";
export default class ProjectVM {
// public id: number;
public id: number;
public title: string;
public description: string;
public creationDate: string;
@ -22,9 +22,14 @@ export default class ProjectVM {
public ticketsTotalCount: number;
public ticketsDone: number;
public remainingDays: number;
public allProjects: Project[];
public constructor(project: Project, allUsers: User[]) {
// this.id = project.id;
public constructor(
project: Project,
allUsers: User[],
allProjects: Project[]
) {
this.id = project.id;
this.title = project.title;
this.description = project.description;
this.creationDate = project.creationDate;
@ -44,5 +49,6 @@ export default class ProjectVM {
? 0
: this.tickets.filter(t => t.status === "Done").length;
this.remainingDays = getRemainingdays(project.endingDate);
this.allProjects = allProjects;
}
}

View file

@ -0,0 +1,112 @@
import React, { useState, useEffect, useContext, FC } from "react";
import createAuth0Client from "@auth0/auth0-spa-js";
import Auth0Client from "@auth0/auth0-spa-js/dist/typings/Auth0Client";
interface IAuth0Context {
isAuthenticated: boolean;
user: any;
loading: boolean;
popupOpen: boolean;
loginWithPopup(options: PopupLoginOptions): Promise<void>;
handleRedirectCallback(): Promise<RedirectLoginResult>;
getIdTokenClaims(o?: getIdTokenClaimsOptions): Promise<IdToken>;
loginWithRedirect(o: RedirectLoginOptions): Promise<void>;
getTokenSilently(o?: GetTokenSilentlyOptions): Promise<string | undefined>;
getTokenWithPopup(o?: GetTokenWithPopupOptions): Promise<string | undefined>;
logout(o?: LogoutOptions): void;
}
export interface IAuth0ProviderOptions {
children: React.ReactElement;
onRedirectCallback?(result: RedirectLoginResult): void;
}
const DEFAULT_REDIRECT_CALLBACK: () => void = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context: React.Context<IAuth0Context | null> = React.createContext<IAuth0Context | null>(
null
);
export const useAuth0: () => IAuth0Context = () => useContext(Auth0Context)!;
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}: IAuth0ProviderOptions & Auth0ClientOptions) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState<Auth0Client>();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (window.location.search.includes("code=")) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallback(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (o: PopupLoginOptions) => {
setPopupOpen(true);
try {
await auth0Client!.loginWithPopup(o);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await auth0Client!.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
const result = await auth0Client!.handleRedirectCallback();
const user = await auth0Client!.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
return result;
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (o: getIdTokenClaimsOptions | undefined) =>
auth0Client!.getIdTokenClaims(o),
loginWithRedirect: (o: RedirectLoginOptions) =>
auth0Client!.loginWithRedirect(o),
getTokenSilently: (o: GetTokenSilentlyOptions | undefined) =>
auth0Client!.getTokenSilently(o),
getTokenWithPopup: (o: GetTokenWithPopupOptions | undefined) =>
auth0Client!.getTokenWithPopup(o),
logout: (o: LogoutOptions | undefined) => auth0Client!.logout(o)
}}
>
{children}
</Auth0Context.Provider>
);
};

View file

@ -6,7 +6,13 @@ interface IProps {
export const Avatar: FC<IProps> = ({ picture }) => {
return (
<>
<img className="circle" src={picture} height="100vh" width="100vh" />
<img
className="circle"
src={picture}
height="100vh"
width="100vh"
alt="user avatar"
/>
</>
);
};

View file

@ -0,0 +1,16 @@
import React from "react";
import { useAuth0 } from "../authentication/auth0";
export const NavBar = () => {
const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
return (
<div>
{!isAuthenticated && (
<button onClick={() => loginWithRedirect({})}>Log in</button>
)}
{isAuthenticated && <button onClick={() => logout()}>Log out</button>}
</div>
);
};

View file

@ -1,4 +1,5 @@
import React, { FC } from "react";
import { Project } from "../types/Project";
interface IProps {
title: string;
@ -7,6 +8,9 @@ interface IProps {
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> = ({
@ -15,7 +19,10 @@ export const NewTicketForm: FC<IProps> = ({
description,
setDescription,
endingDate,
setEndingDate
setEndingDate,
allProjects,
projectId,
setProjectId
}) => {
return (
<>
@ -62,13 +69,23 @@ export const NewTicketForm: FC<IProps> = ({
</div>
<div className="input-field">
<select id="project" className="browser-default">
<option value="" disabled selected>
<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>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
{allProjects.map(p => (
<option key={p.id} value={p.id}>
{p.title}
</option>
))}
</select>
</div>
</div>

View file

@ -1,32 +1,31 @@
import React, { FC, useState, ChangeEvent, useEffect, FormEvent } from "react";
import { useParams } from "react-router-dom";
import React, { FC, useState, FormEvent } from "react";
import { useParams, useRouteMatch } from "react-router-dom";
import { Modal } from "./Modal";
import { NewTicketTabRouter } from "./NewTicketTabRouter";
import { User } from "../types/User";
import { NewTicketForm } from "./NewTicketForm";
import { Ticket } from "../types/Ticket";
import { patch, post } from "../utils/http";
import { Constants } from "../utils/Constants";
import { Project } from "../types/Project";
import { post } from "../utils/http";
import { Constants } from "../utils/Constants";
import { HttpResponse } from "../types/HttpResponse";
interface IProps {
show: boolean;
handleClose(): void;
allUsers: User[];
allProjects: Project[];
}
export const NewTicketModal: FC<IProps> = ({ show, handleClose, allUsers }) => {
const [filterText, setFilterText] = useState<string>("");
const { id } = useParams();
export const NewTicketModal: FC<IProps> = ({
show,
handleClose,
allProjects
}) => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [endingDate, setEndingDate] = useState("");
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
e: ChangeEvent<HTMLInputElement>
) => {
setFilterText(e.target.value);
};
const { url } = useRouteMatch();
const id = url.split("/")[2];
const [projectId, setProjectId] = useState(id);
const handleSubmit: (event: FormEvent<HTMLFormElement>) => void = async (
e: FormEvent
@ -35,20 +34,19 @@ export const NewTicketModal: FC<IProps> = ({ show, handleClose, allUsers }) => {
let newTicket = {
title: title,
description: description,
endingDate: endingDate,
endingDate: new Date(endingDate).toISOString(),
creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94",
projectId: 1
projectId: parseInt(projectId)
};
console.log(newTicket);
// console.log(newTicket);
const response: HttpResponse<Ticket> = await post<Ticket>(
`${Constants.ticketsURI}`,
newTicket
);
console.log(response.parsedBody);
// console.log(response.parsedBody);
handleClose();
};
useEffect(() => {});
return (
<Modal show={show} handleClose={handleClose}>
<div className="row valign-wrapper indigo">
@ -68,15 +66,16 @@ export const NewTicketModal: FC<IProps> = ({ show, handleClose, allUsers }) => {
<form onSubmit={handleSubmit}>
<div className="row">
<NewTicketTabRouter
tabNames={["Details", "Members"]}
users={allUsers}
<NewTicketForm
title={title}
setTitle={setTitle}
description={description}
setDescription={setDescription}
endingDate={endingDate}
setEndingDate={setEndingDate}
allProjects={allProjects}
projectId={projectId}
setProjectId={setProjectId}
/>
</div>

View file

@ -1,14 +1,9 @@
import React, { FC } from "react";
import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { useRouteMatch } from "react-router-dom";
import { TabRouterHeader } from "./TabRouterHeader";
import { NewTicketForm } from "./NewTicketForm";
import { MemberList } from "./MemberList";
import { User } from "../types/User";
import { Ticket } from "../types/Ticket";
interface IProps {
tabNames: string[];
users: User[];
description: string;
setDescription: React.Dispatch<React.SetStateAction<string>>;
title: string;
@ -19,7 +14,6 @@ interface IProps {
export const NewTicketTabRouter: FC<IProps> = ({
tabNames,
users,
description,
setDescription,
title,
@ -33,22 +27,14 @@ export const NewTicketTabRouter: FC<IProps> = ({
<div className="row">
<TabRouterHeader tabNames={tabNames} />
<Redirect from={url} to={`${url}/details`} />
<Route path={`${url}/details`}>
<NewTicketForm
title={title}
setTitle={setTitle}
description={description}
setDescription={setDescription}
endingDate={endingDate}
setEndingDate={setEndingDate}
/>
</Route>
<Route path={`${url}/members`}>
<MemberList users={users} />
</Route>
{/* <NewTicketForm
title={title}
setTitle={setTitle}
description={description}
setDescription={setDescription}
endingDate={endingDate}
setEndingDate={setEndingDate}
/> */}
</div>
</>
);

View file

@ -16,7 +16,18 @@ export const ProgressBar: FC<ProgressBarProps> = ({
remainingDays
}) => {
const styleString: CSSProperties = { width: `${value}%` };
const barColor: string = value < 75 ? "red" : "";
let barColor: string = "green";
if (value < 100) {
barColor = "yellow";
}
if (value < 200 / 3) {
barColor = "orange";
}
if (value < 100 / 3) {
barColor = "red";
}
return (
<>
<div className="row">

View file

@ -3,11 +3,11 @@ import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { TabRouterHeader } from "./TabRouterHeader";
import { TicketList } from "./TicketList";
import { FileList } from "./AppFileList";
import { ActivityList } from "./ActivityList";
// import { ActivityList } from "./ActivityList";
import { Ticket } from "../types/Ticket";
import { AppFile } from "../types/AppFile";
import { Activity } from "../types/Activity";
import { User } from "../types/User";
import { Project } from "../types/Project";
interface IProps {
tickets: Ticket[];
@ -15,7 +15,7 @@ interface IProps {
tabNames: string[];
files: AppFile[];
activities: Activity[];
allUsers: User[];
allProjects: Project[];
}
export const TabRouter: FC<IProps> = ({
@ -23,7 +23,7 @@ export const TabRouter: FC<IProps> = ({
tabNames,
files,
activities,
allUsers
allProjects
}) => {
const { url } = useRouteMatch();
@ -35,16 +35,16 @@ export const TabRouter: FC<IProps> = ({
<Redirect from={url} to={`${url}/tickets`} />
<Route path={`${url}/tickets`}>
<TicketList tickets={tickets} users={allUsers} />
<TicketList tickets={tickets} allProjects={allProjects} />
</Route>
<Route path={`${url}/files`}>
<FileList files={files} />
</Route>
<Route path={`${url}/activity`}>
{/* <Route path={`${url}/activity`}>
<ActivityList activities={activities} />
</Route>
</Route> */}
</div>
</>
);

View file

@ -3,23 +3,22 @@ import { Ticket } from "../types/Ticket";
import { FloatingButton } from "./FloatingButton";
import { HorizontalCard } from "./HorizontalCard";
import { FilterBar } from "./FilterBar";
import { User } from "../types/User";
import { HttpResponse } from "../types/HttpResponse";
import { put } from "../utils/http";
import { Constants } from "../utils/Constants";
import { NewTicketModal } from "./NewTicketModal";
import { Project } from "../types/Project";
type TicketListProps = {
tickets: Ticket[];
users: User[];
allProjects: Project[];
};
export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
export const TicketList: FC<TicketListProps> = ({ tickets, allProjects }) => {
const [filterText, setFilterText] = useState<string>("");
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
setFilterText("");
};
// const archiveTicket = () => {};
const onClick: (e: MouseEvent) => void = (e: MouseEvent) => {
e.preventDefault();
@ -45,7 +44,7 @@ export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
setShowNew(false);
}}
show={showNew}
allUsers={users}
allProjects={allProjects}
/>
<h3>Tickets</h3>
<FloatingButton
@ -59,7 +58,7 @@ export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
clearFilterText={clearFilterText}
/>
</div>
<div className="col s12 grey">
<div className="col s12 grey lighten-1">
<ul>
{filteredTickets.length === 0 ? (
<HorizontalCard />
@ -76,7 +75,6 @@ export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
{}
);
}}
// archiveTicket={archiveTicket}
/>
))
)}

View file

@ -4,6 +4,7 @@ import { TabRouterHeader } from "./TabRouterHeader";
import { ProjectList } from "./ProjectList";
import { Ticket } from "../types/Ticket";
import { Project } from "../types/Project";
import { TicketList } from "./TicketList";
interface IProps {
tabNames: string[];
@ -25,9 +26,9 @@ export const UserTabRouter: FC<IProps> = ({ tickets, tabNames, projects }) => {
<ProjectList projects={projects} />
</Route>
{/* <Route path={`${url}/tickets`}>
<TicketList tickets={tickets} />
</Route> */}
<Route path={`${url}/tickets`}>
<TicketList tickets={tickets} allProjects={[]} />
</Route>
</div>
</>
);

View file

@ -1,4 +1,4 @@
import React, { FC, useState, ChangeEvent, FormEvent } from "react";
import React, { FC, useState, ChangeEvent, FormEvent, useEffect } from "react";
import { Modal } from "./Modal";
import { AvatarList } from "./AvatarList";
import { User } from "../types/User";

View file

@ -12,7 +12,7 @@ export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
return Boolean(members.find(m => m.id === id));
};
return (
<div className="row">
<div className="valign-wrapper">
<label htmlFor={user.id}>
<input
id={user.id}
@ -27,15 +27,16 @@ export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
/>
<span>
{user.fullName}
<img
className="circle"
src={user.picture}
width="32vh"
height="32vh"
alt={user.fullName}
/>
{" "}
</span>
</label>
<img
className="circle"
src={user.picture}
width="32vh"
height="32vh"
alt={user.fullName}
/>
</div>
);
};

View file

@ -13,6 +13,7 @@ import { User } from "../types/User";
export const ProjectController: FC = () => {
const [project, setProject] = useState<Project>({} as Project);
const [allUsers, setAllUsers] = useState<User[]>([]);
const [allProjects, setAllProjects] = useState<Project[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const [error, setError] = useState("");
@ -48,10 +49,25 @@ export const ProjectController: FC = () => {
}
}
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(() => {
if (id !== undefined) {
httpGetProjects(id);
httpGetAllUsers();
httpGetAllProjects();
} else {
setHasError(true);
setError("Bad Request");
@ -61,6 +77,6 @@ export const ProjectController: FC = () => {
if (hasError) {
return <ErrorController error={error} />;
}
const viewModel = new ProjectVM(project, allUsers);
const viewModel = new ProjectVM(project, allUsers, allProjects);
return isLoading ? <Preloader /> : <ProjectPage viewModel={viewModel} />;
};

29
client/src/index.jsx Normal file
View file

@ -0,0 +1,29 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { Auth0Provider } from "./authentication/auth0";
import config from "./authentication/config.json";
import history from "./utils/history";
const onRedirectCallback = appState => {
history.push(
appState && appState.targetUrl
? appState.targetUrl
: window.location.pathname
);
};
ReactDOM.render(
<Auth0Provider
domain={config.domain}
client_id={config.clientId}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
<App />
</Auth0Provider>,
document.getElementById("root")
);
serviceWorker.unregister();

View file

@ -1,8 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(<App />, document.getElementById("root"));
serviceWorker.unregister();

View file

@ -1,11 +1,14 @@
import React, { FC } from "react";
import { AppRouter } from "../utils/router";
import { NavBar } from "../components/Navbar";
const Layout: FC = () => {
return (
<>
{/* <NavBar />
<BreadCrumb /> */}
<header>
<NavBar />
</header>
{/* <BreadCrumb /> */}
<AppRouter />
{/* <Footer /> */}
</>

View file

@ -13,6 +13,7 @@ interface IProps {
export const ProjectPage: FC<IProps> = ({ viewModel }) => {
const {
// id,
title,
description,
users,
@ -23,10 +24,11 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
ticketsTotalCount,
remainingDays,
files,
activities
activities,
allProjects
} = viewModel;
const tabNames: string[] = ["Tickets", "Files", "Activity"];
const tabNames: string[] = ["Tickets", "Files"]; //, "Activity"];
const [showModal, setShowModal] = useState<boolean>(false);
return (
@ -59,7 +61,7 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
tickets={tickets}
files={files}
activities={activities}
allUsers={allUsers}
allProjects={allProjects}
/>
</div>
</div>

View file

@ -23,10 +23,6 @@ export const UserPage: FC<IProps> = ({ viewModel }) => {
tickets={tickets}
/>
</div>
{/* // <TabView>
// <CardList>
// <CardList>
// </TabView> */}
</div>
);
};

View file

@ -0,0 +1,2 @@
import { createBrowserHistory } from "history";
export default createBrowserHistory;

View file

@ -58,5 +58,5 @@ const headers: Headers = new Headers({
Accept: "application/json",
"Content-Type": "application/json",
Authorization:
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4Mjk3MTQyMSwiZXhwIjoxNTgzMDU3ODIxLCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.mH_ejE0gpMXrMIVUV7afuagTopCxm2x7F7Ash9L7zfOCQnw71E7NPsEh8w2_nFFz3lwm988vGbJhB_1G0oetK3VnqgahHn-ZJfk8RhQeKZQtCddbFCXSZzbsvi8XekpN2qLSZswrfxM4hiNfedQW1sM6wSbVbv4q6MrpPrtnepOo5lu67b9eHQZA5MQGqCLqqAZtEAa4Z8bVUCUcf3wU4e9W38LngrMSEMN62_ZZ8AVnjFVQ97zWEadJhYT54S9tVioY8jNR-38qjuYH_ZP3mVQg8INza9YFiYzIsIgdYufhorb_cSXc1qK1ZhHf4kRHaiHCYan-c9nN9SM9MCYA9A"
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4MzI0ODU1NSwiZXhwIjoxNTgzMzM0OTU1LCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.lfgZAwqq3VHNAM1Y33Vj7Jtal6IdN92qWJHwu5T8l-56kBXNC2g1pm6TshRhYDKmedoJmSddjKXClA9eLu1Ve1X8wLj7CbrhZtGNsOcIEStF9icahCy8y2OdP2U6UXJJt9HehwqvwT3JltH_MqyYeJsyMsah3a3rlu6LoEAHNqF4jk8RUxZKjlVJz_iW-tbJ_GoGYrTp_bgvw6IBpgZvJDPGveaA6ms20CoN4zpXaL2ucgtaRbasXfD-z4NDwYN5_9TRPNcf9cuxViZ28CJ66uWxK8BEKysWnABYkh239Q71K2t41hFvQV8ti5l1UKcuqFf_lUGo0wYo9F-MGK6RKA"
});

View file

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@ -19,7 +15,5 @@
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
"include": ["src"]
}