From 4e0c90ee5e09833dddb3dce3ede15c5faeae9b16 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 19 Jul 2020 12:06:15 +0200 Subject: [PATCH 1/7] notistack added --- package-lock.json | 11 ++++++++++- package.json | 1 + src/App.jsx | 31 +++++++++++++++++-------------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8165d0c..056dce4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vrt-frontend", - "version": "1.4.0", + "version": "1.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9463,6 +9463,15 @@ "sort-keys": "^1.0.0" } }, + "notistack": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-0.9.17.tgz", + "integrity": "sha512-nypTN6sEe+q98wMaxF/UwatA1yAq948+bZOo9JKYR+tU65DW0ipWyx8DseJ3UJYvb6VDD+Fqo83qwayQ46bEEA==", + "requires": { + "clsx": "^1.1.0", + "hoist-non-react-statics": "^3.3.0" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", diff --git a/package.json b/package.json index 584584ea..f81fc370 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@testing-library/user-event": "^7.2.1", "konva": "^4.2.2", "material-ui-popup-state": "^1.6.1", + "notistack": "^0.9.17", "qs": "^6.9.4", "react": "^16.13.1", "react-debounce-input": "^3.2.2", diff --git a/src/App.jsx b/src/App.jsx index 00482241..a5c1b08d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,5 @@ import React from "react"; +import { SnackbarProvider } from 'notistack'; import "./App.css"; import Header from "./components/Header"; import { @@ -12,20 +13,22 @@ import { SocketProvider } from "./contexts/socket.context"; function App() { return ( -
- - - - - -
- - - - - - -
+ +
+ + + + + +
+ + + + + + +
+
); } From a263182cd60ba744c8f6feebc03faa8861d1fda4 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 19 Jul 2020 12:07:06 +0200 Subject: [PATCH 2/7] user relater notifications are added --- src/components/LoginForm.tsx | 8 +++++- src/components/RegisterForm.tsx | 9 +++++- src/contexts/auth.context.tsx | 44 ++++++++++++----------------- src/pages/ProfilePage.tsx | 49 ++++++++++++++++++++++----------- src/services/users.service.ts | 2 +- 5 files changed, 67 insertions(+), 45 deletions(-) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index b1c41a85..be94d224 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -11,15 +11,21 @@ import { } from "@material-ui/core"; import { useAuthDispatch, login } from "../contexts"; import { routes } from "../constants"; +import { useSnackbar } from "notistack"; const LoginForm = () => { + const { enqueueSnackbar } = useSnackbar(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const dispatch = useAuthDispatch(); const handleSubmit = (event: FormEvent) => { event.preventDefault(); - login(dispatch, email, password); + login(dispatch, email, password).catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); }; return ( diff --git a/src/components/RegisterForm.tsx b/src/components/RegisterForm.tsx index 50830347..99cb3f77 100644 --- a/src/components/RegisterForm.tsx +++ b/src/components/RegisterForm.tsx @@ -9,8 +9,10 @@ import { } from "@material-ui/core"; import { useAuthDispatch, login } from "../contexts"; import { usersService } from "../services"; +import { useSnackbar } from "notistack"; const RegisterForm = () => { + const { enqueueSnackbar } = useSnackbar(); const [email, setEmail] = useState(""); const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); @@ -21,7 +23,12 @@ const RegisterForm = () => { event.preventDefault(); usersService .register(firstName, lastName, email, password) - .then(() => login(dispatch, email, password)); + .then(() => login(dispatch, email, password)) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); }; return ( diff --git a/src/contexts/auth.context.tsx b/src/contexts/auth.context.tsx index 89ecf439..ba6e264d 100644 --- a/src/contexts/auth.context.tsx +++ b/src/contexts/auth.context.tsx @@ -2,11 +2,6 @@ import * as React from "react"; import { User } from "../types"; import { usersService } from "../services"; -interface IRequestAction { - type: "request"; - payload?: undefined; -} - interface ISuccessAction { type: "success"; payload: User; @@ -17,7 +12,7 @@ interface ILogoutAction { payload?: undefined; } -type IAction = IRequestAction | ISuccessAction | ILogoutAction; +type IAction = ISuccessAction | ILogoutAction; type Dispatch = (action: IAction) => void; type State = { loggedIn: boolean; user?: User }; @@ -36,9 +31,6 @@ const initialState: State = { function authReducer(state: State, action: IAction): State { switch (action.type) { - case "request": { - return { loggedIn: false, user: action.payload }; - } case "success": return { loggedIn: true, @@ -82,27 +74,27 @@ function useAuthDispatch() { } async function login(dispatch: Dispatch, email: string, password: string) { - dispatch({ type: "request" }); - - usersService - .login(email, password) - .then((user) => { - dispatch({ type: "success", payload: user }); - }) - .catch((error) => { - console.log(error.toString()); - }); + return usersService.login(email, password).then((user) => { + dispatch({ type: "success", payload: user }); + }); } -async function update(dispatch: Dispatch, user: User) { - dispatch({ type: "request" }); - - usersService.update(user) +async function update( + dispatch: Dispatch, + { + firstName, + lastName, + email, + }: { firstName: string; lastName: string; email: string } +) { + return usersService + .update({ + firstName, + lastName, + email, + }) .then((user) => { dispatch({ type: "success", payload: user }); - }) - .catch((error) => { - console.log(error.toString()); }); } diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 02868f50..b47e96f7 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -8,14 +8,12 @@ import { Button, Typography, } from "@material-ui/core"; -import { - useAuthState, - useAuthDispatch, - update, -} from "../contexts"; +import { useAuthState, useAuthDispatch, update } from "../contexts"; import { usersService } from "../services"; +import { useSnackbar } from "notistack"; const ProfilePage = () => { + const { enqueueSnackbar } = useSnackbar(); const { user } = useAuthState(); const dispatch = useAuthDispatch(); const [email, setEmail] = useState(user?.email); @@ -26,23 +24,42 @@ const ProfilePage = () => { const handleUserUpdateSubmit = (event: FormEvent) => { event.preventDefault(); if (user && firstName && lastName && email) { - usersService - .update({ - id: user.id, - firstName, - lastName, - email, - }) - .then((user) => update(dispatch, user)); + update(dispatch, { + firstName, + lastName, + email, + }) + .then(() => + enqueueSnackbar("User updated", { + variant: "success", + }) + ) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); } }; const handlePasswordUpdateSubmit = (event: FormEvent) => { event.preventDefault(); if (user && password) { - usersService.changePassword(password).then((isChanged) => { - setPassword(""); - }); + usersService + .changePassword(password) + .then((isChanged) => { + setPassword(""); + }) + .then(() => + enqueueSnackbar("Password updated", { + variant: "success", + }) + ) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); } }; diff --git a/src/services/users.service.ts b/src/services/users.service.ts index d9bc5657..d8ae61d6 100644 --- a/src/services/users.service.ts +++ b/src/services/users.service.ts @@ -42,7 +42,7 @@ function register(firstName: string, lastName: string, email: string, password: }); } -function update({ id, firstName, lastName, email }: { id: string, firstName: string, lastName: string, email: string }): Promise { +function update({ firstName, lastName, email }: { firstName: string, lastName: string, email: string }): Promise { const requestOptions = { method: "PUT", headers: { "Content-Type": "application/json", ...authHeader() }, From 9c9b399eb517145e585140efd6cae85897688014 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 19 Jul 2020 14:21:12 +0200 Subject: [PATCH 3/7] project related events are handeled --- src/contexts/project.context.tsx | 57 +++++++++----------------------- src/pages/ProjectListPage.tsx | 52 ++++++++++++++++++++++++----- src/services/projects.service.ts | 2 +- 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/contexts/project.context.tsx b/src/contexts/project.context.tsx index 1f6fe568..83a0ebd8 100644 --- a/src/contexts/project.context.tsx +++ b/src/contexts/project.context.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import { Project } from "../types"; import { projectsService } from "../services"; -import { useAuthState } from "./auth.context"; interface IRequestAction { type: "request"; @@ -83,13 +82,6 @@ function projectReducer(state: State, action: IAction): State { function ProjectProvider({ children }: ProjectProviderProps) { const [state, dispatch] = React.useReducer(projectReducer, initialState); - const { loggedIn } = useAuthState(); - - React.useEffect(() => { - if (loggedIn) { - getProjectList(dispatch); - } - }, [loggedIn]); return ( @@ -119,14 +111,9 @@ function useProjectDispatch() { async function getProjectList(dispatch: Dispatch) { dispatch({ type: "request" }); - projectsService - .getAll() - .then((projects) => { - dispatch({ type: "get", payload: projects }); - }) - .catch((error) => { - console.log(error.toString()); - }); + return projectsService.getAll().then((projects) => { + dispatch({ type: "get", payload: projects }); + }); } async function createProject( @@ -135,14 +122,10 @@ async function createProject( ) { dispatch({ type: "request" }); - projectsService - .create(project) - .then((project: Project) => { - dispatch({ type: "create", payload: project }); - }) - .catch((error) => { - console.log(error.toString()); - }); + return projectsService.create(project).then((project: Project) => { + dispatch({ type: "create", payload: project }); + return project; + }); } async function updateProject( @@ -151,29 +134,19 @@ async function updateProject( ) { dispatch({ type: "request" }); - projectsService - .update(project) - .then((project: Project) => { - dispatch({ type: "update", payload: project }); - }) - .catch((error) => { - console.log(error.toString()); - }); + return projectsService.update(project).then((project: Project) => { + dispatch({ type: "update", payload: project }); + return project; + }); } async function deleteProject(dispatch: Dispatch, id: string) { dispatch({ type: "request" }); - projectsService - .remove(id) - .then((isDeleted) => { - if (isDeleted) { - dispatch({ type: "delete", payload: id }); - } - }) - .catch((error) => { - console.log(error.toString()); - }); + return projectsService.remove(id).then((project) => { + dispatch({ type: "delete", payload: id }); + return project; + }); } export { diff --git a/src/pages/ProjectListPage.tsx b/src/pages/ProjectListPage.tsx index b26a23fa..b364a3b2 100644 --- a/src/pages/ProjectListPage.tsx +++ b/src/pages/ProjectListPage.tsx @@ -25,9 +25,11 @@ import { Delete, Add, Edit } from "@material-ui/icons"; import { routes } from "../constants"; import { formatDateTime } from "../_helpers/format.helper"; import { ProjectModal } from "../components/ProjectModal"; +import { useSnackbar } from "notistack"; const ProjectsListPage = () => { const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); const projectState = useProjectState(); const projectDispatch = useProjectDispatch(); @@ -44,8 +46,12 @@ const ProjectsListPage = () => { }); useEffect(() => { - getProjectList(projectDispatch); - }, [projectDispatch]); + getProjectList(projectDispatch).catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); + }, [projectDispatch, enqueueSnackbar]); const toggleCreateDialogOpen = () => { setCreateDialogOpen(!createDialogOpen); @@ -92,9 +98,18 @@ const ProjectsListPage = () => { onCancel={toggleCreateDialogOpen} projectState={[project, setProject]} onSubmit={() => - createProject(projectDispatch, project).then((project) => { - toggleCreateDialogOpen(); - }) + createProject(projectDispatch, project) + .then((project) => { + toggleCreateDialogOpen(); + enqueueSnackbar(`${project.name} created`, { + variant: "success", + }); + }) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ) } /> @@ -105,9 +120,18 @@ const ProjectsListPage = () => { onCancel={toggleUpdateDialogOpen} projectState={[project, setProject]} onSubmit={() => - updateProject(projectDispatch, project).then((project) => { - toggleUpdateDialogOpen(); - }) + updateProject(projectDispatch, project) + .then((project) => { + toggleUpdateDialogOpen(); + enqueueSnackbar(`${project.name} updated`, { + variant: "success", + }); + }) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ) } /> @@ -143,7 +167,17 @@ const ProjectsListPage = () => { ) => { - deleteProject(projectDispatch, project.id); + deleteProject(projectDispatch, project.id) + .then((project) => { + enqueueSnackbar(`${project.name} deleted`, { + variant: "success", + }); + }) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); }} > diff --git a/src/services/projects.service.ts b/src/services/projects.service.ts index f4446849..7f3c2b26 100644 --- a/src/services/projects.service.ts +++ b/src/services/projects.service.ts @@ -18,7 +18,7 @@ function getAll(): Promise { return fetch(`${API_URL}/projects`, requestOptions).then(handleResponse); } -function remove(id: string): Promise { +function remove(id: string): Promise { const requestOptions = { method: "DELETE", headers: authHeader(), From 6d46f8d197cf906fc65ad634d0f004c11aa6567c Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 19 Jul 2020 15:11:31 +0200 Subject: [PATCH 4/7] build releated notifications handeled --- src/components/BuildList.tsx | 14 +++++++++++++- src/contexts/build.context.tsx | 27 ++++++++------------------- src/pages/ProjectPage.tsx | 10 ++++++++-- src/services/builds.service.ts | 2 +- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/components/BuildList.tsx b/src/components/BuildList.tsx index be953f5c..04186446 100644 --- a/src/components/BuildList.tsx +++ b/src/components/BuildList.tsx @@ -23,6 +23,7 @@ import { import { BuildStatusChip } from "./BuildStatusChip"; import { SkeletonList } from "./SkeletonList"; import { formatDateTime } from "../_helpers/format.helper"; +import { useSnackbar } from "notistack"; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -42,6 +43,7 @@ const BuildList: FunctionComponent = () => { const history = useHistory(); const { buildList, selectedBuildId, loading } = useBuildState(); const buildDispatch = useBuildDispatch(); + const { enqueueSnackbar } = useSnackbar(); return ( @@ -98,7 +100,17 @@ const BuildList: FunctionComponent = () => { > { - deleteBuild(buildDispatch, build.id); + deleteBuild(buildDispatch, build.id) + .then((b) => + enqueueSnackbar(`${b.id} removed`, { + variant: "success", + }) + ) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); }} > diff --git a/src/contexts/build.context.tsx b/src/contexts/build.context.tsx index 0f2aede6..a079785f 100644 --- a/src/contexts/build.context.tsx +++ b/src/contexts/build.context.tsx @@ -93,7 +93,7 @@ function buildReducer(state: State, action: IAction): State { ...state, buildList: state.buildList.map((p) => { if (p.id === action.payload.id) { - return action.payload + return action.payload; } return p; }), @@ -134,27 +134,16 @@ function useBuildDispatch() { async function getBuildList(dispatch: Dispatch, id: string) { dispatch({ type: "request" }); - buildsService - .getList(id) - .then((items) => { - dispatch({ type: "get", payload: items }); - }) - .catch((error) => { - console.log(error.toString()); - }); + return buildsService.getList(id).then((items) => { + dispatch({ type: "get", payload: items }); + }); } async function deleteBuild(dispatch: Dispatch, id: string) { - buildsService - .remove(id) - .then((isDeleted) => { - if (isDeleted) { - dispatch({ type: "delete", payload: id }); - } - }) - .catch((error) => { - console.log(error.toString()); - }); + return buildsService.remove(id).then((build) => { + dispatch({ type: "delete", payload: id }); + return build; + }); } async function selectBuild(dispatch: Dispatch, id: string) { diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index 7d627aaa..e650841c 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -27,6 +27,7 @@ import { selectTestRun, getTestRunList, } from "../contexts"; +import { useSnackbar } from "notistack"; const getQueryParams = (guery: string) => { const queryParams = qs.parse(guery, { ignoreQueryPrefix: true }); @@ -67,6 +68,7 @@ const ProjectPage = () => { const { projectId } = useParams(); const location = useLocation(); const history = useHistory(); + const { enqueueSnackbar } = useSnackbar(); const { buildList, selectedBuildId } = useBuildState(); const buildDispatch = useBuildDispatch(); const { @@ -87,9 +89,13 @@ const ProjectPage = () => { useEffect(() => { if (projectId) { - getBuildList(buildDispatch, projectId); + getBuildList(buildDispatch, projectId).catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); } - }, [projectId, buildDispatch]); + }, [projectId, buildDispatch, enqueueSnackbar]); useEffect(() => { if (selectedBuildId) { diff --git a/src/services/builds.service.ts b/src/services/builds.service.ts index 801e96d2..f1ebdae1 100644 --- a/src/services/builds.service.ts +++ b/src/services/builds.service.ts @@ -20,7 +20,7 @@ function getList(projectId: string): Promise { ); } -function remove(id: string): Promise { +function remove(id: string): Promise { const requestOptions = { method: "DELETE", headers: authHeader(), From cbd0969326c580233db36b0ec0837657f860e8bc Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 19 Jul 2020 15:48:18 +0200 Subject: [PATCH 5/7] testRun related notifications handled --- src/components/TestDetailsModal.tsx | 50 +++++++++++++++++++++++++++-- src/components/TestRunList.tsx | 14 +++++++- src/contexts/testRun.context.tsx | 27 ++++++---------- src/pages/ProjectPage.tsx | 8 +++-- src/services/testRun.service.ts | 14 ++++---- 5 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/components/TestDetailsModal.tsx b/src/components/TestDetailsModal.tsx index 380a0f10..77495249 100644 --- a/src/components/TestDetailsModal.tsx +++ b/src/components/TestDetailsModal.tsx @@ -40,6 +40,7 @@ import { routes } from "../constants"; import { useTestRunDispatch, updateTestRun, selectTestRun } from "../contexts"; import { DrawArea } from "./DrawArea"; import { CommentsPopper } from "./CommentsPopper"; +import { useSnackbar } from "notistack"; const useStyles = makeStyles((theme) => ({ imageContainer: { @@ -57,6 +58,7 @@ const TestDetailsModal: React.FunctionComponent<{ }> = ({ testRun }) => { const classes = useStyles(); const history = useHistory(); + const { enqueueSnackbar } = useSnackbar(); const testRunDispatch = useTestRunDispatch(); const stageWidth = (window.innerWidth / 2) * 0.9; @@ -177,6 +179,16 @@ const TestDetailsModal: React.FunctionComponent<{ .then((testRun) => { updateTestRun(testRunDispatch, testRun); }) + .then(() => + enqueueSnackbar("Approved", { + variant: "success", + }) + ) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ) } > Approve @@ -186,9 +198,21 @@ const TestDetailsModal: React.FunctionComponent<{