diff --git a/applications/webapp/src/Components/Button.test.tsx b/applications/webapp/src/Components/Button.test.tsx deleted file mode 100644 index 79c526a2..00000000 --- a/applications/webapp/src/Components/Button.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Humanitech Supply Trail - * - * Copyright (c) Humanitech, Peter Rogov and Contributors - * - * Website: https://humanitech.net - * Repository: https://github.com/humanitech-net/supply-trail - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from "react"; -import { - cleanup, - render, - fireEvent, - waitFor, - screen, -} from "@testing-library/react"; -import GraphQlButton, { Connection } from "./Button"; -import { MockedProvider } from "@apollo/client/testing"; -import { GraphQLError } from "graphql"; - -const mockData = { - findAll: { - id: 1, - firstName: "Test", - }, -}; - -const successMock = { - request: { - query: Connection, - }, - result: { - data: mockData, - }, -}; - -const errorMock = { - request: { - query: Connection, - }, - error: new GraphQLError("the fetch was unsuccessful"), -}; - -describe("GraphQlButton", () => { - afterEach(() => { - cleanup(); - }); - - it("should render data when fetched successfully", async () => { - render( - - - , - ); - - fireEvent.click(screen.getByText("Click Me")); - - await waitFor(() => { - expect(screen.getByText("ID: 1")).toBeInTheDocument; - expect(screen.getByText("First Name: Test")).toBeInTheDocument; - }); - }); - - it("should render error message when fetch fails", async () => { - render( - - - , - ); - - fireEvent.click(screen.getByText("Click Me")); - - await waitFor(() => { - expect(screen.getByText("Error: the fetch was unsuccessful")) - .toBeInTheDocument; - }); - }); -}); diff --git a/applications/webapp/src/Components/Button.tsx b/applications/webapp/src/Components/Button.tsx deleted file mode 100644 index 35b09412..00000000 --- a/applications/webapp/src/Components/Button.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Humanitech Supply Trail - * - * Copyright (c) Humanitech, Peter Rogov and Contributors - * - * Website: https://humanitech.net - * Repository: https://github.com/humanitech-net/supply-trail - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, { useState } from "react"; -import { useQuery, gql } from "@apollo/client"; -import { Button } from "@mui/material"; - -export const Connection = gql` - query { - findAll { - id - firstName - } - } -`; - -const GraphQlButton: React.FC = () => { - const [isDataFetched, setIsDataFetched] = useState(false); - - const { error, data, refetch } = useQuery(Connection, { - skip: !isDataFetched, - }); - - const handleButtonClick = () => { - setIsDataFetched(true); - refetch(); - }; - - if (error) { - return ( -
- -
Error: {error.message}
-
- ); - } - - return ( -
- - {isDataFetched && ( -
- - -
- )} -
- ); -}; - -export default GraphQlButton; diff --git a/applications/webapp/src/Components/leftDrawer.tsx b/applications/webapp/src/Components/leftDrawer.tsx deleted file mode 100644 index f5890d1d..00000000 --- a/applications/webapp/src/Components/leftDrawer.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Humanitech Supply Trail - * - * Copyright (c) Humanitech, Peter Rogov and Contributors - * - * Website: https://humanitech.net - * Repository: https://github.com/humanitech-net/supply-trail - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from "react"; -import { styled, useTheme } from "@mui/material/styles"; -import Drawer from "@mui/material/Drawer"; -import List from "@mui/material/List"; -import IconButton from "@mui/material/IconButton"; -import ListItem from "@mui/material/ListItem"; -import ListItemButton from "@mui/material/ListItemButton"; -import ListItemText from "@mui/material/ListItemText"; -import MenuIcon from "@mui/icons-material/Menu"; -import useMediaQuery from "@mui/material/useMediaQuery"; - -interface DrawerProps { - open: boolean; - closeDrawer: () => void; -} - -export default function LeftDrawer({ - open, - closeDrawer, -}: Readonly) { - const drawerWidth = 240; - const theme = useTheme(); - const isDesktop = useMediaQuery(theme.breakpoints.up("md")); - const menu = [ - "Supply", - "Tracking", - "Inventory", - "Logistics", - "Schools", - "Analytics", - "Contact List", - ]; - - const DrawerHeader = styled("div")(({ theme }) => ({ - display: "flex", - alignItems: "center", - padding: theme.spacing(0, 1), - ...theme.mixins.toolbar, - justifyContent: "flex-start", - paddingLeft: 22, - backgroundColor: "#011C27", - })); - - return ( - - - - - - - - {menu.map((text) => ( - - - - - - ))} - - - ); -} diff --git a/applications/webapp/src/Pages/Home/ContextProvider/drawerProvider.tsx b/applications/webapp/src/Pages/Home/ContextProvider/drawerProvider.tsx new file mode 100644 index 00000000..8cb43825 --- /dev/null +++ b/applications/webapp/src/Pages/Home/ContextProvider/drawerProvider.tsx @@ -0,0 +1,37 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { ReactNode, createContext, useState, useMemo } from "react"; + +interface DrawerContextProps { + open: boolean; + setOpen: (open: boolean) => void; +} + +interface DrawerProviderProps { + children: ReactNode; +} + +export const DrawerContext = createContext({ + open: false, + setOpen: () => {}, +}); + +export const DrawerProvider: React.FC = ({ children }) => { + const [open, setOpen] = useState(false); + + const props = useMemo(() => ({ open, setOpen }), [open, setOpen]); + + return ( + {children} + ); +}; diff --git a/applications/webapp/src/Components/footer.tsx b/applications/webapp/src/Pages/Home/Footer/footer.tsx similarity index 100% rename from applications/webapp/src/Components/footer.tsx rename to applications/webapp/src/Pages/Home/Footer/footer.tsx diff --git a/applications/webapp/src/Components/test/footer.test.tsx b/applications/webapp/src/Pages/Home/Footer/tests/footer.test.tsx similarity index 100% rename from applications/webapp/src/Components/test/footer.test.tsx rename to applications/webapp/src/Pages/Home/Footer/tests/footer.test.tsx diff --git a/applications/webapp/src/Pages/Home/LeftDrawer/Components/drawerHeader.tsx b/applications/webapp/src/Pages/Home/LeftDrawer/Components/drawerHeader.tsx new file mode 100644 index 00000000..3bee4700 --- /dev/null +++ b/applications/webapp/src/Pages/Home/LeftDrawer/Components/drawerHeader.tsx @@ -0,0 +1,49 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useContext } from "react"; +import { IconButton, styled } from "@mui/material"; + +import MenuIcon from "@mui/icons-material/Menu"; +import { DrawerContext } from "../../ContextProvider/drawerProvider"; + +export default function DrawerHeader() { + const { open, setOpen } = useContext(DrawerContext); + + const closeDrawer = React.useCallback(() => { + setOpen(false); + }, [open]); + + const Header = styled("div")(({ theme }) => ({ + display: "flex", + alignItems: "center", + padding: theme.spacing(0, 1), + ...theme.mixins.toolbar, + justifyContent: "flex-start", + paddingLeft: 22, + backgroundColor: "#011C27", + })); + + return ( +
+ + + +
+ ); +} diff --git a/applications/webapp/src/Pages/Home/LeftDrawer/Components/drawerMenu.tsx b/applications/webapp/src/Pages/Home/LeftDrawer/Components/drawerMenu.tsx new file mode 100644 index 00000000..27e71521 --- /dev/null +++ b/applications/webapp/src/Pages/Home/LeftDrawer/Components/drawerMenu.tsx @@ -0,0 +1,41 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from "react"; +import { List, ListItem, ListItemButton, ListItemText } from "@mui/material"; +import { drawerMenu } from "../util/constants"; + +export default function DrawerMenu() { + return ( + + {drawerMenu.map((text) => ( + + + + + + ))} + + ); +} diff --git a/applications/webapp/src/Pages/Home/LeftDrawer/leftDrawer.tsx b/applications/webapp/src/Pages/Home/LeftDrawer/leftDrawer.tsx new file mode 100644 index 00000000..2986751b --- /dev/null +++ b/applications/webapp/src/Pages/Home/LeftDrawer/leftDrawer.tsx @@ -0,0 +1,41 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useContext } from "react"; +import { useTheme } from "@mui/material/styles"; +import Drawer from "@mui/material/Drawer"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import DrawerHeader from "./Components/drawerHeader"; +import DrawerMenu from "./Components/drawerMenu"; +import { styles } from "./util/style"; +import { DrawerContext } from "../ContextProvider/drawerProvider"; + +export default function LeftDrawer() { + const theme = useTheme(); + const style = styles(); + + const isDesktop = useMediaQuery(theme.breakpoints.up("md")); + + const { open } = useContext(DrawerContext); + + return ( + + + + + ); +} diff --git a/applications/webapp/src/Components/test/drawer.test.tsx b/applications/webapp/src/Pages/Home/LeftDrawer/tests/drawer.test.tsx similarity index 82% rename from applications/webapp/src/Components/test/drawer.test.tsx rename to applications/webapp/src/Pages/Home/LeftDrawer/tests/drawer.test.tsx index b57d1cef..baee6e6c 100644 --- a/applications/webapp/src/Components/test/drawer.test.tsx +++ b/applications/webapp/src/Pages/Home/LeftDrawer/tests/drawer.test.tsx @@ -16,7 +16,6 @@ import LeftDrawer from "../leftDrawer"; describe("LeftDrawer", () => { test("renders LeftDrawer component", () => { - const closeDrawerMock = jest.fn(); - render(); + render(); }); }); diff --git a/applications/webapp/src/Pages/Home/LeftDrawer/util/constants.ts b/applications/webapp/src/Pages/Home/LeftDrawer/util/constants.ts new file mode 100644 index 00000000..382f0b5b --- /dev/null +++ b/applications/webapp/src/Pages/Home/LeftDrawer/util/constants.ts @@ -0,0 +1,21 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const drawerMenu = [ + "Supply", + "Tracking", + "Inventory", + "Logistics", + "Schools", + "Analytics", + "Contact List", +]; diff --git a/applications/webapp/src/Pages/Home/LeftDrawer/util/style.ts b/applications/webapp/src/Pages/Home/LeftDrawer/util/style.ts new file mode 100644 index 00000000..6d7e10a7 --- /dev/null +++ b/applications/webapp/src/Pages/Home/LeftDrawer/util/style.ts @@ -0,0 +1,23 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const styles = () => ({ + drawer: { + width: 240, + flexShrink: 0, + "& .MuiDrawer-paper": { + width: 240, + boxSizing: "border-box", + backgroundColor: "#011C27", + }, + }, +}); diff --git a/applications/webapp/src/Components/mainContent.tsx b/applications/webapp/src/Pages/Home/MainContent/mainContent.tsx similarity index 51% rename from applications/webapp/src/Components/mainContent.tsx rename to applications/webapp/src/Pages/Home/MainContent/mainContent.tsx index 1dd052ee..a73ba3a8 100644 --- a/applications/webapp/src/Components/mainContent.tsx +++ b/applications/webapp/src/Pages/Home/MainContent/mainContent.tsx @@ -10,46 +10,33 @@ * LICENSE file in the root directory of this source tree. */ -import React from "react"; -import { styled, useTheme } from "@mui/material/styles"; -import useMediaQuery from "@mui/material/useMediaQuery"; +import React, { useContext } from "react"; +import { styled } from "@mui/material/styles"; import { Routes, Route } from "react-router-dom"; -import UserPage from "../Pages/User/userPage"; +import UserPage from "../../User/userPage"; +import { DrawerContext } from "src/Pages/Home/ContextProvider/drawerProvider"; +import { useMediaQuery, useTheme } from "@mui/material"; -interface HandleDrawer { - open: boolean; -} - -export default function MainContent({ open }: Readonly) { - const drawerWidth = 240; +export default function MainContent() { + const { open } = useContext(DrawerContext); const theme = useTheme(); const isDesktop = useMediaQuery(theme.breakpoints.up("md")); - const Main = styled("main", { - shouldForwardProp: (prop) => prop !== "open", - })<{ - open?: boolean; - }>(({ theme }) => ({ + const leftMargin = 240; + + const Main = styled("main")(({ theme }) => ({ + marginLeft: open && isDesktop ? leftMargin : 0, marginTop: 80, flexGrow: 1, transition: theme.transitions.create("margin", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), - - ...(isDesktop && { - ...(open && { - marginLeft: drawerWidth, - transition: theme.transitions.create("margin", { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - }), - }), })); + return ( -
+
} /> diff --git a/applications/webapp/src/Components/test/mainContent.test.tsx b/applications/webapp/src/Pages/Home/MainContent/tests/mainContent.test.tsx similarity index 94% rename from applications/webapp/src/Components/test/mainContent.test.tsx rename to applications/webapp/src/Pages/Home/MainContent/tests/mainContent.test.tsx index 74d0d0e8..10b81a9d 100644 --- a/applications/webapp/src/Components/test/mainContent.test.tsx +++ b/applications/webapp/src/Pages/Home/MainContent/tests/mainContent.test.tsx @@ -19,7 +19,7 @@ describe("MainContent", () => { test("renders MainContent component", () => { render( - + , ); }); diff --git a/applications/webapp/src/Components/navbar.tsx b/applications/webapp/src/Pages/Home/NavBar/navbar.tsx similarity index 91% rename from applications/webapp/src/Components/navbar.tsx rename to applications/webapp/src/Pages/Home/NavBar/navbar.tsx index 14f60f97..6f31d2d9 100644 --- a/applications/webapp/src/Components/navbar.tsx +++ b/applications/webapp/src/Pages/Home/NavBar/navbar.tsx @@ -10,7 +10,7 @@ * LICENSE file in the root directory of this source tree. */ -import React from "react"; +import React, { useContext } from "react"; import { Link } from "react-router-dom"; import Box from "@mui/material/Box"; import { styled } from "@mui/material/styles"; @@ -22,14 +22,10 @@ import MenuIcon from "@mui/icons-material/Menu"; import AccountCircle from "@mui/icons-material/AccountCircle"; import Brightness4Icon from "@mui/icons-material/Brightness4"; import NotificationsIcon from "@mui/icons-material/Notifications"; +import { DrawerContext } from "src/Pages/Home/ContextProvider/drawerProvider"; const drawerWidth = 240; -interface HandleDrawer { - open: boolean; - openDrawer: () => void; -} - interface AppBarProps extends MuiAppBarProps { open?: boolean; } @@ -51,7 +47,11 @@ const AppBar = styled(MuiAppBar, { }), })); -export default function NavBar({ open, openDrawer }: Readonly) { +export default function NavBar() { + const { open, setOpen } = useContext(DrawerContext); + const openDrawer = React.useCallback(() => { + setOpen(true); + }, [open]); return ( { test("renders NavBar component", () => { - const openDrawerMock = jest.fn(); - render( - + , ); }); diff --git a/applications/webapp/src/Pages/Home/test/home.test.tsx b/applications/webapp/src/Pages/Home/home.test.tsx similarity index 87% rename from applications/webapp/src/Pages/Home/test/home.test.tsx rename to applications/webapp/src/Pages/Home/home.test.tsx index 3d2fa8c7..6e7b38d0 100644 --- a/applications/webapp/src/Pages/Home/test/home.test.tsx +++ b/applications/webapp/src/Pages/Home/home.test.tsx @@ -13,9 +13,10 @@ import React from "react"; import { render, fireEvent } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; -import Home from "../home"; +import Home from "./home"; describe("Home", () => { + const openDrawerLabel = "open drawer"; test("renders navbar", () => { const { getByLabelText } = render( @@ -32,7 +33,7 @@ describe("Home", () => { , ); - const OpenDrawerButton = getByLabelText("open drawer"); + const OpenDrawerButton = getByLabelText(openDrawerLabel); fireEvent.click(OpenDrawerButton); const Drawer = getByLabelText("drawer"); @@ -46,7 +47,7 @@ describe("Home", () => { , ); - const OpenDrawerButton = getByLabelText("open drawer"); + const OpenDrawerButton = getByLabelText(openDrawerLabel); fireEvent.click(OpenDrawerButton); const CloseDrawerButton = getByLabelText("close drawer"); diff --git a/applications/webapp/src/Pages/Home/home.tsx b/applications/webapp/src/Pages/Home/home.tsx index a248ba50..66226b2e 100644 --- a/applications/webapp/src/Pages/Home/home.tsx +++ b/applications/webapp/src/Pages/Home/home.tsx @@ -13,27 +13,20 @@ import React from "react"; import Box from "@mui/material/Box"; import CssBaseline from "@mui/material/CssBaseline"; -import NavBar from "../../Components/navbar"; -import LeftDrawer from "../../Components/leftDrawer"; -import MainContent from "../../Components/mainContent"; +import NavBar from "./NavBar/navbar"; +import LeftDrawer from "./LeftDrawer/leftDrawer"; +import MainContent from "./MainContent/mainContent"; +import { DrawerProvider } from "./ContextProvider/drawerProvider"; export default function Home() { - const [open, setOpen] = React.useState(false); - - function openDrawer() { - setOpen(true); - } - - function closeDrawer() { - setOpen(false); - } - return ( - - - - - - + + + + + + + + ); } diff --git a/applications/webapp/src/Pages/User/components/ContextProvider/UserPageContextProvider.tsx b/applications/webapp/src/Pages/User/components/ContextProvider/UserPageContextProvider.tsx index 004e4de0..7535511b 100644 --- a/applications/webapp/src/Pages/User/components/ContextProvider/UserPageContextProvider.tsx +++ b/applications/webapp/src/Pages/User/components/ContextProvider/UserPageContextProvider.tsx @@ -10,22 +10,42 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useMemo } from "react"; +import React, { useMemo, useState } from "react"; import { useCurrentUserData } from "../../../../hooks/useCurrentUserData"; import { UserContext } from "../../context"; +import useUserEntry from "../../../../hooks/useUserEntry"; export function UserPageContextProvider({ children, }: Readonly<{ children: React.ReactNode; }>) { - const { user, loading, error } = useCurrentUserData(); + const { + user: initialUser, + loading: queryLoading, + error: queryError, + } = useCurrentUserData(); + + const { + update, + user: updatedUser, + loading: mutationLoading, + error: mutationError, + } = useUserEntry(); + + const [userUpdated, setUserUpdated] = useState(false); + + const user = userUpdated ? updatedUser : initialUser; + const loading = userUpdated ? mutationLoading : queryLoading; + const error = userUpdated ? mutationError : queryError; const userpagecontext = useMemo( () => ({ user, loading, error, + setUserUpdated, + update, }), [user, loading, error], ); diff --git a/applications/webapp/src/Pages/User/components/detailHolder.tsx b/applications/webapp/src/Pages/User/components/detailHolder.tsx index 662ef89c..f1b773e7 100644 --- a/applications/webapp/src/Pages/User/components/detailHolder.tsx +++ b/applications/webapp/src/Pages/User/components/detailHolder.tsx @@ -11,144 +11,80 @@ */ import React, { useState } from "react"; -import { - Card, - CardContent, - FormControl, - Grid, - useTheme, - TextField, - Button, - Box, -} from "@mui/material"; +import { Card, CardContent, Grid, useTheme, Button, Box } from "@mui/material"; import { styles } from "../util/style"; -import { useMutation } from "@apollo/client"; -import { EditUserMutation } from "../../../hooks/mutation"; import { useCardContext, useUserContext } from "../context"; import { Link } from "react-router-dom"; -import { CHANGE_PASSWORD_URL } from "../util/constants"; +import { CHANGE_PASSWORD_URL, fields } from "../util/constants"; +import { UserDetailGridItem } from "./userDetailHolderGridItem"; +import { User } from "../../interface"; export default function DetailHolder() { const theme = useTheme(); const style = styles(theme).detailHolder; + const boxStyle = styles(theme).userPage; + + const { user, update, setUserUpdated } = useUserContext(); + const { elevation } = useCardContext(); + const [updatedUser, setUpdatedUser] = useState(user); + const { editable, setEditable, setElevation } = useCardContext(); + + const newUserData = { + userInput: { + username: updatedUser.username, + firstName: updatedUser.firstName, + lastName: updatedUser.lastName, + }, + }; + + const handleFieldChange = React.useCallback( + (field: keyof User, value: string) => { + setUpdatedUser((prevUserData) => ({ + ...prevUserData, + [field]: value, + })); + }, + [setUpdatedUser], + ); - const { user } = useUserContext(); - const { firstName, lastName, email, address, birthdate, phoneNumber } = user; - - const card = useCardContext(); - const { editable, setEditable, setElevation } = card; - - const [firstname, setFirstname] = useState(firstName); - - const [lastname, setLastname] = useState(lastName); - - const [editUser] = useMutation(EditUserMutation); - - async function updateUser() { - try { - const { data } = await editUser({ - variables: { - firstname, - lastname, - }, - }); - console.log("User:", data.editUser); - } catch (error) { - console.error("Error:", error); - } - setEditable(!editable); - setElevation(0); - } - - function firstNameChanged( - event: React.ChangeEvent, - ) { - return setFirstname(event.target.value); - } - - function lastNameChanged( - event: React.ChangeEvent, - ) { - return setLastname(event.target.value); - } + const onClickUpdate = React.useCallback(async () => { + await update(newUserData).then(() => { + setEditable(!editable); + setElevation(0); + setUserUpdated(true); + }); + }, [newUserData]); return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + {fields.map((field, index) => ( + - + ))} - - {!card.editable && ( - - - - - - + + + + - - )} - - + )} + + + ); } diff --git a/applications/webapp/src/Pages/User/components/profileHolder.tsx b/applications/webapp/src/Pages/User/components/profileHolder.tsx index 44d393a9..87b2e7f2 100644 --- a/applications/webapp/src/Pages/User/components/profileHolder.tsx +++ b/applications/webapp/src/Pages/User/components/profileHolder.tsx @@ -28,6 +28,7 @@ import { useCardContext, useUserContext } from "../context"; export default function ProfileHolder() { const theme = useTheme(); const style = styles(theme).profileholder; + const boxStyle = styles(theme).userPage; const { user } = useUserContext(); const { username, description } = user; @@ -36,32 +37,34 @@ export default function ProfileHolder() { const { editUser } = card; return ( - - - - - - + + + + + + + + + + {username} + + + {description} + + + + - - {username} - - - {description} - - - - - - - - + + + + ); } diff --git a/applications/webapp/src/Pages/User/components/userDetailHolderGridItem.tsx b/applications/webapp/src/Pages/User/components/userDetailHolderGridItem.tsx new file mode 100644 index 00000000..8e127d88 --- /dev/null +++ b/applications/webapp/src/Pages/User/components/userDetailHolderGridItem.tsx @@ -0,0 +1,47 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from "react"; +import { Grid, TextField } from "@mui/material"; +import { User } from "../../../Pages/interface"; +import { useCardContext } from "../context"; +import { labels } from "../util/constants"; + +export function UserDetailGridItem( + props: Readonly<{ + user: User; + field: keyof User; + onChange: (field: keyof User, val: string) => void; + }>, +) { + const { user, field, onChange } = props; + const card = useCardContext(); + + const updateValue = React.useCallback( + (event: React.ChangeEvent) => { + onChange(field, event.target.value); + }, + [field], + ); + + return ( + + + + ); +} diff --git a/applications/webapp/src/Pages/User/context.tsx b/applications/webapp/src/Pages/User/context.tsx index a69d736c..8baa7788 100644 --- a/applications/webapp/src/Pages/User/context.tsx +++ b/applications/webapp/src/Pages/User/context.tsx @@ -12,15 +12,8 @@ import { createContext, useContext } from "react"; -import { Card, User } from "../interface"; -import { ApolloError } from "@apollo/client"; - -type UserContextType = { - user: User; - loading: boolean; - error: ApolloError | undefined; -}; - +import { Card } from "../interface"; +import { UserContextType } from "../../hooks/util/types"; export const UserContext = createContext( undefined, ); diff --git a/applications/webapp/src/Pages/User/test/profileHolder.test.tsx b/applications/webapp/src/Pages/User/test/profileHolder.test.tsx index dbdc9eee..f47b5c02 100644 --- a/applications/webapp/src/Pages/User/test/profileHolder.test.tsx +++ b/applications/webapp/src/Pages/User/test/profileHolder.test.tsx @@ -17,7 +17,7 @@ import ProfileHolder from "../components/profileHolder"; import { UserPageContextProvider } from "../components/ContextProvider/UserPageContextProvider"; import { CardContextProvider } from "../components/ContextProvider/CardContextProvider"; import { MockedProvider } from "@apollo/client/testing"; -import { GET_USER_QUERY } from "../../../hooks/query"; +import { GET_USER_QUERY } from "../../../hooks/util/query"; describe("ProfileHolder", () => { const mockData = { diff --git a/applications/webapp/src/Pages/User/test/userPage.test.tsx b/applications/webapp/src/Pages/User/test/userPage.test.tsx index f3ff9e8c..550559b2 100644 --- a/applications/webapp/src/Pages/User/test/userPage.test.tsx +++ b/applications/webapp/src/Pages/User/test/userPage.test.tsx @@ -14,7 +14,7 @@ import React from "react"; import { render, fireEvent, screen, waitFor } from "@testing-library/react"; import UserPage from "../userPage"; import { MockedProvider } from "@apollo/client/testing"; -import { GET_USER_QUERY } from "../../../hooks/query"; +import { GET_USER_QUERY } from "../../../hooks/util/query"; import { BrowserRouter } from "react-router-dom"; describe("UserPage", () => { diff --git a/applications/webapp/src/Pages/User/userPage.tsx b/applications/webapp/src/Pages/User/userPage.tsx index 49f8b56b..a12c40b2 100644 --- a/applications/webapp/src/Pages/User/userPage.tsx +++ b/applications/webapp/src/Pages/User/userPage.tsx @@ -15,32 +15,20 @@ import { Box, useTheme } from "@mui/material"; import ProfileHolder from "./components/profileHolder"; import DetailHolder from "./components/detailHolder"; import Overlay from "./components/overlay"; -import { styles } from "./util/style"; import { UserPageContextProvider } from "./components/ContextProvider/UserPageContextProvider"; import { CardContextProvider } from "./components/ContextProvider/CardContextProvider"; +import { styles } from "./util/style"; export default function UserPage() { const theme = useTheme(); const style = styles(theme).userPage; - return ( - - - - - - - - + + + diff --git a/applications/webapp/src/Pages/User/util/constants.ts b/applications/webapp/src/Pages/User/util/constants.ts index 0db1d5cc..0d567574 100644 --- a/applications/webapp/src/Pages/User/util/constants.ts +++ b/applications/webapp/src/Pages/User/util/constants.ts @@ -10,5 +10,25 @@ * LICENSE file in the root directory of this source tree. */ +import { User } from "../../../Pages/interface"; + export const CHANGE_PASSWORD_URL = "https://dev.supply-trail.humanitech.net/auth/realms/humanitech/protocol/openid-connect/auth?response_type=code&client_id=supply-trail-app&kc_action=UPDATE_PASSWORD"; + +export const labels: Partial> = { + firstName: "First Name", + lastName: "Last Name", + email: "Email", + phoneNumber: "Phone Number", + address: "Address", + birthdate: "Birth Date", +}; + +export const fields: Array = [ + "firstName", + "lastName", + "email", + "phoneNumber", + "address", + "birthdate", +]; diff --git a/applications/webapp/src/Pages/User/util/style.ts b/applications/webapp/src/Pages/User/util/style.ts index 30e146cf..b4f8e995 100644 --- a/applications/webapp/src/Pages/User/util/style.ts +++ b/applications/webapp/src/Pages/User/util/style.ts @@ -69,12 +69,11 @@ export const styles = (theme: Theme) => ({ userPage: { userPageContainer: { display: "flex", - flexDirection: ["column", "column", "row"], + flexDirection: { xs: "column", md: "row" }, margin: "0 20px 0 20px", }, profilePageHolder: { display: "grid", - gridTemplateRows: ["auto", "auto", "repeat(2, 0.2fr)"], gap: [minGap, minGap, maxGap], width: ["100%", "100%", "30%"], margin: ["0 0 20px", "0 0 20px", "0 20px 0 0"], diff --git a/applications/webapp/src/hooks/types.ts b/applications/webapp/src/hooks/types.ts deleted file mode 100644 index ed4f7754..00000000 --- a/applications/webapp/src/hooks/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Humanitech Supply Trail - * - * Copyright (c) Humanitech, Peter Rogov and Contributors - * - * Website: https://humanitech.net - * Repository: https://github.com/humanitech-net/supply-trail - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import Joi from "joi"; -import { DocumentNode } from "@apollo/client"; - -export type UserData = { - getUser: { - id: string; - username: string; - firstName: string; - lastName: string; - email: string; - }; -}; -export type UserQueryVariables = Record; - -export type QueryConfigs = { - GET_USER: { - query: DocumentNode; - data: UserData; - variables?: UserQueryVariables; - schema: Joi.ObjectSchema; - }; -}; diff --git a/applications/webapp/src/hooks/useGenericMutation.ts b/applications/webapp/src/hooks/useGenericMutation.ts new file mode 100644 index 00000000..ac8028a7 --- /dev/null +++ b/applications/webapp/src/hooks/useGenericMutation.ts @@ -0,0 +1,38 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { ApolloError, useMutation } from "@apollo/client"; +import { MutationConfigs } from "./util/types"; +import { mutationConfigs } from "./util/configs"; + +export function useGenericMutation( + mutationType: T, +): { + callMutation: ( + mutationVariables: MutationConfigs[T]["variables"], + ) => Promise; + data: MutationConfigs[T]["data"]; + loading: boolean; + error: ApolloError | undefined; +} { + const config = mutationConfigs[mutationType]; + const [mutate, { data, loading, error }] = useMutation(config.mutation); + + const callMutation = async ( + mutationVariables: MutationConfigs[T]["variables"], + ) => { + mutate({ + variables: mutationVariables, + }); + }; + return { callMutation, data, loading, error }; +} diff --git a/applications/webapp/src/hooks/useGenericQuery.ts b/applications/webapp/src/hooks/useGenericQuery.ts index 8420b5af..d33015ae 100644 --- a/applications/webapp/src/hooks/useGenericQuery.ts +++ b/applications/webapp/src/hooks/useGenericQuery.ts @@ -10,8 +10,8 @@ * LICENSE file in the root directory of this source tree. */ -import { queryConfigs } from "./configs"; -import { QueryConfigs } from "./types"; +import { queryConfigs } from "./util/configs"; +import { QueryConfigs } from "./util/types"; import { ApolloError, useQuery } from "@apollo/client"; export default function useGenericQuery( diff --git a/applications/webapp/src/hooks/useUserEntry.ts b/applications/webapp/src/hooks/useUserEntry.ts new file mode 100644 index 00000000..1eb045b5 --- /dev/null +++ b/applications/webapp/src/hooks/useUserEntry.ts @@ -0,0 +1,42 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { useMemo } from "react"; +import { useGenericMutation } from "./useGenericMutation"; +import { EditUserVariable } from "./util/types"; + +export default function useUserEntry() { + const { callMutation, data, loading, error } = + useGenericMutation("EDIT_USER"); + + const update = async (newUserData: EditUserVariable) => { + callMutation(newUserData); + }; + + const { username, firstName, lastName } = data?.editUser ?? {}; + + const user = useMemo( + () => ({ + username: username ?? "", + firstName: firstName ?? "", + lastName: lastName ?? "", + email: "email", + birthdate: "birthdate", + address: "address", + phoneNumber: "123456789", + description: `I am ${firstName}`, + }), + [username, firstName, lastName], + ); + + return { user, loading, error, update }; +} diff --git a/applications/webapp/src/hooks/configs.ts b/applications/webapp/src/hooks/util/configs.ts similarity index 62% rename from applications/webapp/src/hooks/configs.ts rename to applications/webapp/src/hooks/util/configs.ts index acf5ca0f..697c4588 100644 --- a/applications/webapp/src/hooks/configs.ts +++ b/applications/webapp/src/hooks/util/configs.ts @@ -10,9 +10,16 @@ * LICENSE file in the root directory of this source tree. */ +import { EditUserMutation } from "./mutation"; import { GET_USER_QUERY } from "./query"; import { userSchema } from "./schema"; -import { QueryConfigs, UserData } from "./types"; +import { + MutationConfigs, + QueryConfigs, + UserData, + EditUserVariable, + EditData, +} from "./types"; export const queryConfigs: QueryConfigs = { GET_USER: { @@ -21,3 +28,11 @@ export const queryConfigs: QueryConfigs = { data: {} as UserData, }, }; + +export const mutationConfigs: MutationConfigs = { + EDIT_USER: { + mutation: EditUserMutation, + data: {} as EditData, + variables: {} as EditUserVariable, + }, +}; diff --git a/applications/webapp/src/hooks/mutation.ts b/applications/webapp/src/hooks/util/mutation.ts similarity index 83% rename from applications/webapp/src/hooks/mutation.ts rename to applications/webapp/src/hooks/util/mutation.ts index 62a1b412..7e544143 100644 --- a/applications/webapp/src/hooks/mutation.ts +++ b/applications/webapp/src/hooks/util/mutation.ts @@ -14,6 +14,10 @@ import { gql } from "@apollo/client"; export const EditUserMutation = gql` mutation EditUser($userInput: UpdateUser!) { - editUser(userInput: $userInput) + editUser(userInput: $userInput) { + username + firstName + lastName + } } `; diff --git a/applications/webapp/src/hooks/query.ts b/applications/webapp/src/hooks/util/query.ts similarity index 100% rename from applications/webapp/src/hooks/query.ts rename to applications/webapp/src/hooks/util/query.ts diff --git a/applications/webapp/src/hooks/schema.ts b/applications/webapp/src/hooks/util/schema.ts similarity index 100% rename from applications/webapp/src/hooks/schema.ts rename to applications/webapp/src/hooks/util/schema.ts diff --git a/applications/webapp/src/hooks/util/types.ts b/applications/webapp/src/hooks/util/types.ts new file mode 100644 index 00000000..c0d17d6b --- /dev/null +++ b/applications/webapp/src/hooks/util/types.ts @@ -0,0 +1,73 @@ +/** + * Humanitech Supply Trail + * + * Copyright (c) Humanitech, Peter Rogov and Contributors + * + * Website: https://humanitech.net + * Repository: https://github.com/humanitech-net/supply-trail + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Joi from "joi"; +import { DocumentNode, ApolloError } from "@apollo/client"; +import { User } from "../../Pages/interface"; + +export type UserData = { + getUser: { + id: string; + username: string; + firstName: string; + lastName: string; + email: string; + }; +}; +export type UserQueryVariables = Record; + +export type QueryConfigs = { + GET_USER: { + query: DocumentNode; + data: UserData; + variables?: UserQueryVariables; + schema: Joi.ObjectSchema; + }; +}; + +export type EditUserVariable = { + userInput: { + username: string; + firstName: string; + lastName: string; + }; +}; + +export type MutationConfigs = { + EDIT_USER: { + mutation: DocumentNode; + data: EditData; + variables: EditUserVariable; + }; +}; + +export type UserContextType = { + user: User; + loading: boolean; + error: ApolloError | undefined; + update: (newUserData: EditUserVariable) => Promise; + setUserUpdated: React.Dispatch>; +}; + +export type EditData = { + editUser: { + id?: string; + username: string; + firstName: string; + lastName: string; + email?: string; + }; +}; + +export type UserInput = { + userInput: { username: string; firstName: string; lastName: string }; +};