diff --git a/schematics/application/index.test.ts b/schematics/application/index.test.ts index db5c739a..8507d583 100644 --- a/schematics/application/index.test.ts +++ b/schematics/application/index.test.ts @@ -9,6 +9,7 @@ import expectedCloudJanushFiles from "@janush-schematics/cloud/janush/data/expec import expectedCloudTemplateFiles from "@janush-schematics/cloud/template/data/expectedNewFiles.json"; // Web files import expectedWebAuthFiles from "@janush-schematics/web/authentication/data/expectedNewFiles.json"; +import expectedWebUserManagementFiles from "@janush-schematics/web/authentication/userManagement/data/expectedNewFiles.json"; import expectedWebJanushFiles from "@janush-schematics/web/janush/data/expectedNewFiles.json"; import expectedWebTemplateFiles from "@janush-schematics/web/template/data/expectedNewFiles.json"; @@ -36,6 +37,7 @@ const expectedWebFiles = [ ...expectedWebTemplateFiles, ...expectedWebJanushFiles, ...expectedWebAuthFiles, + ...expectedWebUserManagementFiles, ]; const expectedCloudFiles = [ ...expectedCloudTemplateFiles, diff --git a/schematics/web/authentication/index.test.ts b/schematics/web/authentication/index.test.ts index c83ec77e..48646463 100644 --- a/schematics/web/authentication/index.test.ts +++ b/schematics/web/authentication/index.test.ts @@ -4,6 +4,7 @@ import { SchematicTestRunner } from "@angular-devkit/schematics/testing"; import * as path from "path"; import expectedAuthFiles from "@janush-schematics/web/authentication/data/expectedNewFiles.json"; +import expectedWebUserManagementFiles from "@janush-schematics/web/authentication/userManagement/data/expectedNewFiles.json"; import expectedJanushFiles from "@janush-schematics/web/janush/data/expectedNewFiles.json"; import expectedTemplateFiles from "@janush-schematics/web/template/data/expectedNewFiles.json"; import { emptyJanush } from "@mocks/janush"; @@ -30,6 +31,7 @@ describe("web", () => { ...expectedTemplateFiles, ...expectedJanushFiles, ...expectedAuthFiles, + ...expectedWebUserManagementFiles, ]); }); diff --git a/schematics/web/authentication/index.ts b/schematics/web/authentication/index.ts index cbb98724..2e3b53a5 100644 --- a/schematics/web/authentication/index.ts +++ b/schematics/web/authentication/index.ts @@ -41,6 +41,7 @@ export const webAuthenticationGenerator = (options: Schema): Rule => { MergeStrategy.Default, ), options.idP.length ? schematic(WebSchematic.IDP, options) : noop(), + options.userManagement ? schematic(WebSchematic.USER_MANAGEMENT, options) : noop(), packageJsonExtender, tsConfigExtender, ]); diff --git a/schematics/web/authentication/schema.json b/schematics/web/authentication/schema.json index b40414a1..8f1116c3 100644 --- a/schematics/web/authentication/schema.json +++ b/schematics/web/authentication/schema.json @@ -9,6 +9,12 @@ "description": "The name of the application", "default": "janush-app" }, + "userManagement": { + "type": "boolean", + "description": "Add user management feature", + "default": true, + "x-prompt": "[WEB] Do you want add user management feature?" + }, "idP": { "type": "array", "description": "Sets additional identity providers for user pool.", diff --git a/schematics/web/authentication/schema.ts b/schematics/web/authentication/schema.ts index 8395f5dd..0eddcece 100644 --- a/schematics/web/authentication/schema.ts +++ b/schematics/web/authentication/schema.ts @@ -1,7 +1,9 @@ -import { IdentityProviders } from "@enums/Module"; +import { IdentityProviders, Module } from "@enums/Module"; import { Schema as AppSchema } from "../../application/schema"; export interface Schema extends Partial { + userManagement: boolean; idP: IdentityProviders[]; + modules: Module[]; } diff --git a/schematics/web/authentication/userManagement/collection.json b/schematics/web/authentication/userManagement/collection.json new file mode 100644 index 00000000..a993d6bf --- /dev/null +++ b/schematics/web/authentication/userManagement/collection.json @@ -0,0 +1,9 @@ +{ + "$schema": "../../../../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "web.userManagement": { + "description": "User Management", + "factory": "./index#userManagementGenerator" + } + } +} diff --git a/schematics/web/authentication/userManagement/data/expectedNewFiles.json b/schematics/web/authentication/userManagement/data/expectedNewFiles.json new file mode 100644 index 00000000..7a36c5fe --- /dev/null +++ b/schematics/web/authentication/userManagement/data/expectedNewFiles.json @@ -0,0 +1,31 @@ +[ + "/web/src/layouts/Modals/FormModalLayout/FormModalLayout.tsx", + "/web/src/layouts/UsersAdministrationLayout/UsersAdministrationLayout.tsx", + "/web/src/routing/Routes.tsx", + "/web/src/routing/paths.ts", + "/web/src/routing/routes/UsersAdministration/Groups/Groups.tsx", + "/web/src/routing/routes/UsersAdministration/Groups/GroupDetails/GroupDetails.tsx", + "/web/src/routing/routes/UsersAdministration/Groups/GroupsTable/GroupsTable.tsx", + "/web/src/routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal.tsx", + "/web/src/routing/routes/UsersAdministration/Groups/Modals/GroupModal/GroupModal.tsx", + "/web/src/routing/routes/UsersAdministration/Users/Users.tsx", + "/web/src/routing/routes/UsersAdministration/Users/Modals/UserModal/UserModal.tsx", + "/web/src/routing/routes/UsersAdministration/Users/UserDetails/UserDetails.tsx", + "/web/src/routing/routes/UsersAdministration/Users/UsersTable/UsersTable.tsx", + "/web/src/components/Button/Button.tsx", + "/web/src/components/ConfirmationDialog/ConfirmationDialog.tsx", + "/web/src/components/ErrorNotification/ErrorNotification.tsx", + "/web/src/components/FormInput/FormInput.tsx", + "/web/src/components/ListElement/ListElement.tsx", + "/web/src/components/Select/Select.tsx", + "/web/src/components/Table/Table.tsx", + "/web/src/components/Table/TableCell.tsx", + "/web/src/components/Table/TableRow.tsx", + "/web/src/components/icons/ArrowLeftIcon/ArrowLeftIcon.tsx", + "/web/src/components/icons/SearchIcon/SearchIcon.tsx", + "/web/src/types/group.ts", + "/web/src/types/user.ts", + "/web/src/utils/checkIsThisCurrentTab/checkIsThisCurrentTab.ts", + "/web/src/utils/getOptionsFromEnum/getOptionsFromEnum.ts", + "/web/src/validations/UserValidation.ts" +] diff --git a/schematics/web/authentication/userManagement/files/src/components/Button/Button.tsx.template b/schematics/web/authentication/userManagement/files/src/components/Button/Button.tsx.template new file mode 100644 index 00000000..d8febaea --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/Button/Button.tsx.template @@ -0,0 +1,38 @@ +import { FC } from "react"; + +import { Button as MuiButton, Theme, ButtonProps } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { rgbaColors } from "@themes/palette"; + +export const Button: FC = ({ children, ...buttonProps }) => { + const theme = useTheme(); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/ConfirmationDialog/ConfirmationDialog.tsx.template b/schematics/web/authentication/userManagement/files/src/components/ConfirmationDialog/ConfirmationDialog.tsx.template new file mode 100644 index 00000000..9e62f85d --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/ConfirmationDialog/ConfirmationDialog.tsx.template @@ -0,0 +1,85 @@ +import { FC, ReactNode } from "react"; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Theme, + Button, +} from "@mui/material"; +import { useTheme } from "@mui/material/styles"; + +interface Props { + children: ReactNode; + isOpen: boolean; + onSubmit: () => void; + onCancelClick: () => void; + submitButtonTitle: string; +} + +const ConfirmationDialogTitle: FC = ({ children }) => { + const theme = useTheme(); + + return ( + + {children} + + ); +}; + +const ConfirmationDialogContent: FC = ({ children }) => { + const theme = useTheme(); + + return ( + + {children} + + ); +}; + +export const ConfirmationDialog = ({ + children, + isOpen, + onSubmit, + onCancelClick, + submitButtonTitle, +}: Props) => { + const theme = useTheme(); + + return ( + + {children} + + + + + + ); +}; + +ConfirmationDialog.Title = ConfirmationDialogTitle; +ConfirmationDialog.Content = ConfirmationDialogContent; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/ErrorNotification/ErrorNotification.tsx.template b/schematics/web/authentication/userManagement/files/src/components/ErrorNotification/ErrorNotification.tsx.template new file mode 100644 index 00000000..d2c83996 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/ErrorNotification/ErrorNotification.tsx.template @@ -0,0 +1,37 @@ +import { VFC } from "react"; + +import { Theme, Snackbar, Alert } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; + +interface Props { + content?: string; + show: boolean; + onClose: () => void; +} + +export const ErrorNotification: VFC = ({ content, show, onClose }) => { + const theme = useTheme(); + + return ( + + + {content || "Something went wrong. Please try again."} + + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/FormInput/FormInput.tsx.template b/schematics/web/authentication/userManagement/files/src/components/FormInput/FormInput.tsx.template new file mode 100644 index 00000000..db7ce6d1 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/FormInput/FormInput.tsx.template @@ -0,0 +1,20 @@ +import { VFC } from "react"; + +import { TextField, StandardTextFieldProps } from "@mui/material"; + +interface Props extends StandardTextFieldProps { + errorMessage?: string | undefined; +} + +export const FormInput: VFC = ({ + errorMessage, + ...restProps +}: Props) => ( + +); \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/ListElement/ListElement.tsx.template b/schematics/web/authentication/userManagement/files/src/components/ListElement/ListElement.tsx.template new file mode 100644 index 00000000..e098f368 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/ListElement/ListElement.tsx.template @@ -0,0 +1,29 @@ +import { VFC } from "react"; + +import { Box, Typography, Theme } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; + +interface Props { + label: string; + value: string; +} + +export const ListElement: VFC = ({ label, value }) => { + const theme = useTheme(); + + return ( + + + {label} + + + {value} + + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/Select/Select.tsx.template b/schematics/web/authentication/userManagement/files/src/components/Select/Select.tsx.template new file mode 100644 index 00000000..fcf48a86 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/Select/Select.tsx.template @@ -0,0 +1,29 @@ +import { FC } from "react"; + +import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; +import { Select as MuiSelect, MenuItem, SelectProps } from "@mui/material"; + +interface Option { + name: string; + label: string; +} + +interface Props extends SelectProps { + options: Option[]; +} + +export const Select: FC = ({ options, ...selectProps }) => { + return ( + + {options.map((option) => ( + + {option.label} + + ))} + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/Table/Table.tsx.template b/schematics/web/authentication/userManagement/files/src/components/Table/Table.tsx.template new file mode 100644 index 00000000..28f4de91 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/Table/Table.tsx.template @@ -0,0 +1,28 @@ +import { ReactNode } from "react"; + +import { + Table as MuiTable, + TableContainer, + TableHead, + TableBody, + Paper, +} from "@mui/material"; + +import { TableCell } from "./TableCell"; +import { TableRow } from "./TableRow"; + +interface Props { + children: ReactNode; + dataTestId?: string; +} + +export const Table = ({ children, ...restProps }: Props) => ( + + {children} + +); + +Table.TableCell = TableCell; +Table.TableBody = TableBody; +Table.TableHead = TableHead; +Table.TableRow = TableRow; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/Table/TableCell.tsx.template b/schematics/web/authentication/userManagement/files/src/components/Table/TableCell.tsx.template new file mode 100644 index 00000000..37430b0e --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/Table/TableCell.tsx.template @@ -0,0 +1,30 @@ +import { FC, ReactNode } from "react"; + +import { + TableCell as MuiTableCell, + TableCellProps, + Theme, +} from "@mui/material"; +import { useTheme } from "@mui/material/styles"; + +interface Props extends TableCellProps { + children: ReactNode; +} + +export const TableCell: FC = ({ children, ...tableCellProps }) => { + const theme = useTheme(); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/Table/TableRow.tsx.template b/schematics/web/authentication/userManagement/files/src/components/Table/TableRow.tsx.template new file mode 100644 index 00000000..8a9e491c --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/Table/TableRow.tsx.template @@ -0,0 +1,11 @@ +import { ReactNode, FC } from "react"; + +import { TableRow as MuiTableRow, TableRowProps } from "@mui/material"; + +interface Props extends TableRowProps { + children: ReactNode; +} + +export const TableRow: FC = ({ children, ...rowProps }) => ( + {children} +); \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/icons/ArrowLeftIcon/ArrowLeftIcon.tsx.template b/schematics/web/authentication/userManagement/files/src/components/icons/ArrowLeftIcon/ArrowLeftIcon.tsx.template new file mode 100644 index 00000000..31061e7c --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/icons/ArrowLeftIcon/ArrowLeftIcon.tsx.template @@ -0,0 +1,18 @@ +import { SVGProps } from "react"; + +export const ArrowLeftIcon = (props: SVGProps) => ( + + + +); \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/components/icons/SearchIcon/SearchIcon.tsx.template b/schematics/web/authentication/userManagement/files/src/components/icons/SearchIcon/SearchIcon.tsx.template new file mode 100644 index 00000000..38eff6ac --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/components/icons/SearchIcon/SearchIcon.tsx.template @@ -0,0 +1,17 @@ +import { SVGProps } from "react"; + +export const SearchIcon = (props: SVGProps) => ( + + + +); \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/layouts/Modals/FormModalLayout/FormModalLayout.tsx.template b/schematics/web/authentication/userManagement/files/src/layouts/Modals/FormModalLayout/FormModalLayout.tsx.template new file mode 100644 index 00000000..1193c51d --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/layouts/Modals/FormModalLayout/FormModalLayout.tsx.template @@ -0,0 +1,75 @@ +import { FC } from "react"; + +import { Button } from "@components/Button/Button"; +import CloseIcon from "@mui/icons-material/Close"; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Theme, +} from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import { useTheme } from "@mui/material/styles"; +import { rgbaColors } from "@themes/palette"; + +interface Props { + isOpen: boolean; + title: string; + buttonTitle: string; + isButtonDisabled?: boolean; + onSubmit?: () => void; + onModalClose: () => void; +} + +export const FormModalLayout: FC = ({ + children, + isOpen, + title, + buttonTitle, + isButtonDisabled = false, + onSubmit, + onModalClose, +}) => { + const theme = useTheme(); + + return ( + + + {title} + + {children} + + + + + + + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/layouts/UsersAdministrationLayout/UsersAdministrationLayout.tsx.template b/schematics/web/authentication/userManagement/files/src/layouts/UsersAdministrationLayout/UsersAdministrationLayout.tsx.template new file mode 100644 index 00000000..b25095b0 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/layouts/UsersAdministrationLayout/UsersAdministrationLayout.tsx.template @@ -0,0 +1,120 @@ +import { FC, ReactNode } from "react"; + +import { ArrowLeftIcon } from "@components/icons/ArrowLeftIcon/ArrowLeftIcon"; +import { Link } from "@components/Link/Link"; +import { PageLayout } from "@layouts/PageLayout/PageLayout"; +import { TopAppBar } from "@layouts/TopAppBar/TopAppBar"; +import { Box, Theme, Button as MuiButton } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { Paths } from "@routing/paths"; +import { rgbaColors } from "@themes/palette"; +import { checkIsThisCurrentTab } from "@utils/checkIsThisCurrentTab/checkIsThisCurrentTab"; +import { useLocation } from "react-router-dom"; + +interface UsersAdministrationLayoutProps { + onBackClick?: () => void; + buttons: ReactNode; +} + +interface TabProps { + linkTo: string; + isActive: boolean; +} + +const Tab: FC = ({ children, linkTo, isActive }) => { + const theme = useTheme(); + + const getTabStyles = () => ({ + color: isActive ? theme.palette.primary.main : theme.palette.common.black, + opacity: isActive ? 1 : 0.6, + fontWeight: 600, + lineHeight: theme.spacing(2), + height: "100%", + borderBottom: isActive + ? `1px solid ${theme.palette.primary.main}` + : `1px solid ${theme.palette.common.white}`, + borderRadius: 0, + }); + + return ( + + + {children} + + + ); +}; + +export const UsersAdministrationLayout: FC = ({ + children, + onBackClick, + buttons, +}) => { + const theme = useTheme(); + const location = useLocation(); + + const isUsersTab = checkIsThisCurrentTab(location, "users"); + + return ( + <> + + + + {onBackClick ? ( + + + + ) : ( + + + Users + + + Groups + + + )} + {buttons} + + {children} + + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/Routes.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/Routes.tsx.template new file mode 100644 index 00000000..3c68ecc5 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/Routes.tsx.template @@ -0,0 +1,53 @@ +import { lazy, VFC } from "react"; +import { Route, Routes } from "react-router-dom"; +<% if (isAuth) { %>import { Paths } from "@routing/paths";<% } %> + +const IndexPage = lazy(() => import("./routes/Index/IndexPage")); +<% if (isAuth) { %>const SignIn = lazy(() => import("./routes/SignIn/SignIn")); +const SignUp = lazy(() => import("./routes/SignUp/SignUp")); +const SignUpSuccess = lazy(() => + import("./routes/SignUpSuccess/SignUpSuccess") +); +const VerifyEmail = lazy(() => import("./routes/VerifyEmail/VerifyEmail")); +const Users = lazy(() => import("./routes/UsersAdministration/Users/Users")); +const UserDetails = lazy(() => + import("./routes/UsersAdministration/Users/UserDetails/UserDetails") +); +const Groups = lazy(() => import("./routes/UsersAdministration/Groups/Groups")); +const GroupDetails = lazy(() => + import("./routes/UsersAdministration/Groups/GroupDetails/GroupDetails") +); +const ForgotPassword = lazy(() => import("./routes/ForgotPassword/ForgotPassword")); +const ResetPassword = lazy(() => import("./routes/ResetPassword/ResetPassword"));<% } %> + +export const Router: VFC = () => { + return ( + + <% if (isAuth) { %>} /> + } /> + } /> + } /> + + } + /> + } + /> + } + /> + } + /> + + } /> + } /><% } %> + } /> + + ); +}; diff --git a/schematics/web/authentication/userManagement/files/src/routing/paths.ts.template b/schematics/web/authentication/userManagement/files/src/routing/paths.ts.template new file mode 100644 index 00000000..49937ccc --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/paths.ts.template @@ -0,0 +1,14 @@ +export enum Paths { + BASE = "/", + <% if (isAuth) { %> SIGN_IN_PATH = "/sign-in", + SIGN_UP_PATH = "/sign-up", + SIGN_UP_PATH_SUCCESS = "/sign-up/success", + CONFIRM_SIGN_UP = "/confirm-signup", + USERS_ADMINISTRATION_PATH = "/users-administration", + USERS_ADMINISTRATION_USERS_PATH = "/users-administration/users", + USERS_ADMINISTRATION_USERS_USER_DETAILS_PATH = "/users-administration/users/:userId", + USERS_ADMINISTRATION_GROUPS_PATH = "/users-administration/groups", + USERS_ADMINISTRATION_GROUPS_GROUP_DETAILS_PATH = "/users-administration/groups/:groupId", + FORGOT_PASSWORD_PATH = "/forgot-password", + RESET_PASSWORD_PATH = "/create-new-password",<% } %> +} \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/GroupDetails/GroupDetails.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/GroupDetails/GroupDetails.tsx.template new file mode 100644 index 00000000..2e996ac0 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/GroupDetails/GroupDetails.tsx.template @@ -0,0 +1,130 @@ +import { useState, VFC } from "react"; + +import { Button } from "@components/Button/Button"; +import { ConfirmationDialog } from "@components/ConfirmationDialog/ConfirmationDialog"; +import { ListElement } from "@components/ListElement/ListElement"; +import { Group } from "@janush-types/group"; +import { User } from "@janush-types/user"; +import { UsersAdministrationLayout } from "@layouts/UsersAdministrationLayout/UsersAdministrationLayout"; +import { Box } from "@mui/material"; +import { Paths } from "@routing/paths"; +import UsersTable from "@routing/routes/UsersAdministration/Users/UsersTable/UsersTable"; +import { rgbaColors } from "@themes/palette"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { GroupModal, GroupModalVariant } from "../Modals/GroupModal/GroupModal"; + +// TODO: Remove it after getting data from backend +const data: User[] = [ + { + id: "1c4a9a8d-b652-421a-b130-9ad680029521", + email: "b.szurek@codeandpepper.com", + phoneNumber: "+48 666 777 888", + access: true, + status: true, + lastModified: "15-06-2021", + }, + { + id: "1c4a9a8d-b652-421a-b130-9ad680029522", + email: "b.szurek@codeandpepper.com", + phoneNumber: "+48 666 777 888", + access: false, + status: false, + lastModified: "15-06-2021", + }, + { + id: "1c4a9a8d-b652-421a-b130-9ad680029523", + email: "b.szurek@codeandpepper.com", + phoneNumber: "+48 666 777 888", + access: true, + status: false, + lastModified: "15-06-2021", + }, +]; + +interface LocationState { + group: Group; +} + +const GroupDetails: VFC = () => { + const navigate = useNavigate(); + + const location = useLocation(); + // TODO: change to get group data from backend + const { group } = location.state as LocationState; + + const [isEditGroupModalOpen, setIsEditGroupModalOpen] = useState(false); + const [isDeleteGroupModalOpen, setIsDeleteGroupModalOpen] = useState(false); + + const onTableRowClick = (user: User) => { + navigate(`${Paths.USERS_ADMINISTRATION_USERS_PATH}/${user.id}`, { + state: { user }, + }); + }; + + return ( + navigate(-1)} + buttons={ + <> + + {/* TODO: Handle removing user while implementing backend */} + + + + } + > + + + + + + + + + + + + setIsEditGroupModalOpen(false)} + variant={GroupModalVariant.Edit} + /> + setIsDeleteGroupModalOpen(false)} + // TODO: Add function for submitting behavior while backend implementation + onSubmit={() => null} + submitButtonTitle="Remove user" + > + Deleting group + + Are you sure you want to delete this group? + + + + ); +}; + +export default GroupDetails; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/Groups.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/Groups.tsx.template new file mode 100644 index 00000000..9b0a15bf --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/Groups.tsx.template @@ -0,0 +1,49 @@ +import { VFC, useState } from "react"; + +import { Button } from "@components/Button/Button"; +import { Group } from "@janush-types/group"; +import { UsersAdministrationLayout } from "@layouts/UsersAdministrationLayout/UsersAdministrationLayout"; + +import GroupsTable from "./GroupsTable/GroupsTable"; +import { GroupModal, GroupModalVariant } from "./Modals/GroupModal/GroupModal"; + +// TODO: Remove it after getting data from backend +const data: Group[] = [ + { + id: "1c4a9a8d-b652-421a-b130-9ad680029521", + name: "Group0001", + description: "Admins", + members: 1, + lastModified: "15-06-2021", + }, + { + id: "1c4a9a8d-b652-421a-b130-9ad680029522", + name: "Group0002", + description: "Moderators", + members: 3, + lastModified: "15-06-2021", + }, +]; + +const Groups: VFC = () => { + const [isCreateGroupModalOpen, setIsCreateGroupModalOpen] = useState(false); + + return ( + setIsCreateGroupModalOpen(true)}> + New Group + + } + > + + setIsCreateGroupModalOpen(false)} + variant={GroupModalVariant.Create} + /> + + ); +}; + +export default Groups; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/GroupsTable/GroupsTable.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/GroupsTable/GroupsTable.tsx.template new file mode 100644 index 00000000..0751edee --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/GroupsTable/GroupsTable.tsx.template @@ -0,0 +1,73 @@ +import { VFC } from "react"; + +import { Table } from "@components/Table/Table"; +import { Group } from "@janush-types/group"; +import { Theme } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { rgbaColors } from "@themes/palette"; +import { useNavigate } from "react-router-dom"; + +const columns = [ + { name: "name", label: "Name" }, + { name: "description", label: "Description" }, + { name: "members", label: "Members" }, + { name: "lastModified", label: "Last Modified" }, +]; + +interface Props { + data: Group[]; +} + +const { TableHead, TableRow, TableCell, TableBody } = Table; + +const GroupsTable: VFC = ({ data }) => { + const navigate = useNavigate(); + const theme = useTheme(); + + const onRowClick = (group: Group) => { + navigate(group.id, { state: { group } }); + }; + + return ( + + + + {columns.map((column) => ( + {column.label} + ))} + + + + {data.length ? ( + data.map((item) => ( + onRowClick(item)} + > + {item.name} + {item.description} + {item.members} + + {item.lastModified} + + + )) + ) : ( + + + No results + + + )} + +
+ ); +}; + +export default GroupsTable; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal.tsx.template new file mode 100644 index 00000000..6db2fddf --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal.tsx.template @@ -0,0 +1,69 @@ +import { VFC } from "react"; + +import { Select } from "@components/Select/Select"; +import { FormModalLayout } from "@layouts/Modals/FormModalLayout/FormModalLayout"; +import { Box, Typography, Theme } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { useForm, Controller } from "react-hook-form"; + +const groupOptions = [ + { name: "group1", label: "Group1" }, + { name: "group2", label: "Group2" }, + { name: "group3", label: "Group3" }, +]; + +interface FormData { + group: string; +} + +interface Props { + isOpen: boolean; + onModalClose: () => void; +} + +export const AddToGroupModal: VFC = ({ ...props }) => { + const theme = useTheme(); + + const { control, formState, handleSubmit } = useForm({ + mode: "onChange", + defaultValues: { + group: "group1", + }, + }); + + return ( + + + b.szurek@codeandpepper.com + + + Group + ( + + )} + /> + + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/Modals/UserModal/UserModal.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/Modals/UserModal/UserModal.tsx.template new file mode 100644 index 00000000..8b0409a1 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/Modals/UserModal/UserModal.tsx.template @@ -0,0 +1,149 @@ +import { VFC } from "react"; + +import { FormInput } from "@components/FormInput/FormInput"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { FormModalLayout } from "@layouts/Modals/FormModalLayout/FormModalLayout"; +import { Box, Checkbox, FormControlLabel } from "@mui/material"; +import { emailSchema } from "@validations/UserValidation"; +import { useForm, Controller } from "react-hook-form"; + +export enum UserModalVariant { + Create = "create", + Edit = "edit", +} + +interface FormData { + email: string; + phoneNumber: string; + isEmailVerified: boolean; + password: string; + shouldResendVerificationEmail: boolean; + shouldResendInvitationEmail: boolean; +} + +interface Props { + isOpen: boolean; + onModalClose: () => void; + variant: UserModalVariant; +} + +export const UserModal: VFC = ({ variant, ...restProps }) => { + const { control, formState, handleSubmit } = useForm({ + mode: "onChange", + resolver: yupResolver(emailSchema), + defaultValues: { + email: "", + phoneNumber: "", + isEmailVerified: true, + password: "", + shouldResendVerificationEmail: false, + shouldResendInvitationEmail: false, + }, + }); + + return ( + + ( + + )} + /> + ( + + )} + /> + {variant === UserModalVariant.Edit && ( + ( + + )} + /> + )} + {variant === UserModalVariant.Create && ( + ( + } + label="Mark email as verified" + sx={{ + mt: 2, + "& .MuiFormControlLabel-label": { + fontSize: "12px", + }, + }} + /> + )} + /> + )} + {variant === UserModalVariant.Edit && ( + + ( + } + label="Resend verification email" + sx={{ + ml: 1, + mt: 2, + "& .MuiFormControlLabel-label": { + fontSize: "12px", + }, + }} + /> + )} + /> + ( + } + label="Resend invitation email" + sx={{ + ml: 1, + "& .MuiFormControlLabel-label": { + fontSize: "12px", + }, + }} + /> + )} + /> + + )} + + ); +}; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/UserDetails/UserDetails.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/UserDetails/UserDetails.tsx.template new file mode 100644 index 00000000..4d268db8 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/UserDetails/UserDetails.tsx.template @@ -0,0 +1,114 @@ +import { useState, VFC, FC } from "react"; + +import { Button } from "@components/Button/Button"; +import { ConfirmationDialog } from "@components/ConfirmationDialog/ConfirmationDialog"; +import { ListElement } from "@components/ListElement/ListElement"; +import { User } from "@janush-types/user"; +import { UsersAdministrationLayout } from "@layouts/UsersAdministrationLayout/UsersAdministrationLayout"; +import { Box, Typography, Theme, ButtonProps } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { AddToGroupModal } from "@routing/routes/UsersAdministration/Groups/Modals/AddToGroupModal/AddToGroupModal"; +import { rgbaColors } from "@themes/palette"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { UserModal, UserModalVariant } from "../Modals/UserModal/UserModal"; + +interface LocationState { + user: User; +} + +const ButtonWrapper: FC = ({ children, ...buttonProps }) => ( + +); + +const UserDetails: VFC = () => { + const theme = useTheme(); + const navigate = useNavigate(); + + const location = useLocation(); + // TODO: change to get user data from backend + const { user } = location.state as LocationState; + + const [isEditUserModalOpen, setIsEditUserModalOpen] = useState(false); + const [isAddToGroupModalOpen, setIsAddToGroupModalOpen] = useState(false); + const [isRemoveUserModalOpen, setIsRemoveUserModalOpen] = useState(false); + + return ( + navigate(-1)} + buttons={ + <> + setIsEditUserModalOpen(true)}> + Edit + + setIsAddToGroupModalOpen(true)}> + Add to group + + {/* TODO: Handle enable button while implementing backend */} + Enable + {/* TODO: Handle disable button while implementing backend */} + Disable + setIsRemoveUserModalOpen(true)}> + Remove + + + } + > + + + Account Details + + + + + + + + + {/* TODO: set value from server */} + + + setIsEditUserModalOpen(false)} + variant={UserModalVariant.Edit} + /> + setIsAddToGroupModalOpen(false)} + /> + setIsRemoveUserModalOpen(false)} + // TODO: Add function for submitting behavior while backend implementation + onSubmit={() => null} + submitButtonTitle="Remove user" + > + Removing user + + Are you sure you want to remove this user? + + + + ); +}; + +export default UserDetails; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/Users.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/Users.tsx.template new file mode 100644 index 00000000..560034ad --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/Users.tsx.template @@ -0,0 +1,209 @@ +import { useState, VFC } from "react"; + +import { Button } from "@components/Button/Button"; +import { SearchIcon } from "@components/icons/SearchIcon/SearchIcon"; +import { Select } from "@components/Select/Select"; +import { User } from "@janush-types/user"; +import { UsersAdministrationLayout } from "@layouts/UsersAdministrationLayout/UsersAdministrationLayout"; +import { Box, Typography } from "@mui/material"; +import Input from "@mui/material/Input"; +import InputAdornment from "@mui/material/InputAdornment"; +import { rgbaColors } from "@themes/palette"; +import { getOptionsFromEnum } from "@utils/getOptionsFromEnum/getOptionsFromEnum"; +import { useNavigate } from "react-router-dom"; + +import { UserModal, UserModalVariant } from "./Modals/UserModal/UserModal"; +import UsersTable from "./UsersTable/UsersTable"; + +enum SearchBy { + ID = "id", + Email = "email", + Access = "access", + Status = "status", +} + +enum Status { + All = "all", + Confirmed = "confirmed", + Unconfirmed = "unconfirmed", +} + +enum Access { + All = "all", + Enabled = "enabled", + Disabled = "disabled", +} + +interface Search { + searchBy: SearchBy; + searchFor: string; +} + +// TODO: Remove it after getting data from backend +const data: User[] = [ + { + id: "1c4a9a8d-b652-421a-b130-9ad680029521", + email: "b.szurek@codeandpepper.com", + phoneNumber: "+48 666 777 888", + access: true, + status: true, + lastModified: "15-06-2021", + }, + { + id: "1c4a9a8d-b652-421a-b130-9ad680029522", + email: "b.szurek@codeandpepper.com", + phoneNumber: "+48 666 777 888", + access: false, + status: false, + lastModified: "15-06-2021", + }, + { + id: "1c4a9a8d-b652-421a-b130-9ad680029523", + email: "b.szurek@codeandpepper.com", + phoneNumber: "+48 666 777 888", + access: true, + status: false, + lastModified: "15-06-2021", + }, +]; + +const Users: VFC = () => { + const [search, setSearch] = useState({ + searchBy: SearchBy.Email, + searchFor: "", + }); + const [status, setStatus] = useState(Status.All); + const [access, setAccess] = useState(Access.All); + const [isCreateUserModalOpen, setIsCreateUserModalOpen] = useState(false); + + const navigate = useNavigate(); + + const onTableRowClick = (user: User) => { + navigate(user.id, { state: { user } }); + }; + + return ( + + + + + + + } + > + + + + + + + } + sx={{ + pl: 2, + pr: 1.5, + // TODO: Static width need to be removed while implementing RWD + width: 272, + "& .MuiInput-input": { + pb: 1, + }, + }} + /> + )} + {search.searchBy === SearchBy.Status && ( + setAccess(e.target.value as Access)} + options={getOptionsFromEnum(Access)} + sx={{ + // TODO: Static width need to be removed while implementing RWD + width: 272, + "& .MuiSelect-select": { + pl: 2, + }, + "& .MuiSvgIcon-root": { + mr: 1.5, + }, + }} + /> + )} + + + {`Showing ${data.length} of ${data.length}`} + {` (selected 0)`} + + + + + setIsCreateUserModalOpen(false)} + variant={UserModalVariant.Create} + /> + + ); +}; + +export default Users; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/UsersTable/UsersTable.tsx.template b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/UsersTable/UsersTable.tsx.template new file mode 100644 index 00000000..b68bf9c4 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/routing/routes/UsersAdministration/Users/UsersTable/UsersTable.tsx.template @@ -0,0 +1,102 @@ +import { VFC, FC } from "react"; + +import { Table } from "@components/Table/Table"; +import { User } from "@janush-types/user"; +import { Typography, Theme, Checkbox } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { rgbaColors } from "@themes/palette"; + +const columns = [ + { name: "checkbox", label: "" }, + { name: "id", label: "ID" }, + { name: "email", label: "Email" }, + { name: "phoneNumber", label: "Phone Number" }, + { name: "access", label: "Access" }, + { name: "status", label: "Status" }, + { name: "lastmodified", label: "Last Modified" }, +]; + +interface Props { + data: User[]; + onRowClick: (user: User) => void; +} + +const { TableHead, TableRow, TableCell, TableBody } = Table; + +const TableCellStyled: FC = ({ children }) => ( + {children} +); + +const UsersTable: VFC = ({ data, onRowClick }) => { + const theme = useTheme(); + + return ( + + + + {columns.map((column) => ( + {column.label} + ))} + + + + {data.length ? ( + data.map((item) => ( + onRowClick(item)} + > + + e.stopPropagation()} /> + + {item.id} + {item.email} + {item.phoneNumber} + + + {item.access ? "Enabled" : "Disabled"} + + + + + {item.access ? "Confirmed" : "Unconfirmed"} + + + {item.lastModified} + + )) + ) : ( + + + No results + + + )} + +
+ ); +}; + +export default UsersTable; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/types/group.ts.template b/schematics/web/authentication/userManagement/files/src/types/group.ts.template new file mode 100644 index 00000000..e4bf2a4c --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/types/group.ts.template @@ -0,0 +1,7 @@ +export interface Group { + id: string; + name: string; + description: string; + members: number; + lastModified: string; +} \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/types/user.ts.template b/schematics/web/authentication/userManagement/files/src/types/user.ts.template new file mode 100644 index 00000000..e8b9233f --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/types/user.ts.template @@ -0,0 +1,8 @@ +export interface User { + id: string; + email: string; + phoneNumber: string; + access: boolean; + status: boolean; + lastModified: string; +} \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/utils/checkIsThisCurrentTab/checkIsThisCurrentTab.ts.template b/schematics/web/authentication/userManagement/files/src/utils/checkIsThisCurrentTab/checkIsThisCurrentTab.ts.template new file mode 100644 index 00000000..f34d8baa --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/utils/checkIsThisCurrentTab/checkIsThisCurrentTab.ts.template @@ -0,0 +1,6 @@ +import { Location } from "react-router-dom"; + +export const checkIsThisCurrentTab = ( + location: Location, + tabName: string +): boolean => location.pathname.split("/")[2] === tabName; \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/utils/getOptionsFromEnum/getOptionsFromEnum.ts.template b/schematics/web/authentication/userManagement/files/src/utils/getOptionsFromEnum/getOptionsFromEnum.ts.template new file mode 100644 index 00000000..a03ff596 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/utils/getOptionsFromEnum/getOptionsFromEnum.ts.template @@ -0,0 +1,5 @@ +export const getOptionsFromEnum = (passedEnum: any) => + Object.keys(passedEnum).map((item) => ({ + name: item.toLocaleLowerCase(), + label: item, + })); \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/files/src/validations/UserValidation.ts.template b/schematics/web/authentication/userManagement/files/src/validations/UserValidation.ts.template new file mode 100644 index 00000000..c78f9614 --- /dev/null +++ b/schematics/web/authentication/userManagement/files/src/validations/UserValidation.ts.template @@ -0,0 +1,11 @@ +import * as yup from "yup"; + +export const baseSchema = { + firstName: yup.string().max(30).required().label("First name"), + lastName: yup.string().max(30).required().label("Last name"), +}; + +export const emailSchema = yup.object({ + ...baseSchema, + email: yup.string().email().required().label("Email address"), +}); \ No newline at end of file diff --git a/schematics/web/authentication/userManagement/index.test.ts b/schematics/web/authentication/userManagement/index.test.ts new file mode 100644 index 00000000..19ffa4de --- /dev/null +++ b/schematics/web/authentication/userManagement/index.test.ts @@ -0,0 +1,62 @@ +import { Tree } from "@angular-devkit/schematics"; +import { SchematicTestRunner } from "@angular-devkit/schematics/testing"; +import * as path from "path"; + +import { Module } from "@enums/Module"; +import { Schematic } from "@enums/Schematic"; +import expectedWebUserManagementFiles from "@janush-schematics/web/authentication/userManagement/data/expectedNewFiles.json"; +import { emptyJanush } from "@mocks/janush"; +import * as janush from "@utility/janushJson"; + +const collectionPath = path.join(__dirname, "../../../collection.json"); + +describe("userManagement", () => { + it("should create all user management files", async () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + + jest.spyOn(janush, "readJanushJSON").mockReturnValue(emptyJanush); + jest.spyOn(janush, "updateJanushJSON").mockImplementation(); + + const tree = await runner + .runSchematicAsync( + Schematic.WEB, + { + name: "janush-app", + modules: [Module.AUTHENTICATION], + userManagement: true, + e2e: false, + }, + Tree.empty(), + ) + .toPromise(); + + expect(tree.files).toIncludeEvery(expectedWebUserManagementFiles); + }); + + it("shouldn't create any user management files", async () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + + jest.spyOn(janush, "readJanushJSON").mockReturnValue(emptyJanush); + jest.spyOn(janush, "updateJanushJSON").mockImplementation(); + + const tree = await runner + .runSchematicAsync( + Schematic.WEB, + { + name: "janush-app", + modules: [Module.AUTHENTICATION], + userManagement: false, + e2e: false, + }, + Tree.empty(), + ) + .toPromise(); + + // we want to create this 2 files + expect(tree.files).not.toIncludeSome( + expectedWebUserManagementFiles.filter( + (item) => item !== "/web/src/routing/Routes.tsx" && item !== "/web/src/routing/paths.ts", + ), + ); + }); +}); diff --git a/schematics/web/authentication/userManagement/index.ts b/schematics/web/authentication/userManagement/index.ts new file mode 100644 index 00000000..4013f29b --- /dev/null +++ b/schematics/web/authentication/userManagement/index.ts @@ -0,0 +1,42 @@ +import { strings } from "@angular-devkit/core"; +import { + apply, + applyTemplates, + chain, + // filter, + MergeStrategy, + mergeWith, + move, + // noop, + Rule, + schematic, + SchematicContext, + Tree, + url, +} from "@angular-devkit/schematics"; + +import { Module } from "@enums/Module"; +import { Schematic } from "@enums/Schematic"; + +import { Schema } from "../schema"; + +export const userManagementGenerator = (options: Schema): Rule => { + const isAuth = options.modules.includes(Module.AUTHENTICATION); + + return (_tree: Tree, _context: SchematicContext) => { + return chain([ + mergeWith( + apply(url("./files"), [ + applyTemplates({ + isAuth, + ...strings, + ...options, + }), + move(Schematic.WEB), + ]), + MergeStrategy.Overwrite, + ), + schematic("applyPrettier", {}), + ]); + }; +}; diff --git a/schematics/web/authentication/utils/packageJsonExtender.ts b/schematics/web/authentication/utils/packageJsonExtender.ts index 0934f9d1..74618d35 100644 --- a/schematics/web/authentication/utils/packageJsonExtender.ts +++ b/schematics/web/authentication/utils/packageJsonExtender.ts @@ -16,6 +16,7 @@ export const packageJsonExtender = (tree: Tree) => { "@layouts/(.*)": "/src/layouts/$1", "@routing/(.*)": "/src/routing/$1", "@themes/(.*)": "/src/themes/$1", + "@validations/(.*)": "/src/validations/$1", }, }, }); diff --git a/schematics/web/authentication/utils/tsConfigExtender.ts b/schematics/web/authentication/utils/tsConfigExtender.ts index 08b0f1eb..823ad59d 100644 --- a/schematics/web/authentication/utils/tsConfigExtender.ts +++ b/schematics/web/authentication/utils/tsConfigExtender.ts @@ -12,6 +12,7 @@ export const tsConfigExtender = (tree: Tree) => { "@interfaces/*": ["./src/interfaces/*"], "@janush-types/*": ["./src/types/*"], "@utils/*": ["./src/utils/*"], + "@validations/*": ["./src/validations/*"], }, }, }); diff --git a/schematics/web/collection.json b/schematics/web/collection.json index 317fb4a8..44b7e0fa 100644 --- a/schematics/web/collection.json +++ b/schematics/web/collection.json @@ -16,6 +16,10 @@ "factory": "./authentication/index#webAuthenticationGenerator", "schema": "./authentication/schema.json" }, + "web.userManagement": { + "description": "User Management", + "factory": "./authentication/userManagement/index#userManagementGenerator" + }, "web.idP": { "description": "Additional identity providers", "factory": "./authentication/idP/index#identityProvidersGenerator" diff --git a/schematics/web/janush/files/config-overrides.js.template b/schematics/web/janush/files/config-overrides.js.template index 5748a421..eabea194 100644 --- a/schematics/web/janush/files/config-overrides.js.template +++ b/schematics/web/janush/files/config-overrides.js.template @@ -13,6 +13,7 @@ module.exports = function override(config) { "@layouts": path.resolve(__dirname, "src/layouts"), "@routing": path.resolve(__dirname, "src/routing"), "@themes": path.resolve(__dirname, "src/themes"), + "@validations": path.resolve(__dirname, "src/validations"), }, }; return config; diff --git a/schematics/web/janush/files/package.json.template b/schematics/web/janush/files/package.json.template index 7e4f8e09..72085bdd 100644 --- a/schematics/web/janush/files/package.json.template +++ b/schematics/web/janush/files/package.json.template @@ -10,6 +10,7 @@ "@mui/icons-material": "^5.8.3", "@mui/material": "^5.8.3", "@mui/styles": "^5.8.3", + "@mui/x-data-grid": "^5.10.0", "history": "5.3.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/schematics/web/janush/files/src/layouts/TopAppBar/TopAppBar.tsx.template b/schematics/web/janush/files/src/layouts/TopAppBar/TopAppBar.tsx.template index 85dd05f3..bebcfd12 100644 --- a/schematics/web/janush/files/src/layouts/TopAppBar/TopAppBar.tsx.template +++ b/schematics/web/janush/files/src/layouts/TopAppBar/TopAppBar.tsx.template @@ -7,7 +7,11 @@ import { useUserContext } from "@features/UserProvider/useUserContext"; import { NavLink } from "react-router-dom"; import { Paths } from "@routing/paths";<% } %> -export const TopAppBar: VFC = () => { +interface Props { + showLogo?: boolean; +} + +export const TopAppBar: VFC = ({ showLogo = true }) => { <% if (isAuth) { %>const { user } = useUserContext(); const signOut = async () => { @@ -19,8 +23,8 @@ export const TopAppBar: VFC = () => { };<% } %> return ( - - + + {showLogo && } <% if (isAuth) { %>{user ? (